Haskell的绑定运算符(>>=)的类型签名:
m a -> (a -> m b) -> m b
F#的前向管道运算符(|>)的类型签名:
'a -> ('a -> 'b) -> 'b
他们看起来很相似。考虑到F#的不纯性质,Haskell中与
|>
等价的操作符是>>=
?例如:
Haskell:
getLine >>= putStrLn
F#:
stdin.ReadLine() |> stdout.Write
m
专门用于 IO
,那么可能存在一些表面上的相似之处,因此也许 (>>=) @IO
确实有点像 F# 的 |>
,但总的来说,这种相似性并不成立。m
专门用于 Maybe
,那么 >>=
就像 Option.bind
,只是参数顺序颠倒了(这很有道理,因为 >>=
的发音是“bind”)。ghci> Just [1, 2, 3] >>= headMay
Just 1
ghci> Just [] >>= headMay
Nothing
ghci> Nothing >>= headMay
Nothing
m
专门化为Either e
,那么>>=
会执行与Maybe
类似的操作,但会在Left
值上短路,而不是在Nothing
上。这些示例有点类似于使用带有引发异常的函数的|>
,但它们并不完全相同。m
专门化为Parser
(来自例如megaparsec
包),则>>=
会生成一个新解析器,它运行第一个解析器,并使用其结果确定要运行哪个解析器。例如,下面定义了一个解析器,该解析器生成解析两个数字或非数字后跟任意字符的解析器:p :: Parser Char
p = anyChar >>= \c -> if isDigit c then digit else anyChar
|>
大不相同,因为我们并没有运行任何东西,只是构建了一个结构(解析器),稍后将应用于一个值,但代码仍然谈论最终将提供的值(在c
绑定中)。m
专门用于(->) r
,那么>>=
实现了一种隐式参数传递。例如,如果我们有一组接受公共参数的函数:f :: Key -> String
g :: String -> Key -> Char
h :: Char -> Key -> Bool
如果我们想将它们组合在一起,并将同样的第一个参数传递给它们,就可以使用>>=
:
ghci> :t f >>= g >>= h
f >>= g >>= h :: Key -> Bool
这与|>
显然不同,因为我们执行的是一种函数组合,而不是函数应用。
我可以继续列举许多例子,但列出几个例子可能并没有比直接列出更多有帮助。重点是>>=
不仅仅用于序列化具有效果的事物,它是一个更通用的抽象,其中序列化IO
操作是一种特殊情况。当然,IO
的情况在实践中很有用,但它也可能是理论上最不有趣的,因为它有点神奇(IO
被烘焙进了运行时)。>>=
的这些其他用途完全不神奇;它们完全使用普通的纯Haskell代码定义,但它们仍然非常有用,因此它们比IO
更相关于理解>>=
和Monad
的本质。
最后提一句,Haskell确实有一个类似F#的|>
函数。它称为&
,来自Data.Function
模块。它具有与F#中相同的类型:
(&) :: a -> (a -> b) -> b
这个函数本身非常有用,但与单子无关。
Key
这个东西行不通:a)我们需要 f :: Key -> String
,b)Key
应该是另外两个参数的第二个(或者应该使用 (<*>)
)。2) (>>=)
(实际上是 (=<<)
)是一种应用形式。有一个强大的塔:对于纯函数,使用 $
/(&)
;对于 Functor
,使用 <$>
;对于 Applicative
,使用 <*>
/<**>
;对于 Monad
,使用 (=<<)
/(>>=)
。M<'T> * ('T -> M<'U>) -> M<'U>,尽管这是伪代码,因为像M<'T>这样的类型不是正确的F#语法。
F#带有一些内置的单子,例如Async<'a>,'a list,'a seq。您还可以轻松创建'a option和Result的计算表达式,尽管我认为这些都没有内置。
您可以查看各种计算表达式生成器的源代码,以确定如何为每个实现单子绑定,但是AJFarmar是正确的,它们通常被称为collect:
> List.collect;;
val it : (('a -> 'b list) -> 'a list -> 'b list)
> Array.collect;;
val it : (('a -> 'b []) -> 'a [] -> 'b [])
> Seq.collect;;
val it : (('a -> #seq<'c>) -> seq<'a> -> seq<'c>)
但并非总是这样。有时操作被称为bind
:
> Option.bind;;
val it : (('a -> 'b option) -> 'a option -> 'b option)
为了说明,考虑这个小的F#辅助函数将字符串解析为整数:
open System
let tryParse s =
match Int32.TryParse s with
| true, i -> Some i
| _ -> None
如果您有一个字符串,可以使用前向管道:
> "42" |> tryParse;;
val it : int option = Some 42
另一方面,如果您的字符串已经在
|>
就像$
。>>=
就像*.collect
,但更通用。 - AJF