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 中 |>
的等效运算符是 >>=
?
例如:
Haskell:
getLine >>= putStrLn
F#:
stdin.ReadLine() |> stdout.Write
不是真的。如果您将 m
专门化为 IO
,那么会有一些表面上的相似之处,所以也许 (>>=) @IO
是 有点像 F# 的 [=18] =],但一般来说,相似性不成立。
如果我们将 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
这与 |>
有很大不同,因为我们没有 运行ning 任何东西,只是构建一个稍后将应用于值的结构(解析器),但是代码仍然在谈论最终将提供的价值(在 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 代码定义的,但它们仍然非常有用,因此它们比 [=16] 更有助于理解 >>=
和 Monad
的本质=]是。
最后说一句,Haskell 是否具有与 F# 的 |>
类似的功能。它叫做 &
,它来自 Data.Function
模块。它与 F# 中的类型相同:
(&) :: a -> (a -> b) -> b
这个函数本身就很有用,但它与 monad 无关。
虽然 F# 不区分纯操作和非纯操作,但它确实有 monad 的概念。当您使用 computation expressions 时,这是最明显的。为了实现一个计算表达式,你必须实现monadic bind。在 F# 文档中,它必须具有类型 M<'T> * ('T -> M<'U>) -> M<'U>
,尽管这是伪代码,因为像 M<'T>
这样的类型不是正确的 F# 语法。
F# 带有一些内置的 monad,例如 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
另一方面,如果您的字符串已经在 option
值中,则您必须使用单子绑定:
> Some "42" |> Option.bind tryParse;;
val it : int option = Some 42
|>
运算符也存在于Haskell中,但你必须导入Data.Function
:
Prelude Data.Function> :t (&)
(&) :: a -> (a -> b) -> b
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# 的 [=18] =],但一般来说,相似性不成立。
如果我们将 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
这与 |>
有很大不同,因为我们没有 运行ning 任何东西,只是构建一个稍后将应用于值的结构(解析器),但是代码仍然在谈论最终将提供的价值(在 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 代码定义的,但它们仍然非常有用,因此它们比 [=16] 更有助于理解 >>=
和 Monad
的本质=]是。
最后说一句,Haskell 是否具有与 F# 的 |>
类似的功能。它叫做 &
,它来自 Data.Function
模块。它与 F# 中的类型相同:
(&) :: a -> (a -> b) -> b
这个函数本身就很有用,但它与 monad 无关。
虽然 F# 不区分纯操作和非纯操作,但它确实有 monad 的概念。当您使用 computation expressions 时,这是最明显的。为了实现一个计算表达式,你必须实现monadic bind。在 F# 文档中,它必须具有类型 M<'T> * ('T -> M<'U>) -> M<'U>
,尽管这是伪代码,因为像 M<'T>
这样的类型不是正确的 F# 语法。
F# 带有一些内置的 monad,例如 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
另一方面,如果您的字符串已经在 option
值中,则您必须使用单子绑定:
> Some "42" |> Option.bind tryParse;;
val it : int option = Some 42
|>
运算符也存在于Haskell中,但你必须导入Data.Function
:
Prelude Data.Function> :t (&)
(&) :: a -> (a -> b) -> b