在 Haskell 中使用 do 语句

Using the do statement in Haskell

终于学习如何在 Haskell 中使用 monad!

我想读取文件 testInput,删除第一行,每隔一行应用函数 waffles,并将结果保存在文件 output.txt.

我写了下面的代码:

main = do
    contents <- tail . fmap lines . readFile $ "testInput"
    result <- fmap waffles contents
    writeFile "output.txt" $ concat result

waffles row col = (row - 1)*(col - 1)

可悲的是编译器抱怨:

waffles.hs:3:41:
    Couldn't match type ‘IO String’ with ‘[String]’
    Expected type: FilePath -> [String]
      Actual type: FilePath -> IO String
    In the second argument of ‘(.)’, namely ‘readFile’
    In the second argument of ‘(.)’, namely ‘fmap lines . readFile’

waffles.hs:5:9:
    Couldn't match expected type ‘[b]’ with actual type ‘IO ()’
    Relevant bindings include program :: [b] (bound at waffles.hs:2:1)
    In a stmt of a 'do' block: writeFile "output.txt" $ concat result
    In the expression:
      do { contents <- tail . fmap lines . readFile $ "testInput";
           result <- fmap waffles contents;
           writeFile "output.txt" $ concat result }
    In an equation for ‘program’:
        program
          = do { contents <- tail . fmap lines . readFile $ "testInput";
                 result <- fmap waffles contents;
                 writeFile "output.txt" $ concat result }
Failed, modules loaded: none.

我发现那个错误非常令人生畏。你能帮我调试一下吗?

我也非常感谢代码风格建议!

编辑:我忘记拆分文件的行并将它们转换为整数。我尝试按如下方式解决该问题:

main = do
    contents <- tail . fmap lines . readFile $ "testInput"
    contents <- fmap read . words contents
    result <- fmap waffles contents
    writeFile "output.txt" $ concat result

waffles row col = (row - 1)*(col - 1)

但这只会引入更多令人困惑的编译器错误。

您的 do 语句中的第一行失败,因为您正试图在 IO [String] 上使用 tail。您需要 fmap tail 函数:

contents <- fmap tail . fmap lines . readFile $ "testInput"
-- or....
contents <- fmap (tail . lines) . readFile $ "testInput"

现在您需要一种从 contents 获取每隔一行的方法。您可以为此定义一个简单的 everyOther 函数:

everyOther :: [a] -> [a]
everyOther (x:_:xs) = x : everyOther xs
everyOther _        = []

现在您可以将其链接到第一行的 fmap 中:

contents <- fmap (everyOther . tail . lines) . readFile $ "testInput"

(row - 1)*(col - 1)waffles 函数似乎与我认为类型签名应该是什么无关。尝试从类型签名开始并从他们的构建 waffles。根据您的描述,您只是将每隔一行提供给该函数,因此它应该具有签名:

waffles :: String -> String

鉴于 waffles 的类型签名,您可以通过以下方式应用它:

let result = fmap waffles contents

输出中还有一件事:concat 会将所有行混在一起。您可能希望在那里换行,因此您可能希望改用 unlines

main = do
    contents <- fmap (everyOther . tail . lines) . readFile $ "testInput"
    let result = fmap waffles contents
    writeFile "output.txt" $ unlines result