Kleisli箭头在fp-ts中解决了什么问题?



我通过使用fp-ts lib学习函数式编程,并注意到一些函数以K结尾,如:

  • chainTaskEitherK
  • chainEitherK
  • fromTaskK

我也在fp-ts文档中阅读了K后缀的解释,但不幸的是,我不能说一个例子给我一个如何在战场上使用它的坚实想法。

我想确切地知道他们解决了什么问题,以及没有他们的代码会是什么样子(看看好处)。

请考虑到我是这个话题的新手。

首先(从您分享的链接中的示例开始)。我认为Kleisli箭头只是一种类型的名称,看起来像:

<A>(value: A) => F<A>

,其中F是某个函子值。他们在你链接的文档中称它为构造函数哪个更准确?我的理解是,它只是一个函数,它接受一些非Functor值(解析示例中的string)并将其放入某个Functor。

当你已经有了一个Kleisli箭头,并且想要将它与其他函子函数值一起使用时,你就可以使用上面列出的这些帮助函数。在这个例子中,有

declare function parse(s: string): Either<Error, number>;

IOEither值可能来自用户输入。他们想把两者结合起来,如果是Right,基本上在输入上运行parse,最后使用parse的函数,签名为:

declare function parseForIO(s: string): IOEither<Error, number>;

这样返回类型就可以与输入类型兼容(这样我们就可以在IOEither上使用chain来组成更大的函数)。

因此,fromEitherK将基本parse函数包装在一些逻辑中,以自然地将生成的常规Either转换为IOEitherchainEitherK可以做到这一点,chain可以保存一些样板文件。

基本上,它解决了一个兼容性问题,当你的Kleisli箭头的返回值与chaining东西在一起时你需要的值不匹配。

除了@Souperman的解释,我想分享一下我对这个话题的调查

让我们从fp-ts文档中取一个已知的例子。

我们有一个input类型的变量IOEtiher

const input: IE.IOEither<Error, string> = IE.right('foo')

和函数,它们接受普通的string并返回E.Either

function parse(s: string): E.Either<Error, number> {
// implentation
}

如果我们想让这些代码以fp-ts风格一起工作,我们需要引入pipepipe是一个函数,它通过管道中列出的函数传递数据。

所以,与其这样做(命令式)

const input: IE.IOEither<Error, string> = IE.right('foo')
const value = input()
let result: E.Either<Error, number>
if (value._tag === 'Right') {
result = parse(value.right) // where value.right is our 'foo'
}

我们可以这样做

pipe(
input,
IE.chain(inputValue => parse(inputValue))
~~~~~~~~~~~~~~~~~ <- Error is shown
)

错误消息

Type 'Either<Error, number>' is not assignable to type 'IOEither<Error, unknown>'.

不幸的是,fp-ts不能隐式地在类型之间跳转(例如从IOEitherEither)。在我们的示例中,我们从input变量开始,该变量具有IOEither(缩短的IE)类型,然后继续使用IE.chain方法,该方法试图返回Either值。

为了使它工作,我们可以引入一个函数来帮助我们转换这些类型。

pipe(
input,
IE.chain(inputValue => IE.fromEitherK(parse)(inputValue))
)

现在我们的chain函数明确地知道parse函数是通过使用fromEitherKEither类型转换为IOEither类型的。

此时,我们可以看到fromEitherK是一个辅助函数,它的参数需要一个Kleisli函数,并返回一个新的返回类型的新函数。

为了更清楚,我们不需要使用K后缀,例如,如果我们的parse是一个值(而不是函数)。

代码看起来像

pipe(
input,
IE.chain(inputValue => IE.fromEither(parsed)) // I know this is useless code, but it shows its purpose
)

回到我们的例子。我们可以改进代码使其更具可读性

而不是

pipe(
input,
IE.chain(inputValue => IE.fromEitherK(parse)(inputValue))
)

我们可以这样做

pipe(
input,
IE.chain(IE.fromEitherK(parse))
)

甚至更多

pipe(
input,
IE.chainEitherK(parse)
)

总结据我所知,Kleisli箭头是接受参数并返回包装在容器中的结果的函数(如parse函数)。

最新更新