几个小时前问如何在 F# 中使用鱼(>=>,Kleisli 组合)运算符实现
map
?,'kaefer 的回答让我大吃一惊:
let fmap f = id >=> (Ok << f)
它的简单性是完美的,但是当我查看类型签名时,它不应该工作。我已经研究了近一个小时,所以我可能错过了 F# 如何计算表达式的一些基本知识...
郑重声明,这些是
bind
、switch
和 >=>
的实现:
let bind
( f : 'a -> Result<'b,'c>)
(result : Result<'a,'c>)
=
match result with
| Ok o -> f o
| Error e -> Error e
let (>=>)
(f : 'a -> Result<'b,'error>)
(g : 'b -> Result<'c,'error>)
=
f >> (bind g)
我的下一次尝试是使用
>=>
的替代实现:
let (>=>>)
(f : 'a -> Result<'b,'error>)
(g : 'b -> Result<'c,'error>)
x
=
match (f x) with
| Ok o -> g o
| Error e -> Error e
这是
dotnet fsi
上的测试调用:
(id >=>> (Ok << ((+) 2) : int -> Result<int,string>))
((Ok 27) : Result<int,string>)
//=> Ok 29
我已经陷入了为什么
>=>>
不会在 id
作为第一个参数时爆炸?
这就是我认为的评估方式,但显然不是这样:
id >=>> switch ((+) 2)
|
V
(>=>>) id (switch ((+) 2))
|
V
match (id x) with
| Ok o -> (Ok << ((+) 2)) o
| Error e -> Error e
由于表达式
let fmap f = id >=> (Ok << f)
有括号,我们需要先计算内部表达式。这就是Ok << f
部分。
Ok << f
是什么意思?你可以询问FSI:
> Ok;;
val it: ResultValue: 'a -> Result<'a,'b>
所以,这是一个接受
'a
并返回 Result<'a,'b>
的函数。
好吧,那
Ok << f
呢?你不能只向 FSI 询问这个问题,因为单独来看 f
并未定义,但你可以询问 FSI 采用 f
的 lambda 表达式会是什么样子:
> fun f -> Ok << f;;
val it: f: ('a -> 'b) -> ('a -> Result<'b,'c>)
从中我们了解到,FSI 和 F# 编译器推断
f
必须是一个接受 'a
并返回 'b
的函数。这是有道理的,因为它是 Ok << f
表达式的最一般解释:您采用任何函数 f
并使用 Ok
函数组合其输出,并且您将获得 'a -> Result<'b,'c>
函数作为返回值。
最后,那么
id >=> (Ok << f)
怎么样?
Klesli 运算符
>=>
采用两个类似“电梯”的函数并将它们组合起来。我们已经知道右侧的表达式是什么样的:
('a -> Result<'b,'c>)
因此,左侧必须是一些返回
Result<'a,'c>
:?? -> Result<'a,'c>
。
并且由于
id
返回其输入('T -> 'T)
,因此只能推断其具有类型
Result<'a,'c> -> Result<'a,'c>
所以这一切都是类型检查。这是一种非常迂回的实现方式
fmap
,但它确实有效。