开始使用 fp-ts:应用应用柯里化应用应用提升一般问题解决了吗?

2025-06-08

fp-ts入门:Applicative

柯里化

申请

应用

起重

一般问题解决了吗?

上一篇文章中,我们看到,我们可以f: (a: A) => F<B>将纯程序提升为一个提供函子实例的函数,g: (b: B) => C从而组成一个有效的程序。glift(g): (fb: F<B>) => F<C>F

计划 f 程序g 作品
纯的 纯的 g ∘ f
有效的 纯(一元) lift(g) ∘ f

但是g必须是一元的,也就是说它只能接受一个参数作为输入。如果g接受两个参数呢?我们还能g只使用函子实例来提升吗?好吧,我们试试看!

柯里化

首先,我们必须建立一个函数,该函数接受两个参数,假设类型为Band C(我们可以使用元组),并返回一个类型为D

g: (args: [B, C]) => D
Enter fullscreen mode Exit fullscreen mode

我们可以使用一种称为curryingg的技术来重写

柯里化是一种将一个接受多个参数的函数的求值转换为一系列函数求值的技术,每个函数只有一个参数。例如,一个函数接受两个参数,一个来自B,一个来自C,输出为D,通过柯里化,它被转换为一个接受一个参数的函数,输出为C,输出BC

(来源:wikipedia.org 上的 currying

所以我们可以重写g

g: (b: B) => (c: C) => D
Enter fullscreen mode Exit fullscreen mode

我们想要的是一个提升操作,liftA2为了将它与旧的 区分开来lift,我们不要调用它,它输出具有以下签名的函数

liftA2(g): (fb: F<B>) => (fc: F<C>) => F<D>
Enter fullscreen mode Exit fullscreen mode

我们怎么才能做到这一点?因为g现在是一元的,我们可以使用函子实例和我们之前的lift

lift(g): (fb: F<B>) => F<(c: C) => D>
Enter fullscreen mode Exit fullscreen mode

但现在我们陷入了困境:没有对函子实例的合法操作能够将解包F<(c: C) => D>为函数(fc: F<C>) => F<D>

申请

因此,让我们引入一个Apply拥有这种解包操作的新抽象(名为ap

interface Apply<F> extends Functor<F> {
  ap: <C, D>(fcd: HKT<F, (c: C) => D>, fc: HKT<F, C>) => HKT<F, D>
}
Enter fullscreen mode Exit fullscreen mode

ap函数基本上是unpack重新排列参数

unpack: <C, D>(fcd: HKT<F, (c: C) => D>) => ((fc: HKT<F, C>) => HKT<F, D>)
ap:     <C, D>(fcd: HKT<F, (c: C) => D>, fc: HKT<F, C>) => HKT<F, D>
Enter fullscreen mode Exit fullscreen mode

因此ap可以从中推导出来unpack(反之亦然)。

注意HKT类型是fp-ts表示通用类型构造函数的方式(轻量级更高类型多态性论文中提出的一种技术),因此当您看到时,HKT<F, X>您可以想到F应用于类型的类型构造函数X(即F<X>)。

应用

此外,如果存在一个操作,能够将类型的值提升A为 类型的值,那就方便了F<A>。这样,我们可以通过提供liftA2(g)类型的参数,或者提升和类型的值来调用该函数F<B>F<C>BC

因此,让我们介绍一下Applicative基于Apply并拥有此类操作的抽象(名为of

interface Applicative<F> extends Apply<F> {
  of: <A>(a: A) => HKT<F, A>
}
Enter fullscreen mode Exit fullscreen mode

让我们看看Applicative一些常见数据类型的实例

例子F = Array

import { flatten } from 'fp-ts/Array'

const applicativeArray = {
  map: <A, B>(fa: Array<A>, f: (a: A) => B): Array<B> => fa.map(f),
  of: <A>(a: A): Array<A> => [a],
  ap: <A, B>(fab: Array<(a: A) => B>, fa: Array<A>): Array<B> =>
    flatten(fab.map(f => fa.map(f)))
}
Enter fullscreen mode Exit fullscreen mode

例子F = Option

import { Option, some, none, isNone } from 'fp-ts/Option'

const applicativeOption = {
  map: <A, B>(fa: Option<A>, f: (a: A) => B): Option<B> =>
    isNone(fa) ? none : some(f(fa.value)),
  of: <A>(a: A): Option<A> => some(a),
  ap: <A, B>(fab: Option<(a: A) => B>, fa: Option<A>): Option<B> =>
    isNone(fab) ? none : applicativeOption.map(fa, fab.value)
}
Enter fullscreen mode Exit fullscreen mode

例子F = Task

import { Task } from 'fp-ts/Task'

const applicativeTask = {
  map: <A, B>(fa: Task<A>, f: (a: A) => B): Task<B> => () => fa().then(f),
  of: <A>(a: A): Task<A> => () => Promise.resolve(a),
  ap: <A, B>(fab: Task<(a: A) => B>, fa: Task<A>): Task<B> => () =>
    Promise.all([fab(), fa()]).then(([f, a]) => f(a))
}
Enter fullscreen mode Exit fullscreen mode

起重

Apply那么,给定一个for的实例,F我们现在可以写liftA2吗?

import { HKT } from 'fp-ts/HKT'
import { Apply } from 'fp-ts/Apply'

type Curried2<B, C, D> = (b: B) => (c: C) => D

function liftA2<F>(
  F: Apply<F>
): <B, C, D>(g: Curried2<B, C, D>) => Curried2<HKT<F, B>, HKT<F, C>, HKT<F, D>> {
  return g => fb => fc => F.ap(F.map(fb, g), fc)
}
Enter fullscreen mode Exit fullscreen mode

不错!但是如果函数有三个参数怎么办?我们还需要另一个抽象吗?

好消息是答案是否定的,Apply这就足够了

type Curried3<B, C, D, E> = (b: B) => (c: C) => (d: D) => E

function liftA3<F>(
  F: Apply<F>
): <B, C, D, E>(
  g: Curried3<B, C, D, E>
) => Curried3<HKT<F, B>, HKT<F, C>, HKT<F, D>, HKT<F, E>> {
  return g => fb => fc => fd => F.ap(F.ap(F.map(fb, g), fc), fd)
}
Enter fullscreen mode Exit fullscreen mode

实际上,给定一个实例,Apply我们可以为每个实例liftAn编写一个函数 n

注意.liftA1只是liftFunctor操作。

我们现在可以更新我们的“组成表”

计划 f 程序g 作品
纯的 纯的 g ∘ f
有效的 纯的,n-ary liftAn(g) ∘ f
在哪里liftA1 = lift

一般问题解决了吗?

还没有。还有一个重要的案例没有解决:如果两个方案都有效怎么办?

我们再次需要更多的东西:在下一篇文章中,我将讨论函数式编程最重要的抽象之一:monads

TLDR:函数式编程就是组合

鏂囩珷鏉ユ簮锛�https://dev.to/gcanti/getting-started-with-fp-ts-applicative-1kb3
PREV
fp-ts 入门:IO 错误处理提升
NEXT
函数式设计:代数数据类型 什么是ADT?乘积类型 和类型 函数式错误处理 结论