2

我经常遇到这种情况,需要完成几个顺序操作。如果每个操作都专门使用上一步中的数据,那么我可以很高兴地做类似pipe(startingData, TE.chain(op1), TE.chain(op2), TE.chain(op3), ...). op2当还需要来自 的数据时,我找不到一个很好的方法来编写这个startingData,没有一堆嵌套回调。

在下面的示例中,如何避免厄运金字塔?

declare const op1: (x: {one: string}) => TE.TaskEither<Error, string>;
declare const op2: (x: {one: string, two: string}) => TE.TaskEither<Error, string>;
declare const op3: (x: {one: string, two: string, three: string}) => TE.TaskEither<Error, string>;

pipe(
  TE.of<Error, string>('one'),
  TE.chain((one) =>
    pipe(
      op1({ one }),
      TE.chain((two) =>
        pipe(
          op2({ one, two }),
          TE.chain((three) => op3({ one, two, three }))
        )
      )
    )
  )
);

4

2 回答 2

6

这个问题有一个解决方案,它被称为“do notation”。它已经有fp-ts-contrib一段时间了,但现在它也有一个使用函数(在所有单子类型上定义)嵌入到fp-ts自身的版本。bind基本思想类似于我在下面所做的 - 我们将计算结果绑定到特定名称,并在我们进行时在“上下文”对象中跟踪这些名称。这是代码:

pipe(
  TE.of<Error, string>('one'),
  TE.bindTo('one'), // start with a simple struct {one: 'one'}
  TE.bind('two', op1), // the payload now contains `one`
  TE.bind('three', op2), // the payload now contains `one` and `two`
  TE.bind('four', op3), // the payload now contains `one` and `two` and `three`
  TE.map(x => x.four)  // we can discharge the payload at any time
)

原答案如下

我提出了一个我不太自豪的解决方案,但我正在分享它以获得可能的反馈!

首先,定义一些辅助函数:

function mapS<I, O>(f: (i: I) => O) {
  return <R extends { [k: string]: I }>(vals: R) =>
    Object.fromEntries(Object.entries(vals).map(([k, v]) => [k, f(v)])) as {
      [k in keyof R]: O;
    };
}
const TEofStruct = <R extends { [k: string]: any }>(x: R) =>
  mapS(TE.of)(x) as { [K in keyof R]: TE.TaskEither<unknown, R[K]> };

mapS允许我将函数应用于对象中的所有值(子问题 1:是否有内置函数可以让我这样做?)。TEofStruct使用此函数将值的结构转换TaskEither为这些值的 s 结构。

TEofStruct我的基本想法是使用and将新值与以前的值一起累积sequenceS。到目前为止,它看起来像这样:

pipe(
  TE.of({
    one: 'one',
  }),
  TE.chain((x) =>
    sequenceTE({
      two: op1(x),
      ...TEofStruct(x),
    })
  ),
  TE.chain((x) =>
    sequenceTE({
      three: op2(x),
      ...TEofStruct(x),
    })
  ),
  TE.chain((x) =>
    sequenceTE({
      four: op3(x),
      ...TEofStruct(x),
    })
  )
);

感觉就像我可以编写某种辅助函数来减少样板sequenceTETEofStruct这里,但我仍然不确定这是否是正确的方法,或者是否有更惯用的模式!

于 2020-11-06T17:58:55.240 回答
0

官方 fp-ts DO 符号在这里:https ://gcanti.github.io/fp-ts/guides/do-notation.html

但是嵌套逻辑提取到它自己的函数并没有错。

于 2021-09-28T11:57:31.103 回答