简体   繁体   中英

Using the do statement in Haskell

Finally learning how to use monads in Haskell!

I want to read a file testInput , drop the first line, apply the function waffles to every other line, and save the result in a file output.txt .

I have written the following code:

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

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

Sadly the compiler complains:

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.

I find that error quite daunting. Can you help me debug it?

I also would appreciate code style advice!

EDIT: I forgot to split the lines of the file and convert them to integers. I tried solving that as follows:

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)

But that only introduced more confusing compiler errors.

The first line in your do statement fails because you are trying to use tail on an IO [String] . You need to fmap the tail function:

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

Now you need a way to get every other line from contents . You could define a simple everyOther function for this:

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

And now you can chain that into your fmap in the first line:

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

Your waffles function of (row - 1)*(col - 1) does not seem related to what I believe the type signature should be. Try starting with a type signature and building waffles from their. Based on your description, you are simply providing every other line to the function, so it should have signature:

waffles :: String -> String

Given that type signature for waffles , you can apply it via:

let result = fmap waffles contents

One more thing on the output: concat will smush all lines together. You probably want line breaks in there, so you might want to use unlines instead.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM