Haskell的绑定运算符(>> =)是否等于F#的正向管道运算符(|>)?

时间:2018-08-12 13:58:13

标签: haskell f# bind monads pipeline

Haskell绑定运算符(>> =)的类型签名:

m a -> (a -> m b) -> m b

F#的前向管道运算符(|>)的类型签名:

'a -> ('a -> 'b) -> 'b

它们看起来很相似。 考虑到F#的不纯性质, Haskell中|>的等效运算符是>>=

例如:

Haskell:

getLine >>= putStrLn

F#:

stdin.ReadLine() |> stdout.Write

2 个答案:

答案 0 :(得分:8)

不是。如果您将m专用于IO,则存在一些表面上的相似之处,因此(>>=) @IO 确实很像F#的|>,但总的来说,相似性并不成立。

如果我们将m专门化为Maybe,那么>>=就像Option.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代码定义,但是它们仍然非常有用,因此与>>=相比,它们与理解MonadIO的本质更为相关。


最后,Haskell 确实具有与F#的|>类似的功能。它称为&,它来自Data.Function模块。它具有与F#中相同的类型:

(&) :: a -> (a -> b) -> b

此功能本身非常有用,但与monad无关。

答案 1 :(得分:7)

尽管F#不能区分纯运算和不纯运算,但它确实有单子的概念。当您使用computation expressions时,这是最明显的。为了实现计算表达式,必须实现 monadic bind 。在F#文档中,该类型必须为M<'T> * ('T -> M<'U>) -> M<'U>,尽管这是伪代码,因为类似M<'T>的类型不是正确的F#语法。

F#带有一些内置的monad,例如Async<'a>'a list'a seq。您也可以为'a optionResult创建计算表达式,尽管我认为这些表达式都不是内置的。

您可以细读各种计算表达式生成器的源代码,以识别如何为每个表达式实现monadic绑定,但是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

另一方面,如果您的字符串已经在option值中,则必须使用单子绑定:

> Some "42" |> Option.bind tryParse;;
val it : int option = Some 42

Haskell中也存在|>运算符,但是您必须导入Data.Function

Prelude Data.Function> :t (&)
(&) :: a -> (a -> b) -> b