Discussion:
Haskell IO Monad help/tutorials?
(too old to reply)
Jeff M.
2011-03-24 22:36:06 UTC
Permalink
So, I'm diving trying to learn me some Haskell (coming from Common
Lisp), and while I conceptually understand the problems that monads
solve and - to a lesser degree - how they work, I'm just bashing my
head up against the wall trying to work with them. I find myself doing
"genetic programming" (read: try this... nope... try this... nope)
until I get something that works, and I'm hoping someone here can give
me a little help?

I'll read a tutorial, and it will give an example of parsing a text
file with something like this:

input = lines $ readFile "foo.txt"

Except when I try that, it doesn't work; GHC complains that readFile
returns a IO String and lines expects a String. More research reveals
both the fmap and liftM functions. I'll be frank in that I have yet to
comprehend what exactly these do, but I can get the above to work like
so:

input = liftM lines $ readFile "foo.txt"

Can someone explain what exactly they do (and how)?

Of course, when I go to use input anywhere I end up with the same
problem again. Is there a "trick" to not having to use liftM or fmap
with every subsequent function call? Better still, is there a way I
can expect the IO monad in the rest of my functions so I don't have to
do that extra work?

How about a more concrete, simple example... I have a file of names,
one per line:

John Smith
Alex Smith
...

I'd like to generate a (Data.Map [Char] [[Char]]) where the k/v pairs
for the above would be ("Smith", ["John","Alex"]). Just to bang out a
quick function...

type NameMap = (Map [Char] [[Char]])

names :: NameMap -> [[Char]] -> NameMap
names tree [] = tree
names tree (x:xs) = names (insertWith (++) last [first] tree) xs
where [first,last] = words x

Now, if I test the above function... names empty ["John Smith", "Alex
Smith"]... everything works as expected. So, let's try doing this from
an input file:

main = names empty (lines $ readFile "names.txt")

Okay, on to type errors. So, I know I can "lift" lines...

main = names empty (liftM lines $ readFile "names.txt")

But this just defers to error onto names, and at this point I feel
pretty solidly stuck. I don't know how to rectify this problem without
implementing an intermediate function thusly:

hack text = names empty text
main = liftM hack (liftM lines $ readFile "names.txt")

Sorry if this is long-winded. I just wanted to clearly show where I'm
having problems. I'm quite sure that I'm just missing a little tidbit
of knowledge that will make this all come together nicely (I hope). If
someone can take a few minutes to explain how I should code this to
work properly with the IO Monad - and any other monad - or at least
point me to a really good tutorial that I haven't been able to find
yet, it'd be much appreciated!

Thanks!

Jeff M.
Jim Burton
2011-03-25 00:55:07 UTC
Permalink
"Jeff M." <***@gmail.com> writes:

[...]
Post by Jeff M.
Sorry if this is long-winded. I just wanted to clearly show where I'm
having problems. I'm quite sure that I'm just missing a little tidbit
of knowledge that will make this all come together nicely (I hope). If
someone can take a few minutes to explain how I should code this to
work properly with the IO Monad - and any other monad - or at least
point me to a really good tutorial that I haven't been able to find
yet, it'd be much appreciated!
Have a look at Real World Haskell
(http://book.realworldhaskell.org/read/), e.g. the chapter that
introduces IO.

HTH,

Jim
Post by Jeff M.
Thanks!
Jeff M.
--
J Burton
***@sdf-eu.org
Dirk Thierbach
2011-03-25 10:27:17 UTC
Permalink
Post by Jeff M.
So, I'm diving trying to learn me some Haskell (coming from Common
Lisp), and while I conceptually understand the problems that monads
solve and - to a lesser degree - how they work, I'm just bashing my
head up against the wall trying to work with them.
Very condensed version: You already understand the I/O Monad, because
you have already used the very same mechanism in imperative
programming all the time. Even if you're doing imperative programming
in Lisp. It's basically just an operator that is used to make
the implicit ordering of actions in imperative programming very
explicit.
Post by Jeff M.
I'll read a tutorial, and it will give an example of parsing a text
input = lines $ readFile "foo.txt"
That's just wrong. For some reason lots of people seem to be compelled
to write tutorials about monads, I guess you'll end up with bad
ones this way, too.
Post by Jeff M.
input = liftM lines $ readFile "foo.txt"
Can someone explain what exactly they do (and how)?
It's maybe easier to postpone that, and just look at the do-syntax.
When you write e.g. in C

void foo ()
{
puts ("first");
puts ("second");
puts ("third")
}

you can write in Haskell instead

foo = do {
putStrLn "first";
putStrLn "second";
putStrLn "third"
}

Actually, it's more elegant to use Haskell's indentation rules instead
of { ... ; ... ; ... }:

foo = do
putStrLn "first"
putStrLn "second"
putStrLn "third"

There's still a small gotcha: What do you do if an imperative I/O
action returns a value? There are no mutable variables in pure
functional programming, so Haskell allows one to bind the result value
to an immutable variable. That's very similar to let-expressions, it
just uses "<-" instead of "=":

foo = do
l <- getLine
putStr "You just input: "
putStrLn l
return (length l)

The last line says that "foo", seen as an I/O action, should "return"
the expression at the bottom when used similarly. So

bar = do
n <- foo
putStrLn (show n)

prints additionally the length of the line that was input.

Exercises:

1) Under what circumstances would "return ()" be useful?

2) Look up the definition of "print" in the library source code. What is
this function used for? Could you have guessed that if you had just looked
at the type signature of "print"? (use :t in the toplevel).

3) Given a type signature (a -> b) -> m a -> m b, write a function

baz :: (a -> b) -> m a -> m b
baz f m = do
...

with this signature.

4) Look up the definition of "liftM" in the library source code.
Notice anything? If you already know something about typeclasses,
look at the instance of "fmap" for Monads. Notice anything?

5) Figure out how to do loops inside the do-notation. In pure functional
programming, loops are often subsumed by higher-order functions (HOFs)
like "map" and "foldl/foldr" (also called "reduce" in other languages).
What are the appropriate HOFs for monads, and how do they work? Look up
the source code for "forM" and "forM_" in Control.Monad. What are these
functions good for, and why do you need extra names for them? Write
some example programs, if it helps you.

6) Find out (with some good tutorial, or the Haskell 98 report) how
the do-syntax is translated into (>>) and (>>=).

BTW, comp.lang.haskell exists. Crosspost and f'up.

HTH,

- Dirk

Loading...