Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

No cooperation between readFile & IO monad when programming pointlessly

Why do countInFile1 & countInFile3 have compiler errors, when countInFile0 & countInFile2 do not. All four are the same thing.

count :: String -> String -> Int
count w = length . filter (==w) . words

present :: String -> String -> IO String
present w = return . show . count w

-- VALID: pointed readFile, regular present
countInFile0 :: String -> FilePath -> IO ()
countInFile0 w f = putStrLn =<< present w =<< readFile f

-- INVALID: pointless readFile, regular present
countInFile1 :: String -> FilePath -> IO ()
countInFile1 w = putStrLn =<< present w =<< readFile

-- VALID: pointed readFile, inline present
countInFile2 :: String -> FilePath -> IO ()
countInFile2 w f = putStrLn =<< (return . show . count w) =<< readFile f

-- INVALID: pointless readFile, inline present
countInFile3 :: String -> FilePath -> IO ()
countInFile3 w = putStrLn =<< (return . show . count w) =<< readFile

main = do
  countInFile0 "bulldogs" "bulldogs.txt"
  countInFile1 "bulldogs" "bulldogs.txt"
  countInFile2 "bulldogs" "bulldogs.txt"
  countInFile3 "bulldogs" "bulldogs.txt"

Also why does countInFile3 have this additional error that countInFile1 does not:

example_one.hs:21:27:
    No instance for (Monad ((->) FilePath))
      arising from a use of `=<<'
    Possible fix:
      add an instance declaration for (Monad ((->) FilePath))
    In the expression:
        putStrLn =<< (return . show . count w) =<< readFile
    In an equation for `countInFile3':
        countInFile3 w
          = putStrLn =<< (return . show . count w) =<< readFile
like image 502
Vanson Samuel Avatar asked Dec 04 '22 02:12

Vanson Samuel


1 Answers

With both countInFile1 and countInFile3, since you are composing three things of the form a -> IO b, you are thinking of the so-called Kleisli composition, the <=< from Control.Monad. Try

 countInFile1 w = putStrLn <=< present w <=< readFile
 countInFile3 w = putStrLn <=< return . show . count w <=< readFile

Or you can write countInFile3 w file = ... =<< readFile file, as you do elsewhere. readFile file (with the parameter) is an IO String, so it can be passed along by >>= or =<< to any String -> IO b. But that isn't as swank as what you intended. readFile just by itself is a FilePath -> IO String so it can be >=>'d with any String -> IO b to make a FilePath -> IO b and so on with a b -> IO c, etc. in your case ending with a FilePath -> IO ()

The second error comes from ghc trying to read =<< readFile, to do so it needs readFile to be m b for some monad m, so it settles on Monad ((->) FilePath) (this would actually make sense with Control.Monad.Instances, but would just delay getting the first error.)

If you add the file parameter to these it would be thus,

 countInFile1 w file = (putStrLn <=< present w <=< readFile) file 

and it is possible that you are parsing countInFile2 and countInFile0 this way, while construing =<< as <=< when actually they are like so:

 countInFile0 w file = putStrLn =<< present w =<< (readFile file)

The difference is the same as that between

 f n = (even . (+1) . (*3)) n

or equivalently

 f   = even . (+1) . (3*)

and on the other hand

 f n = even $ (+1) $ 3 * n  -- cp. your 0 and 2

If you delete the n from both sides here

 f   = even $ (+1) $ (3*)  -- cp. your 1 and 3

you will get a type error akin to the ones you saw:

 No instance for (Integral (a0 -> a0)) arising from a use of `even'

Where you use $ you need the parameter n -- as where you use >>= or =<< you need the parameter file. With ., as with <=<, you don't.

like image 150
applicative Avatar answered Jan 08 '23 11:01

applicative