Haskell的"do"关键字有什么作用?

Dov*_*iin 5 io syntax haskell program-entry-point keyword

我是一名C++/Java程序员,我正在努力学习Haskell(以及一般的函数式编程),而且我一直在努力学习它.我试过的一件事是这样的:

isEven :: Int -> Bool
isEven x =
    if mod x 2 == 0 then True
    else False

isOdd :: Int -> Bool
isOdd x =
    not (isEven x)

main =
    print (isEven 2)
    print (isOdd 2)
Run Code Online (Sandbox Code Playgroud)

但是在编译期间这个错误失败了:

ghc --make doubler.hs -o Main
[1 of 1] Compiling Main             ( doubler.hs, doubler.o )

doubler.hs:11:5: error:
    • Couldn't match expected type ‘(a0 -> IO ()) -> Bool -> t’
              with actual type ‘IO ()’
    • The function ‘print’ is applied to three arguments,
      but its type ‘Bool -> IO ()’ has only one
      In the expression: print (isEven 2) print (isOdd 2)
      In an equation for ‘main’: main = print (isEven 2) print (isOdd 2)
    • Relevant bindings include main :: t (bound at doubler.hs:10:1)
make: *** [all] Error 1
Run Code Online (Sandbox Code Playgroud)

所以,我在网上看到了一些带有"do"关键字的代码,所以我尝试了这样:

isEven :: Int -> Bool
isEven x =
    if mod x 2 == 0 then True
    else False

isOdd :: Int -> Bool
isOdd x =
    not (isEven x)

main = do
    print (isEven 2)
    print (isOdd 2)
Run Code Online (Sandbox Code Playgroud)

它的工作方式与我认为应该完全一样.

这里发生了什么?为什么第一个代码段不起作用?添加"做"实际上做了什么?

PS.我在互联网上看到了与"do"关键字相关的"monads",这与此有关吗?

sep*_*p2k 16

为什么第一个代码段不起作用?

在do街区之外,换行没有任何意义.所以你的第一个定义main是等价的main = print (isEven 2) print (isOdd 2),因为print只有一个参数才会失败.

现在你可能想知道为什么我们不能只使用换行符来表示应该一个接一个地调用一个函数.问题在于Haskell(通常)是懒惰的并且纯粹是功能性的,因此函数没有副作用,并且没有任何有意义的概念来调用一个接一个的函数.

那么print工作怎么样?print是一个接受字符串并产生类型结果的函数IO ().IO是一种表示可能产生副作用的操作的类型.main生成此类型的值,然后执行该值描述的操作.虽然没有一个接一个地调用一个函数的有意义的概念,但是有一个有意义的概念是在另一个函数之后执行一个IO值的操作.为此我们使用>>运算符,它将两个IO值链接在一起.

我在互联网上看到了与"do"关键字相关的"monads",这与此有关吗?

是的,Monad是一个类型类(如果您还不知道它们是什么:它们与OO语言中的接口类似),其中(以及其他)提供了函数>>和>>=.IO是该类型类的一个实例(在OO术语中:一种实现该接口的类型),它使用这些方法将多个操作链接在一起.

该do语法是使用更方便的方式>>与>>=这些功能.具体来说,您对main的定义等同于以下内容do:

main = (print (isEven 2)) >> (print (isOdd 2))
Run Code Online (Sandbox Code Playgroud)

(额外的括号不是必需的,但我添加了它们以避免对优先级产生任何混淆.)

因此main产生一个IO值,执行步骤print (isEven 2),然后执行print (isOdd 2).


jpa*_*ath 5

我认为暂时您只需要接受即可。是的,do-notation是monad类型类的语法糖。您的代码可以简化为以下内容:

main = print (isEven 2) >> print (isOdd 2)
Run Code Online (Sandbox Code Playgroud)

(>>)表示在此特定情况下需要执行此操作。但是,尝试在StackOverflow答案中解释Haskell IO和monad确实没有任何好处。相反,我建议您继续学习,直到您的书或用作学习资源的任何内容涵盖该主题为止。

但是,这是您可以在IO-中进行操作的快速示例do。不要太在意语法。

import System.IO
main = do
  putStr "What's your name? "  -- Print strings
  hFlush stdout                -- Flush output
  name <- getLine              -- Get input and save into variable name
  putStrLn ("Hello " ++ name)
  putStr "What's your age? "
  hFlush stdout
  age <- getLine
  putStr "In one year you will be "
  print (read age + 1)         -- convert from string to other things with read
                               -- use print to print things that are not strings
Run Code Online (Sandbox Code Playgroud)

  • 是的,您可以随意使用它。 (2认同)