为什么`let fmap f = id >=> (好吧<< f)` work?

问题描述 投票:0回答:1

几个小时前问如何在 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
functional-programming f# monads
1个回答
0
投票

由于表达式

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
,但它确实有效。

© www.soinside.com 2019 - 2024. All rights reserved.