[英]Generically filtering on an fp-ts Option property and extracting the value
I often find myelf implementing the following pattern when using fp-ts:我经常发现自己在使用 fp-ts 时实现了以下模式:
interface Person {
id: number;
pet: O.Option<'dog' | 'cat'>;
}
const person: Person = { id: 1, pet: O.some('dog') };
// simplest case:
const maybePersonWithPet = pipe(
person.pet,
O.map(pet => ({ ...person, pet })),
);
// very often slightly more cumbersome:
const maybePersonWithPet2 = pipe(
O.some(person),
O.filterMap(p =>
pipe(
p.pet,
O.map(pet => ({ ...p, pet })),
),
),
);
console.log(maybePersonWithPet);
console.log(maybePersonWithPet2);
// { _tag: 'Some', value: { id: 1, pet: 'dog' }
So this is an option filter but it's on a nested option property, where the value of the nested property is extracted.所以这是一个选项过滤器,但它位于嵌套选项属性上,其中嵌套属性的值被提取。 I would like to generalise this, so I thought to write a function that I could call as follows:
我想概括这一点,所以我想写一个 function,我可以调用如下:
function filterAndExtractOption<O, K extends keyof O>(object: O, key: K): O.Option<Omit<O, K> & { K: any }> {
return pipe(
object[key] as any,
O.map(value => ({ ...object, [key]: value })),
) as any;
}
const maybePersonWithPet3 = filterAndExtractOption(person, 'pet');
console.log(maybePersonWithPet3);
const maybePersonWithPet4 = pipe(
O.some(person),
O.chain(p => filterAndExtractOption(p, 'pet')),
);
console.log(maybePersonWithPet4);
What would the correct type definition for the filterAndExtractOption
function be? filterAndExtractOption
function 的正确类型定义是什么? I need to pass an object, a property key which must be the key for an Option<A>
and I also need to extract the A
type.我需要传递一个 object,一个属性键,它必须是
Option<A>
的键,我还需要提取A
类型。
I also am wondering if there's a canonical and succinct way of doing this with fp-ts?我还想知道使用 fp-ts 是否有一种规范和简洁的方法来做到这一点?
Few things we need to take into account before we proceed:在我们继续之前,我们需要考虑一些事情:
key
argument should represent only Option
valuekey
参数应该只代表Option
值({...object, [key]: value }))
in TS always returns {[prop:string]: Value}
indexed type instead of expected Record<Key, Value>
({...object, [key]: value }))
总是返回{[prop:string]: Value}
索引类型而不是预期的Record<Key, Value>
object[key]
should be treated as an Option
value inside of function scope. object[key]
应被视为 function scope 内部的Option
值。1)
1)
In order to assure function scope that key
argument represents Option
value, you need to do this:为了确保 function scope
key
参数代表Option
值,您需要这样做:
type GetOptional<Obj> = Values<{
[Prop in keyof Obj]: Obj[Prop] extends O.Option<unknown> ? Prop : never;
}>;
const filter = <
OptionValue,
Obj,
Key extends GetOptional<Obj>
>(
obj: Obj & Record<Key, O.Option<OptionValue>>,
key: Key
) =>
pipe(
obj[key],
O.map((value) => extendObj(obj, key, value))
);
Please see my article and SO answer for more details and context.请参阅我的文章和SO 答案以获取更多详细信息和上下文。
GetOptional
- iterates through each key and checks whether value which represents this key is a subtype of O.Option<unknown>
or not. GetOptional
- 遍历每个键并检查代表该键的值是否是O.Option<unknown>
的子类型。 If it is - it returns Prop
name, otherwise - returns never.如果是 - 它返回
Prop
名称,否则 - 永远不会返回。
Values
- obtains a union of all values in object. Hence GetOptional<Person>
returns pet
, because this is a key which represents Option
value. Values
- 获得 object 中所有值的并集。因此GetOptional<Person>
返回pet
,因为这是代表Option
值的键。
2)
2)
I have provided helper function:我提供了帮手function:
const extendObj = <Obj, Key extends keyof Obj, Value>(
obj: Obj,
key: Key,
value: Value
) => ({ ...obj, [key]: value }) as Omit<Obj, Key> & Record<Key, Value>;
3)
:3)
: Then, we need to represent filtering in a type scope.然后,我们需要用类型 scope 表示过滤。
type InferOption<Opt> = Opt extends O.Some<infer Value> ? Value : never;
type FilterOption<Obj, Key extends GetOptional<Obj>> = {
[Prop in keyof Obj]: Prop extends Key ? InferOption<Obj[Prop]> : Obj[Prop];
};
InferOption
- extracts value from Option
InferOption
- 从Option
中提取值
FilterOption
- iterates through object and checks whether Prop
is a Key
which in turn represents Option
value. FilterOption
- 遍历 object 并检查Prop
是否是一个Key
,而 Key 又代表Option
值。 If yes - extracts option value, otherwise - returns non modified value.如果是 - 提取选项值,否则 - 返回未修改的值。
Let's put it all together:让我们把它们放在一起:
import * as O from "fp-ts/lib/Option";
import { pipe } from "fp-ts/lib/function";
interface Person {
id: number;
pet: O.Option<"dog" | "cat">;
}
const person: Person = { id: 1, pet: O.some("dog") };
const extendObj = <Obj, Key extends keyof Obj, Value>(
obj: Obj,
key: Key,
value: Value
) => ({ ...obj, [key]: value }) as Omit<Obj, Key> & Record<Key, Value>;
type Values<T> = T[keyof T];
type InferOption<Opt> = Opt extends O.Some<infer Value> ? Value : never;
type FilterOption<Obj, Key extends GetOptional<Obj>> = {
[Prop in keyof Obj]: Prop extends Key ? InferOption<Obj[Prop]> : Obj[Prop];
};
type GetOptional<Obj> = Values<{
[Prop in keyof Obj]: Obj[Prop] extends O.Option<unknown> ? Prop : never;
}>;
const filter = <
OptionValue,
Obj,
Key extends GetOptional<Obj>
>(
obj: Obj & Record<Key, O.Option<OptionValue>>,
key: Key
) =>
pipe(
obj[key],
O.map((value) => extendObj(obj, key, value))
) as FilterOption<Obj, Key>;
const maybePersonWithPet3 = filter(person, "pet");
maybePersonWithPet3.pet; // "dog" | "cat"
So captain-yossarian from Ukraine gave me an answer that worked, but the return type in his solution is rather hard to read.所以来自乌克兰的 captain-yossarian给了我一个有效的答案,但是他的解决方案中的返回类型很难阅读。 Inspired by his answer, I managed to get some help from another person on Discord, and now have a more readable solution.
受他回答的启发,我设法从 Discord 上的另一个人那里得到了一些帮助,现在有了一个更具可读性的解决方案。 I've also extended the function to take more than one key:
我还扩展了 function 以使用多个密钥:
type Values<T> = T[keyof T];
type GetOptional<Obj> = Values<{
[Prop in keyof Obj]: Obj[Prop] extends O.Option<unknown> ? Prop : never;
}>;
const sequenceProps = <A, Key extends GetOptional<A>>(
a: A,
...keys: Key[]
): O.Option<{ [K in keyof A]: K extends Key ? (A[K] extends O.Option<infer B> ? B : A[K]) : A[K] }> => {
const out = {} as any;
let allSome = true;
for (const key in a) {
if (keys.includes(key as any)) {
const value = a[key] as any;
if (O.isSome(value)) {
out[key] = value.value;
} else {
allSome = false;
break;
}
} else {
out[key] = a[key];
}
}
return allSome ? O.some(out) : O.none;
};
I can call the function as follows:我可以拨打 function,如下所示:
type PetType = 'dog' | 'cat';
type VehicleType = 'bicycle' | 'car';
interface Person {
id: number;
pet: O.Option<PetType>;
vehicle: O.Option<VehicleType>;
}
const person: Person = { id: 1, pet: O.some('dog'), vehicle: O.some('bicycle') };
const maybePersonWithPetAndVehicle = sequenceProps(person, 'pet', 'vehicle');
... and the return type is clean; ...并且返回类型是干净的; when I hover over the variable, I get:
当我 hover 超过变量时,我得到:
const maybePersonWithPetAndVehicle: O.Option<{
id: number;
pet: PetType;
vehicle: VehicleType;
}>
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.