繁体   English   中英

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

[英]Is Haskell's bind operator (>>=) equivalent to F#'s forward pipe operator (|>)?

输入Haskell绑定运算符的签名(>> =):

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

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

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

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

例如:

哈斯克尔:

getLine >>= putStrLn

F#:

stdin.ReadLine() |> stdout.Write

并不是的。 如果你擅长mIO ,然后有一些表面上的相似,也许这是真的, (>>=) @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代码定义,但它们仍然非常有用,因此它们与理解>>=Monad的本质比IO更为相关。


最后一点,Haskell 确实有一个函数就像F#的|> 它被称为& ,它来自Data.Function模块。 它与F#中的类型相同:

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

这个函数本身非常有用,但它与monad无关。

虽然F#不区分纯操作和不纯操作,但它确实具有monad的概念。 使用计算表达式时,这是最明显的。 要实现计算表达式,必须实现monadic绑定 在F#文档中,它必须具有类型M<'T> * ('T -> M<'U>) -> M<'U> ,尽管这是伪代码,因为像M<'T>这样的类型M<'T>不是正确的F#语法。

F#带有一些内置的monad,例如Async<'a>'a list 'a seq 你也可以简单地为'a optionResult创建计算表达式,虽然我不认为其中任何一个是内置的。

您可以仔细阅读各种计算表达式构建器的源代码,以确定如何为每个AJFarmar实现AJFarmar绑定,但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#helper函数将字符串解析为整数:

open System

let tryParse s =
    match Int32.TryParse s with
    | true, i -> Some i
    | _ -> None

如果你有一个字符串,你可以使用正向管道:

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

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM