了解I / O monad

时间:2014-06-18 19:42:31

标签: haskell functional-programming

我仍在与Haskell挣扎,现在我遇到了将输入/输出monad from this example包裹起来的问题:

main = do   
line <- getLine  
if null line  
    then return ()  
    else do  
        putStrLn $ reverseWords line  
        main  

reverseWords :: String -> String  
reverseWords = unwords . map reverse . words

我理解,因为像Haskell这样的函数语言不能基于函数的副作用,所以必须发明一些解决方案。在这种情况下,似乎所有内容都必须包含在do块中。我得到了简单的例子,但在这种情况下我真的需要一些解释。

为什么在I / O操作中使用一个do块是不够的?为什么你必须在if / else情况下打开全新的?此外,什么时候,我不知道如何调用它,do monad的“范围”结束,即你什么时候才能使用标准的Haskell术语/函数?

2 个答案:

答案 0 :(得分:8)

do块涉及与第一个语句相同的缩进级别上的任何。因此,在您的示例中,它实际上只是将两个事物连接在一起:

 line <- getLine

以及所有其他的,恰好相当大:

 if null line  
  then return ()
  else do
      putStrLn $ reverseWords line  
      main  

但无论多么复杂,do语法都不会将看作这些表达式。所以这一切与

完全相同
main :: IO ()
main = do
   line <- getLine
   recurseMain line

使用辅助函数

recurseMain :: String -> IO ()
recurseMain line
   | null line  = return ()
   | otherwise  = do
           putStrLn $ reverseWords line
           main

现在,显然 recurseMain中的内容无法知道函数是在main的do块内调用的,所以你需要使用另一个{{1} }}

答案 1 :(得分:8)

do实际上并没有做任何事情,它只是简单地结合语句的语法糖。一个可疑的比喻是将do[]进行比较:

如果您有多个表达式,可以使用:将它们组合到列表中:

(1 + 2) : (3 * 4) : (5 - 6) : ...

然而,这很烦人,所以我们可以使用[]符号来编译同样的东西:

[1+2, 3*4, 5-6, ...] 

同样,如果您有多个IO状态,则can combine them using >> and >>=

(putStrLn "What's your name?") >> getLine >>= (\name -> putStrLn $ "Hi " ++ name)

然而,这很烦人,所以我们可以使用do符号来编译同样的东西:

do
  putStrLn "What's your name?"
  name <- getLine
  putStrLn $ "Hi " ++ name

现在您需要多个do块的答案很简单:

如果您有多个值列表,则需要多个[] s(即使它们已嵌套)。

如果您有多个monadic语句序列,则需要多个do s(即使它们已经嵌套)。