getFoldableComposition、option、array 等弃用后的重构

Dam*_*yer 2 fp-ts

去年我花了一些时间尝试学习 FP-TS。我终于开始在项目中使用它,并且由于最近的重构,我的很多示例代码都被破坏了。我已经修复了一些破损,但正在与其他破损进行斗争。毫无疑问,它突出了我的 FP 知识的一个巨大整体!

我有这个:

import { strict as assert } from 'assert';
import { array } from 'fp-ts/Array';
import { getFoldableComposition, } from 'fp-ts/Foldable';
import { Monoid as MonoidString } from 'fp-ts/string'
import { none,some, option } from 'fp-ts/Option';

const F = getFoldableComposition(array, option)
assert.strictEqual(F.reduce([some('a'), none, some('c')], '', MonoidString.concat), 'ac')
Run Code Online (Sandbox Code Playgroud)

getFoldableComposition、选项和数组现已弃用。getFoldableComposition 上的评论说要使用reduce、foldMap 或reduceRight 来代替,所以除其他外,我尝试了这个。

import { strict as assert } from 'assert';
import { reduceRight } from 'fp-ts/Foldable';
import { Monoid as MonoidString } from 'fp-ts/string'
import { some } from 'fp-ts/Option';

assert.strictEqual(reduceRight([some('a'), none, some('c')], '', MonoidString.concat), 'ac')
Run Code Online (Sandbox Code Playgroud)

这甚至都没有编译,所以显然我偏离了基地。

有人可以向我展示正确的替换方法吗getFoldableComposition?在我们进行替换时,请解释一下 和 的“使用小型的特定实例代替”的含义是option什么array?另外,我还有什么明显做错的事情吗?

谢谢你!

Der*_*ing 8

让我们从你的问题开始

对于选项和数组来说,“使用小的、特定的实例代替”是什么意思?

在 v2.10.0之前fp-ts,类型类实例被分组在一起作为实现多个类的接口的单个​​记录,并且类型类记录以定义类的数据类型命名。因此,对于Array模块,array导出包含所有实例;它有mapforFunctorapforApply等。对于Optionoption记录与所有实例一起导出。等等。

许多函数(例如getFoldableComposition和 )sequenceT都是使用“高级类型”非常通用地定义的,并且要求您传入您希望函数使用的数据类型的类型类实例。因此,例如,sequenceT要求您传递一个Apply实例,例如

assert.deepEqual(
  sequenceT(O.option)([O.some(1), O.none]), 
  O.none
)
Run Code Online (Sandbox Code Playgroud)

要求像这样传递类型类实例的这些大记录,最终导致fp-ts应用程序和库代码中的树摇动效果不佳,因为 JS 捆绑器无法静态地告诉类型类记录的哪些成员正在被访问,哪些成员没有被访问。 t,所以即使只使用了一个,它最终也会包括所有这些。这会增加包大小,最终使用户的应用程序加载速度变慢和/或增加使用库的库的包大小。

这个问题的解决方案是将大类型类记录分开,并为每个类型类提供自己的记录。因此,现在每个数据类型模块都会导出小型的、单独的类型类实例,最终大型实例记录将被删除。所以现在你会使用sequenceT

assert.deepEqual(
  sequenceT(O.Apply)([O.some(1), O.none]), 
  O.none
)
Run Code Online (Sandbox Code Playgroud)

现在,捆绑器知道只Apply使用了方法,并且它可以从捆绑中删除未使用的实例。

因此,所有这一切的结果是不再使用大型实例记录,而仅使用较小的实例记录。

现在为您的代码。

我要说的第一件事是与编译器交谈。您的代码应该会出现编译错误。我看到的是这样的:

reduceRight 期望有 2 个参数,但得到了 3 个。

所以你传递了reduceRight太多参数,所以让我们看一下签名:

export declare function reduceRight<F extends URIS, G extends URIS>(
  F: Foldable1<F>,
  G: Foldable1<G>
): <B, A>(b: B, f: (a: A, b: B) => B) => (fga: Kind<F, Kind<G, A>>) => B
Run Code Online (Sandbox Code Playgroud)

首先您应该注意的是,这个函数是柯里化的,需要三次调用才能完全评估(即它被柯里化为三个单独的函数调用)。首先它采用类型类实例,然后是累加器和归约函数,最后它采用我们要归约的数据类型。

因此,首先它需要Foldable一个 kind 类型的实例Type -> Type,以及Foldable另一个(或相同)类型 kind 的另一个实例Type -> Type。这就是小型实例与大型实例记录发挥作用的地方。你会通过SomeDataType.Foldable而不是SomeDataType.someDataType

然后,它将Bkind 的多态类型Type作为 reduce 的初始值(也称为“累加器”),并返回一个采用Akind 的多态类型Type和的二元函数。这是reduceRight 的典型签名。BB

然后它需要一个看起来可怕的类型,它利用了更高类型的类型。我会将其发音为“F of G of A”或F<G<A>>。最后它返回B,即减少后的值。

听起来很复杂,但希望在此之后它不会看起来那么糟糕。

从您的代码来看,您似乎想将 an 减少Array<Option<string>>为 a stringArray<Option<string>>是您要指定的高级类型。您只需将“F of G of A”替换为“字符串选项数组”即可。因此,在 的签名中reduceRightF是 的Foldable实例ArrayG是 的Foldable实例Option

如果我们传递这些实例,我们将返回一个reduceRight专门用于选项数组的函数。

import * as A from 'fp-ts/Array'
import * as O from 'fp-ts/Option'
import { reduceRight } from 'fp-ts/Foldable'

const reduceRightArrayOption: <B, A>(
  b: B, 
  f: (a: A, b: B) => B) => (fga: Array<O.Option<A>>) => B = 
  reduceRight(A.Foldable, O.Foldable)
Run Code Online (Sandbox Code Playgroud)

然后我们用初始累加器和一个归约函数调用这个reduce,该函数接受其中的值Array<Option<?>>string累加器的类型,这也是string。在您的初始代码中,您使用的是concatfor string。这将在这里起作用,您将在模块Monoid<string>的实例上找到它string

import * as A from 'fp-ts/Array'
import * as O from 'fp-ts/Option'
import { reduceRight } from 'fp-ts/Foldable'
import * as string from 'fp-ts/string'

const reduceRightArrayOption: <B, A>(
  b: B, 
  f: (a: A, b: B) => B) => (fga: Array<O.Option<A>>) => B
  = reduceRight(A.Foldable, O.Foldable)

const reduceRightArrayOptionStringToString: (fga: Array<O.Option<string>>) => string
  = reduceRightArrayOption("", string.Monoid.concat)
Run Code Online (Sandbox Code Playgroud)

最后,它准备好接受我们的Array<O.Option<string>>.

import * as assert from 'assert'
import * as A from 'fp-ts/Array'
import * as O from 'fp-ts/Option'
import { reduceRight } from 'fp-ts/Foldable'
import * as string from 'fp-ts/string'

const reduceRightArrayOption: <B, A>(
  b: B, 
  f: (a: A, b: B) => B) => (fga: Array<O.Option<A>>) => B
  = reduceRight(A.Foldable, O.Foldable)

const reduceRightArrayOptionStringToString: (fga: Array<O.Option<string>>) => string
  = reduceRightArrayOption("", string.Monoid.concat)

const result = reduceRightArrayOptionStringToString([
  O.some('a'),
  O.none,
  O.some('c'),
])

assert.strictEqual(result, "ac")
Run Code Online (Sandbox Code Playgroud)

为了简化这一切,我们可以使用更惯用的pipe方法来调用reduceRight

import * as assert from "assert"
import { reduceRight } from "fp-ts/Foldable"
import * as string from "fp-ts/string"
import * as O from "fp-ts/Option"
import * as A from "fp-ts/Array"
import { pipe } from "fp-ts/lib/function"

assert.strictEqual(
  pipe(
    [O.some("a"), O.none, O.some("c")],
    reduceRight(A.Foldable, O.Foldable)(string.empty, string.Monoid.concat)
  ),
  "ac"
)
Run Code Online (Sandbox Code Playgroud)

我知道这已经很多了,但希望它能让大家清楚地了解正在发生的事情。reduceRight非常通用,几乎没有其他 TypeScript 库尝试这样做,因此如果您需要一段时间才能理解它,这是完全正常的。更高种类的类型并不是 TypeScript 的内置功能,而且fp-ts它的实现方式无疑是为了解决 TS 的限制而采取的一些技巧。但请继续玩耍和尝试。最终一切都会开始顺利。