我们可以想象你问名字是为了打印它,然后让我们重写它。
在伪代码中,我们有
main =
print "what is your name"
bind varname with userReponse
print varname
然后,您的问题涉及第二条指令。
看看这个的语义。
- userReponse是一个返回用户输入的函数(getLine)
- varname是一个 var
- bind var with fun : 是将 var(varname) 与函数 (getLine) 的输出相关联的函数
或者正如你在 haskell 中所知道的,一切都是函数,那么我们的语义不太适合。
为了尊重这个成语,我们需要重新审视它。根据后面的反思,我们的bind函数的语义变成了bind fun with fun
由于我们不能有变量,因此要将参数传递给函数,乍一看,我们需要调用另一个函数来生成它们。因此,我们需要一种链接两个函数的方法,而这正是bind应该做的事情。此外,正如我们的示例所暗示的,应该尊重评估顺序,这导致我们使用 fun bind fun进行以下重写
这表明 bind 更像是一个函数,它是一个运算符。
然后对于所有函数 f 和 g,我们有f绑定g。
在haskell中,我们注意到这一点如下
f >>= g
此外,正如我们所知,函数接受 0、1 或更多参数并返回 0、1 或更多参数。
我们可以改进我们对绑定运算符的定义。
事实上,当 f 没有返回任何结果时,我们将>>=记为>>
Applying,这些对我们的伪代码的反思导致我们
main = print "what's your name" >> getLine >>= print
等一下,绑定运算符与用于组合两个函数的点运算符有何不同?
它有很大的不同,因为我们省略了一个重要信息,bind 不是链接两个函数,而是链接两个计算单元。这就是理解我们为什么要定义这个操作符的全部意义所在。
让我们把一个全局计算写成一个计算单元序列。
f0 >>= f1 >> f2 >> f3 ... >>= fn
在这个阶段,全局计算可以定义为一组具有两个运算符>>=、>>的计算单元。
我们如何表示计算机科学中的集合?
通常作为容器。
那么全局计算是一个包含一些计算单元的容器。在这个容器上,我们可以定义一些运算符,允许我们从一个计算单元移动到下一个计算单元,考虑或不考虑后者的结果,这是我们的>>=和>>运算符。
由于它是一个容器,我们需要一种向其中注入值的方法,这是由return函数完成的。它将一个对象注入到一个计算中,你可以通过签名来检查它。
return :: a -> m a -- m symbolize the container, then the global computation
由于这是一种计算,我们需要一种管理故障的方法,这由fail函数完成。
实际上计算的接口是由一个类定义的
class Computation
return -- inject an objet into a computation
>>= -- chain two computation
>> -- chain two computation, omitting the result of the first one
fail -- manage a computation failure
现在我们可以改进我们的代码如下
main :: IO ()
main = return "What's your name" >>= print >> getLine >>= print
在这里,我特意包含了 main 函数的签名,以表达我们在全局IO 计算中的事实以及使用 be ()生成的输出(作为练习,在 ghci 中输入$ :t print )。
如果我们更多地关注>>=的定义,我们可以出现以下语法
f >>= g <=> f >>= (\x -> g) and f >> g <=> f >>= (\_ -> g)
然后写
main :: IO ()
main =
return "what's your name" >>= \x ->
print x >>= \_ ->
getLine >>= \x ->
print x
正如您应该怀疑的那样,我们当然有一种特殊的语法来处理计算环境中的绑定运算符。没错这就是do 语法
的目的
那么我们之前的代码就变成了,用 do 语法
main :: IO ()
main = do
x <- return "what's your name"
_ <- print x
x <- getLine
print x
如果您想了解更多信息,请查看monad
正如leftaroundabout所说,我最初的结论有点过于热情
你应该感到震惊,因为我们违反了参照透明法则(x 在我们的指令序列中取两个不同的值),但这不再重要了,因为我们进入了一个计算,而后面定义的计算是一个容器我们可以派生一个接口,这个接口旨在管理与现实世界相对应的不纯世界。