Following a haskell tutorial, the author provides the following implementation of the withFile method:
withFile' :: FilePath -> IOMode -> (Handle -> IO a) -> IO a
withFile' path mode f = do
handle <- openFile path mode
result <- f handle
But why do we need to wrap the
result in a
return? Doesn't the supplied function
f already return an
IO as can be seen by it's type
Handle -> IO a?
f already returns an
IO, so if the function were written like this:
withFile' path mode f = do handle <- openFile path mode f handle
there would be no need for a return. The problem is
hClose handle comes in between, so we have to store the result first:
result <- f handle
<- gets rid of the
return puts it back.
This is one of the tricky little things that confused me when I first tried Haskell. You're misunderstanding the meaning of the
<- construct in do-notation.
result <- f handle doesn't mean "assign the value of
f handle to
result"; it means "bind
result to a value 'extracted' from the monadic value of
f handle" (where the 'extraction' happens in some way that's defined by the particular Monad instance that you're using, in this case the IO monad).
I.e., for some Monad typeclass m, the
<- statement takes an expression of type
m a in the right hand side and a variable of type
a on the left hand side, and binds the variable to a value. Thus in your particular example, with
result <- f handle, we have the types
f result :: IO a,
result :: a and
return result :: IO a.
PS do-notation has also a special form of
let (without the
in keyword in this case!) that works as a pure counterpart to
<-. So you could rewrite your example as:
withFile' :: FilePath -> IOMode -> (Handle -> IO a) -> IO a withFile' path mode f = do handle <- openFile path mode let result = f handle hClose handle result
In this case, because the
let is a straightforward assignment, the type of