我仍在与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术语/函数?
答案 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(即使它们已经嵌套)。