1

I have these two code snippets, which I'd guess do the same thing, but they don't. Why is that?

This one works fine:

fdup :: String -> IO ()
fdup filename = do
        h <- openFile filename ReadMode
        c <- hGetContents h
        putStr $ unlines $ parse $ lines c
        hClose h

This one returns an error Couldn't match expected type ‘IO [String]’ with actual type ‘[String]’:

fdup' :: String -> IO ()
fdup' filename = do
        h <- openFile filename ReadMode
        c <- hGetContents h
        ls <- lines c
        putStr $ unlines $ parse $ ls
        hClose h

parse :: [String] -> [String]

What is the difference between them?

T.Poe
  • 1,949
  • 6
  • 28
  • 59
  • The error is probaby located in `ls <- lines c`? – Willem Van Onsem May 11 '19 at 17:33
  • Yes, but I don't get it why... – T.Poe May 11 '19 at 17:36
  • because if you write `x <- some_expr`, then `some_expr` needs to be of type `Monad m => m a`, and then `x` is of type `a`. Throughout a `do` block one uses the same monad `m`. `do` notation unfortunately may suggest that is just a bit of a different "style". But in fact here monads come in to play. In short the idea is that Haskell is a *pure* functional language, and that a call with the same parameter always gives the same result. Stateful actions, like opening a file, etc. thus require a state. A monad *can* be used to *implictly* pass that state. – Willem Van Onsem May 11 '19 at 17:38
  • Now it's clear to me, thank you for the explanation! – T.Poe May 11 '19 at 17:43
  • `ls <- return $ lines c` wold work. – Will Ness May 11 '19 at 18:59
  • 1
    @WillNess Why involve `<-` or `return` at all? Why not just `let ls = lines c`? – Joseph Sible-Reinstate Monica May 11 '19 at 19:53
  • @JosephSible for uniformity? also, I was illustrating to the OP the types mismatch in their code. – Will Ness May 12 '19 at 07:01

1 Answers1

4

As Willem Van Onsem explained, you don't need <- in that specific place because lines c is just a list of strings, and not an IO computation. If you want to give it a name, you can use a let-binding instead:

fdup' :: String -> IO ()
fdup' filename = do
        h <- openFile filename ReadMode
        c <- hGetContents h
        let ls = lines c
        putStr $ unlines $ parse $ ls
        hClose h
duplode
  • 33,731
  • 7
  • 79
  • 150