Search code examples
haskelltypesfunctional-programmingio

How can I use the result of an IO action in an non-IO-function?


This function takes an filepath and returns the content of the file. this file includes a cuple of lines of the same length. -- It's used to be an primitive picture.

parsePicture :: FilePath -> IO()
parsePicture fileName = do
    content <- lines <$> readFile fileName
    print content

Now I've tried to implement a function to flip the "picture" vertically:

type Picture = [[Char]]

flipVertical :: IO() -> Picture
flipVertical xs = map reverse xs

But I only get the following error code:

zuP12.hs:24:31: error:
    • Couldn't match expected type ‘[[Char]]’ with actual type ‘IO ()’
    • In the second argument of ‘map’, namely ‘xs’
      In the expression: map reverse xs
      In an equation for ‘flipVertical’: flipVertical xs = map reverse xs
   |
24 | flipVertical xs = map reverse xs
   |                               ^^
Failed, no modules loaded.

How can I use my function flipVertical on the result of parsePicture?


Solution

  • This function... returns the content of the file.

    No it doesn't. It prints the content of the file, to STDOUT. For most purposes you should consider information you put to STDOUT to be gone – the information has left your program and is now on the terminal screen, or whereever else the user chooses to put it, but not accessible to the program anymore.

    (Strictly speaking, it is possible to redirect STDOUT back into your own program, but it's a big hack, don't do this.)

    Instead, you should change the function so it actually does return the content:

    parsePicture :: FilePath -> IO Picture
    parsePicture fileName = do
        content <- lines <$> readFile fileName
        return content
    

    ...or simply

    parsePicture fileName = lines <$> readFile fileName
    

    which behaves exactly the same (by the monad laws).

    Also, I would rather call this loadPicture: parsing shouldn't involve file reading.

    Of course, you can still print the contents later on, with something like

    main = do
       ...
       fooPicture <- parsePicture "foofilename"
       print fooPicture
       ...
    

    As for flipVertical, this shouldn't have anything to do with IO at all. It's simply a pure function

    flipVertical :: Picture -> Picture
    flipVertical xs = map reverse xs
    

    which can be used like, for example

    main = do
       ...
       fooPicture <- parsePicture "foofilename"
       print $ flipVertical fooPicture
       ...
    

    or

    main = do
       ...
       fooVFPicture <- flipVertical <$> parsePicture "foofilename"
       ...