You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 20:01:21