【问题标题】:'do' construct in HaskellHaskell 中的“do”构造
【发布时间】:2011-05-29 15:25:27
【问题描述】:

我正在尝试学习 Haskell,并想编写一个将文件内容打印到屏幕上的小程序。当我将它加载到 GHCi 中时,出现以下错误:

“do”结构中的最后一条语句必须是表达式

我知道这里已经有人问过这个问题:Haskell — “The last statement in a 'do' construct must be an expression”

即使我的代码非常相似,我仍然无法找出问题所在。如果有人能向我指出问题,我将非常感激。

module Main (main) where

import System.IO
import System(getArgs)

main :: IO()
main = do
    args <- getArgs
    inh <- openFile $ ReadMode head args
    printFile inh
    hClose inh

printFile :: Handle -> IO ()
printFile handle = do
    end <- hIsEOF handle
        if end
            then return ()
            else do line <- hGetLine handle
                putStrLn line
                printFile handle

【问题讨论】:

  • 那么,您检查过您的缩进是否损坏(即您有制表符和空格)吗?
  • @delnan:我做到了。我什至把它从制表符改成了空格。
  • @delnan:第 15:4 行:'end
  • 使用明确的{ ; } 永远不必担心这种空白的愚蠢。

标签: haskell syntax io monads do-notation


【解决方案1】:

您始终可以在 { ; } 中使用显式括号,而不必担心这种愚蠢的空白。

printFile :: Handle -> IO ()
printFile handle = do {
    end <- hIsEOF handle ;
        if end
            then return ()
            else do { line <- hGetLine handle ;
                putStrLn line ;
                printFile handle }}

会完全没问题(如,不会导致错误)。

I/O 在 Haskell 中通过特殊的“do”语言处理。它应该被接受。它实际上是通过 monads 实现的,这是一个实现细节。

澄清一下:我不认为大括号更好,我认为它们应该与一个漂亮且一致的缩进一起使用。大括号为我们提供了关于代码结构的良好且直接的视觉线索。大多数时候,狂野的缩进当然是一种毫无意义的干扰。而且,大括号为我们的工作代码提供了保证,并让我们摆脱了空白事故的无意义担心。他们消除了这种脆弱性。

【讨论】:

  • 认真的吗?即使你认为大括号比缩进更好——这在 Haskell 社区中是少数人的意见。所以说“总是使用包围”几乎是诱饵。你可以这样说,“如果你对缩进不完全自信并遇到奇怪的错误,那么尝试显式括号是个好主意,事实上我更喜欢总是用括号/分号我的 do 构造”。
  • @leftaroundabout 感谢您的反馈,我在答案中阐述了这一点。
【解决方案2】:

这是你写的:

main :: IO()
main = do
    args <- getArgs
    inh <- openFile $ ReadMode head args
    printFile inh
    hClose inh

但这样可能更好:

main :: IO()
main = do
    args <- getArgs
    withFile (head args) ReadMode printFile

【讨论】:

  • 即使printFile 也可以缩短为printFile handle = do end &lt;- hIsEof handle; unless end $ hGetLine handle &gt;&gt;= putStrLn &gt;&gt; printFile handle
【解决方案3】:

有几个问题。首先,if 缩进太远——end &lt;- ... 被假定为do 的最后一行。取消缩进...

下一个问题出现了。相同的错误消息,仅在第 18 行。这一次,第 19 行和第 20 行的缩进不够深(它们没有被解析为do 的一部分)。缩进(无论如何看起来更好,因为它现在都排好了)...下一个错误消息。好消息是,这一次不是缩进错误,修复也很简单。

test.hs:9:22:
    Couldn't match expected type `([a] -> a) -> [String] -> FilePath'
           against inferred type `IOMode'
    In the second argument of `($)', namely `ReadMode head args'
    In a stmt of a 'do' expression:
        inh <- openFile $ ReadMode head args
    In the expression:
        do { args <- getArgs;
             inh <- openFile $ ReadMode head args;
             printFile inh;
             hClose inh }

修复是inh &lt;- openFile (head args) ReadMode。如果您想更详细地说明您的版本不正确的原因/方式,或者错误的含义,请告诉我,我会进行编辑。

【讨论】:

  • 我在浏览时错过了openFile 位。好像您实际上已将其插入编译器:)
  • 感谢您的帮助。我想我需要阅读那个美元符号。
  • @Anonymous:这没什么花哨的,事实上它很无聊:) f $ x = f x。它之所以有用是因为它的优先级很低,所以它可以在某些情况下替换括号(即return $ Foo 1 "bar" 而不是return (Foo 1 "bar"))。
【解决方案4】:

你的缩进被破坏了。这些更好:

printFile :: Handle -> IO ()
printFile handle = do
    end <- hIsEOF handle
    if end
        then return ()
        else do line <- hGetLine handle
                putStrLn line
                printFile handle

printFile :: Handle -> IO ()
printFile handle = do
    end <- hIsEOF handle
    if end
        then return ()
        else do
            line <- hGetLine handle
            putStrLn line
            printFile handle

通过使ifend &lt;- hIsEof handle 进一步缩进,它实际上是一个行继续,而不是do 中的后续操作。同样,putStrLn line 的缩进少于line &lt;- hGetLine handle 的事实意味着do(在else 内)到此结束。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-04
    • 1970-01-01
    相关资源
    最近更新 更多