1

我迷失在这个概念中。这是我的代码,它的作用只是询问你的名字。

askName = do
  name <- getLine
  return ()

main :: IO ()
main = do
       putStrLn "Greetings once again.  What is your name?"
       askName

但是,如何访问我main在 askName 中使用的变量名?

这是我的第二次尝试:

askUserName = do
  putStrLn "Hello, what's your name?"  
  name <- getLine  
  return name

sayHelloUser name = do
  putStrLn ("Hey " ++ name ++ ", you rock!")

main = do
  askUserName >>=  sayHelloUser

我现在可以以回调方式重新使用name,但是如果在 main 中我想再次调用 name,我该怎么做?name <- getLine (显然,避免放在主要内容中)

4

2 回答 2

6

我们可以想象你问名字是为了打印它,然后让我们重写它。
在伪代码中,我们有

main =   
    print "what is your name"  
    bind varname with userReponse    
    print varname  

然后,您的问题涉及第二条指令。
看看这个的语义。

  1. userReponse是一个返回用户输入的函数(getLine)
  2. varname是一个 var
  3. 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 在我们的指令序列中取两个不同的值),但这不再重要了,因为我们进入了一个计算,而后面定义的计算是一个容器我们可以派生一个接口,这个接口旨在管理与现实世界相对应的不纯世界。

于 2013-01-07T23:42:28.313 回答
3

从 askname 返回名称。在 Haskell 中,访问“全局”变量并不习惯:

askName :: IO String
askName = do
  name <- getLine
  return name

main :: IO ()
main = do
       putStrLn "Greetings once again.  What is your name?"
       name <- askName
       putStrLn name

现在唯一的问题是 askName 函数有点毫无意义,因为它现在只是getLine. 我们可以通过将问题放在 askName 中来“解决”这个问题:

askName :: IO String
askName = do
  putStrLn "Greetings once again.  What is your name?"
  name <- getLine
  return name

main :: IO ()
main = do
       name <- askName
       putStrLn name

最后,只有两点:在学习时放置类型声明通常是一个好主意,使事情变得明确并帮助编译器错误消息。另一件事是要记住“return”函数仅用于一元代码(它传统的 return 语句不同!)有时我们可以省略一些中间变量:

askName :: IO String
askName = do
  putStrLn "Greetings once again.  What is your name?"
  getLine
于 2013-01-07T18:47:04.627 回答