fp-ts中Option嵌套属性过滤提取的泛型函数类型定义及规范实现
问题描述
在使用fp-ts时,我经常会实现以下模式:
import * as O from 'fp-ts/Option'; interface Person { id: number; pet: O.Option<'dog' | 'cat'>; } const person: Person = { id: 1, pet: O.some('dog') }; // 最简单的场景: const maybePersonWithPet = pipe( person.pet, O.map(pet => ({ ...person, pet })), ); // 更常见的繁琐写法: 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' } }
这是针对嵌套Option属性的过滤操作,同时提取该嵌套属性的值。我希望将其泛化,因此编写了一个函数:
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);
请问filterAndExtractOption函数的正确类型定义是什么?我需要传入对象、对应Option<A>类型属性的键,同时提取A类型。另外,我想知道在fp-ts中有没有规范且简洁的实现方式?
解答
一、正确的类型定义
我们需要明确约束目标属性的类型为Option<A>,同时确保返回对象的类型准确提取出A。以下是两种严谨的写法:
写法1:条件类型约束
import * as O from 'fp-ts/Option'; import { pipe } from 'fp-ts/function'; function filterAndExtractOption<O, K extends keyof O, A>( object: O, key: K ): O extends { [P in K]: O.Option<A> } ? O.Option<Omit<O, K> & { [P in K]: A }> : never { return pipe( object[key] as O.Option<A>, O.map(value => ({ ...object, [key]: value })) ) as any; }
写法2:推断泛型参数
import * as O from 'fp-ts/Option'; import { pipe } from 'fp-ts/function'; function filterAndExtractOption<O, K extends keyof O>( object: O & { [P in K]: O.Option<infer A> }, key: K ): O.Option<Omit<O, K> & { [P in K]: A }> { return pipe( object[key], O.map(value => ({ ...object, [key]: value })) ) as any; }
两种写法都能让TypeScript自动推导A的类型,比如传入Person和'pet'时,返回类型会被正确推断为Option<{ id: number; pet: 'dog' | 'cat' }>,完全符合需求。
二、fp-ts中的规范简洁实现
不需要额外封装函数,直接用fp-ts内置函数就能组合出逻辑,也可以封装成更符合函数式风格的柯里化版本:
1. 直接组合现有函数
- 处理普通对象:
const extractPet = (p: Person) => pipe(p.pet, O.map(pet => ({ ...p, pet }))); const maybePersonWithPet = extractPet(person);
- 处理Option包裹的对象:
const maybePerson = O.some(person); const maybePersonWithPet = pipe( maybePerson, O.filterMap(p => pipe(p.pet, O.map(pet => ({ ...p, pet })))) );
2. 封装为柯里化复用函数
符合fp-ts的函数设计风格,方便与其他函数链式组合:
import * as O from 'fp-ts/Option'; import { pipe, flow } from 'fp-ts/function'; const filterAndExtractOption = <K extends string>(key: K) => <O extends { [P in K]: O.Option<infer A> }>( obj: O ): O.Option<Omit<O, K> & { [P in K]: A }> => pipe(obj[key], O.map(value => ({ ...obj, [key]: value }))); // 使用示例 const extractPet = filterAndExtractOption('pet'); const maybePersonWithPet = extractPet(person); const maybePersonWithPetFromOption = pipe(O.some(person), O.chain(extractPet));
内容的提问来源于stack exchange,提问作者Wayne Maurer
相关产品推荐
相关产品推荐

