我通过使用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
转换为IOEither
。chainEitherK
可以做到这一点,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风格一起工作,我们需要引入pipe
。pipe
是一个函数,它通过管道中列出的函数传递数据。
所以,与其这样做(命令式)
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不能隐式地在类型之间跳转(例如从IOEither
到Either
)。在我们的示例中,我们从input
变量开始,该变量具有IOEither
(缩短的IE
)类型,然后继续使用IE.chain
方法,该方法试图返回Either
值。
为了使它工作,我们可以引入一个函数来帮助我们转换这些类型。
pipe(
input,
IE.chain(inputValue => IE.fromEitherK(parse)(inputValue))
)
现在我们的chain
函数明确地知道parse
函数是通过使用fromEitherK
从Either
类型转换为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
函数)。