2

去年我花了一些时间尝试学习 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')

getFoldableComposition、option 和 array 现在已弃用。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')

那甚至都没有编译,所以显然我离基地很远。

有人可以告诉我正确的替换方法吗?getFoldableComposition在我们讨论的时候,解释一下“使用小而具体的实例”是什么option意思array?另外,还有什么我显然做错了吗?

谢谢!

4

1 回答 1

7

让我们从你的问题开始

对于选项和数组,“改用小型特定实例”是什么意思?

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

许多函数,例如getFoldableCompositionsequenceT使用“高级类型”非常通用地定义,并要求您传入类型类实例以获取您希望函数使用的数据类型。因此,例如,sequenceT要求您传递一个Apply实例,例如

assert.deepEqual(
  sequenceT(O.option)([O.some(1), O.none]), 
  O.none
)

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

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

assert.deepEqual(
  sequenceT(O.Apply)([O.some(1), O.none]), 
  O.none
)

现在捆绑器知道只有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

首先您应该注意,此功能需要三个应用程序才能完全评估。首先它采用类型类实例,然后是累加器和归约函数,最后是我们正在归约的数据类型。

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

然后它将多态类型B的类型Type作为 reduce(也称为“累加器”)的初始值,以及一个二进制函数,该函数采用多态A类型TypeB返回B。这是 reduceRight 的典型签名。

然后它需要一个看起来很吓人的类型,它正在使用更高种类的类型。我会将其发音为“F of G of A”或F<G<A>>. 最后它返回B,减少的值。

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

通过查看您的代码,您似乎想将 a 缩减Array<Option<string>>为 a stringArray<Option<string>>是您要指定的更高种类的类型。您只需将“F of G of A”替换为“字符串选项数组”。因此,在 的签名中reduceRightF是 的Foldable实例,Array并且G是 的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)

然后我们用初始累加器和一个归约函数来调用这个 reduce,该函数接受其中的值Array<Option<?>>isstring和累加器的类型,也就是string。在您的初始代码中,您使用concat的是 for 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)

最后,它已经准备好接受我们的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")

为了简化所有这些,我们可以使用更惯用的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"
)

我知道这很多,但希望它能让我们对正在发生的事情有所了解。reduceRight非常通用,几乎没有其他 TypeScript 库试图做到这一点,所以如果你花一些时间来理解它是完全正常的。更高种类的类型不是 TypeScript 的内置功能,而且fp-ts绕过 TS 限制的方式无疑是一种 hack。但请继续玩耍和尝试。这一切最终都会开始点击。

于 2021-06-07T16:38:36.250 回答