【问题标题】:Strange monadic behaviour奇怪的单子行为
【发布时间】:2021-06-12 17:35:54
【问题描述】:

我尝试编写一个程序,它将文件路径作为(命令行)参数并返回每个文件的第一行

main = getArgs >>= ( mapM_ ( \file -> ( openFile file ReadMode >>= ( (\handle -> hGetLine handle >>= print)  >> hClose )  ) ) ) 

我知道这看起来不太漂亮,但我只是 Haskell 的初学者。我也故意避免使用 do 表示法,因为我(还)对她感觉不太舒服。

因此,上面的代码编译并返回无效文件路径的错误,而对于有效路径则没有任何内容(即尤其不是文件的第一行)。

我必须承认我几乎不知道我做错了什么,但我做了以下观察:
如果我添加以下内容来检查哪些部分仍然被执行

main = getArgs >>= ( mapM_ ( \file -> ( openFile file ReadMode >>= ( (\handle -> hGetLine handle >>= print) >> (const $ putStr "Hello1")  >> hClose >> (const $ putStr "Hello2") )  ) ) ) 

程序只打印第二个“Hello”,这让我想起了 (>>) 的类型签名:

(>>) :: Monad m => m a -> m b -> m b

所以考虑到只有第二个参数类型的东西被返回,也许第一个参数被忽略了?
但是反对这个理论的第一个论点是这样的函数似乎不是很有用(至少在 IO Monad 的上下文中不是),第二个是程序

main = (putStr "Hello" >> putStr "World" >> putStr "!")

返回“HelloWorld!”正如预期的那样。因此,我一定是完全走错了路,这就是我来这里的原因。

感谢您的帮助!

【问题讨论】:

  • >> 只返回正确的结果,但它会执行 both effects - 你应该很好 - 你可以添加关于如何调用程序以及错误到底是什么的示例?也许文件确实不存在或找不到?
  • 顺便说一句:你真的应该试着把它翻译成do - 它应该是直截了当的(应该看起来像你在命令式语言中所期望的那样) - 在这里(对我来说)很难解析为这一切都与(,,) 和滚动条一致;)
  • @Carsten '关于如何调用程序以及错误到底是什么的示例':“./Main file.txt”返回“”,“./Main file_notExistent.txt”返回“Main :file_notExistent.txt:openFile:不存在(没有这样的文件或目录)”。所以问题不是包含在错误消息中,而是缺少任何消息。关于 do 符号:我的问题是我总是必须先将符号转换回显式符号才能理解它。首先是硬逻辑,然后是漂亮的符号,至少这是我学得最好的方式。
  • 正如我在下面写的那样,您的问题实际上是您没有将hClose 放在正确的位置-这样>> 用于(->) Monad 而不是IO-所以它确实将handle 传递给hGetLine handle >>= printhClose>> 忽略了第一个结果IO 结果,只返回了hClose 一个 - 所以这里的效果是通过handle 和结果只是关闭了文件——是的,它很微妙而且不明显,因为你很少看到/想到 reader-monad 实例(这里是 (->) handle 的 Monad 实例)
  • 我同意这对于新的(甚至是经验丰富的)Haskellers 来说可能真的很令人费解,为什么这不会在这里导致类型错误并且实际上在运行时会做一些事情......-> functor、monad 等的实例很好,但可能对初学者不太友好

标签: haskell


【解决方案1】:

我认为你的主要错误是你弄乱了句柄:

main = getArgs >>= (mapM_ (\file -> (openFile file ReadMode >>= (\handle -> (hGetLine handle >>= print)  >> hClose handle)  ) ) ) 

你这样做>> 是针对 (-> handle) Monad 的(它是一个阅读器 monad - 请参阅(->) cMonad 实例,用于常量c)而不是IO

所以它确实将句柄传递给hGetLine handle >>= printhClose,但>> 忽略了第一个结果 IO 操作 并返回了hClose 一个结果到>>

这里的效果正在传递句柄!

所以是的,最终唯一执行的 IO 效果是关闭文件!

因为你很少看到/想到这样的 reader-monad 实例,所以它很微妙而且不明显。


这是do符号

main = do
  args <- getArgs 
  mapM_ (\file -> do
    handle <- openFile file ReadMode
    line <- hGetLine handle
    print line
    hClose handle) args

我建议将 args 参数切换到 forM_(来自 Control.Monad):

main = do
  args <- getArgs 
  forM_ args (\file -> do
    handle <- openFile file ReadMode
    line <- hGetLine handle
    print line
    hClose handle)

现在您应该确保关闭手柄 - 您可以使用 bracket 中的 bracket 来完成此操作:

main = do
  args <- getArgs 
  forM_ args (\file -> do
    bracket
      (openFile file ReadMode)
      hClose
      (\h -> do
        line <- hGetLine h
        print line
      )
    )

或者(因为这很常见)只是来自System.IOwithFile 为您打开/关闭:

main = do
  args <- getArgs 
  forM_ args (\file -> do
    withFile file ReadMode
      (\h -> do
        line <- hGetLine h
        print line
      )
    )

最后,您实际上不必使用所有 handle 东西,您可以使用 (lazy) readFile 来代替,并且对于空文件也更安全一些:

main = do
  args <- getArgs 
  forM_ args (\file -> do
    content <- readFile file
    let ls = lines content
    case ls of
      [] -> putStrLn "no line in file"
      (firstLine:_) -> putStrLn firstLine
    )

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-08-20
    • 2021-07-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-14
    相关资源
    最近更新 更多