简体   繁体   English

如何在 fp-ts 中避免带有链的厄运金字塔?

[英]How can I avoid the pyramid of doom with chain in fp-ts?

I frequently come up against this situation, where I need to complete several sequential operations.我经常遇到这种情况,我需要完成几个顺序操作。 If each operation exclusively used data from the previous step, then I could happily do something like pipe(startingData, TE.chain(op1), TE.chain(op2), TE.chain(op3), ...) .如果每个操作都只使用上一步中的数据,那么我可以很高兴地执行诸如pipe(startingData, TE.chain(op1), TE.chain(op2), TE.chain(op3), ...) I can't find a nice way to write this when op2 also needs data from startingData , without a bunch of nested callbacks.op2也需要来自startingData数据而没有一堆嵌套回调时,我找不到写这个的好方法。

How can I avoid the pyramid of doom in the example below?如何避免下面示例中的厄运金字塔?

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 }))
        )
      )
    )
  )
);

There is a solution to the problem, and it's called "do notation".还有就是要解决问题的办法,这就是所谓的“做记号”。 It's been available in fp-ts-contrib for a while, but it now also has a version baked into fp-ts itself using the bind function (which is defined on all monadic types).它已经在fp-ts-contrib可用一段时间了,但现在它也有一个版本,它使用bind函数(在所有 monadic 类型上定义)烘焙到fp-ts本身中。 The basic idea is similar to what I was doing below - we bind computation results to a particular name, and track those names inside a "context" object as we go along.基本思想类似于我在下面所做的 - 我们将计算结果绑定到一个特定的名称,并在我们进行时在“上下文”对象中跟踪这些名称。 Here's the code:这是代码:

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
)

Original Answer below下面的原答案

I've come up with a solution that I'm not very proud of, but I'm sharing it for possible feedback!我想出了一个我并不引以为豪的解决方案,但我正在分享它以获取可能的反馈!

Firstly, define some helper functions:首先,定义一些辅助函数:

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 allows me to apply a function to all values in an object (subquestion 1: is there a builtin function that would allow me to do this?). mapS允许我将函数应用于对象中的所有值(子问题 1:是否有内置函数可以让我这样做?)。 TEofStruct uses this function to turn a struct of values into a struct of TaskEither s for those values. TEofStruct使用此函数将值结构TaskEither为这些值的TaskEither结构。

My basic idea is to accumulate the new value along with the previous values using TEofStruct and sequenceS .我的基本想法是使用TEofStructsequenceS将新值与以前的值一起累积。 So far it looks like this:到目前为止它看起来像这样:

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),
    })
  )
);

It feels like I could write some kind of helper function that combines sequenceTE with TEofStruct to reduce the boilerplate here, but I'm still not sure overall if this is the right approach, or if there is a more idiomatic pattern!感觉我可以编写某种辅助函数,将sequenceTETEofStruct结合起来以减少这里的样板文件,但我仍然不确定总体上这是否是正确的方法,或者是否有更惯用的模式!

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

But there is nothing wrong with nested logic extraction to its own function.但是嵌套逻辑提取到它自己的函数并没有错。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM