首页 > 解决方案 > 奇怪的单子行为

问题描述

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

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 "!")

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

谢谢你的帮助!

标签: haskell

解决方案


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

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

你这样做>>是为了 ( -> handle) Monad(它是一个 reader monad - 看到有一个for constant的Monad实例)而不是!(->) ccIO

所以它确实将句柄传递给两者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_(from Control.Monad):

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

现在您应该确保关闭手柄 - 您可以使用bracketfrom Control.Exception

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

或(因为这很常见)只是withFileSystem.IO您打开/关闭:

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

最后,您实际上不必使用所有 可以使用 ( 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
    )

推荐阅读