输入Haskell绑定运算符的签名(>> =):
m a -> (a -> m b) -> m b
输入F#的前向管道运算符(|>)的签名:
'a -> ('a -> 'b) -> 'b
他们看起来很相似 考虑到F#的不纯性,|>
Haskell中的等价运算符是>>=
?
例如:
哈斯克尔:
getLine >>= putStrLn
F#:
stdin.ReadLine() |> stdout.Write
Alexis King..
9
并不是的.如果你专门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代码,但他们还是非常有用的,所以他们更有助于理解的本质>>=
和Monad
比IO
是.
最后,Haskell 确实有一个像F#一样的功能|>
.它被称为&
,它来自Data.Function
模块.它与F#中的类型相同:
(&) :: a -> (a -> b) -> b
这个函数本身非常有用,但它与monad无关.
1> Alexis King..:
并不是的.如果你专门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代码,但他们还是非常有用的,所以他们更有助于理解的本质>>=
和Monad
比IO
是.
最后,Haskell 确实有一个像F#一样的功能|>
.它被称为&
,它来自Data.Function
模块.它与F#中的类型相同:
(&) :: a -> (a -> b) -> b
这个函数本身非常有用,但它与monad无关.
2> Mark Seemann..:
虽然F#不区分纯操作和不纯操作,但它确实具有monad的概念.使用计算表达式时,这是最明显的.要实现计算表达式,必须实现monadic绑定.在F#文档中,它必须具有类型M<'T> * ('T -> M<'U>) -> M<'U>
,尽管这是伪代码,因为类似的类型M<'T>
不是正确的F#语法.
F#附带了一些内置的单子,比如Async<'a>
,'a list
,'a seq
.您也可以为'a option
和创建计算表达式Result
,尽管我认为这些都不是内置的.
您可以仔细阅读各种计算表达式构建器的源代码,以确定如何为每个构造实现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#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