Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Deep confusion about monads

0 views
Skip to first unread message

Mark Carroll

unread,
Aug 16, 2001, 4:32:29 PM8/16/01
to haskel...@haskell.org
Let me know if I'm bugging you guys too much. It'd be great if I had
someone close at hand who knew more about Haskell than I do, but
unfortunately I don't. Are there any fora that are especially for people
to help novices, or is haskell-cafe the best fit?

I decided I ought to learn more about monads, so I've been trying to do
simple IO. However, I'm running into really basic problems that I haven't
managed to solve with the three monad tutorials I've read so far!

First, given an IO String, I don't seem to be able to take the head of it
to get an IO Char. I'm guessing that the IO monad is quite unlike the
simple 'tainting' I first imagined; I reall do seem to have to treat it as
an abstract polymorphic type. Worse, I can't work out how to write a IO
String -> IO Char version of head; I can't make my (x:xs) match an IO
String! Better would be if I could somehow write an IO String -> IO Char
wrapper for head.

I'm also confused as to why I can write:

readFile "/tmp/foo" >>= putStr

to print out the contents of a file but, in ghci,

let x = readFile "/tmp/foo"
putStr x

...doesn't work. Then again, I'm also confused by the way that Simon
Thompson's book says that,

(>>=) :: IO a -> (a -> IO a) -> IO a

which I think might be a misprint for,

(>>=) :: IO a -> (a -> IO b) -> IO b

...?

I'm not sure if I've explained enough of my confusion that a succinct,
helpful response not mentioned in what I've read so far can be easily
given. I guess that my problem is that I had initially imagined that the
prefix 'IO' appears as a magical tainting of any data that could depend on
the result of some IO action. However, I'm coming to the conclusion that
I'm quite wrong in this. I'd like to be able to manipulate IO Strings as
if they were just strings that I can't forget came from IO, but I'm
guessing now that things aren't that simple - they really are quite
different to strings, yet I want to avoid resorting to unsafePerformIO.
The really annoying thing is that all the monad examples I've read so far
have made perfect sense to me. (-: What I'm really hoping is that a simple
answer as to how best to arrange my IO String -> IO Char (or IO [a] -> IO
a) version of head will somehow resolve a lot of my confusion. For
instance, maybe I have to somehow write an 'action' version of head that I
apply with the help of >>= ?

-- Mark


_______________________________________________
Haskell-Cafe mailing list
Haskel...@haskell.org
http://www.haskell.org/mailman/listinfo/haskell-cafe

Mark Carroll

unread,
Aug 16, 2001, 4:38:26 PM8/16/01
to haskel...@haskell.org
I should add that I do seem to have written something that sort of works:

firstChar :: [a] -> IO a
firstChar xs = return head xs

then,

readFile "/tmp/foo" >>= firstChar >>= putChar

...however, I can believe that

Mark Carroll

unread,
Aug 16, 2001, 4:40:41 PM8/16/01
to haskel...@haskell.org
That's embarrassing - I hit 'send' before I finished. Sorry. ):

On Thu, 16 Aug 2001, Mark Carroll wrote:

> I should add that I do seem to have written something that sort of works:
>
> firstChar :: [a] -> IO a
> firstChar xs = return head xs

I mean firstChar xs = return $ head xs

> then,
>
> readFile "/tmp/foo" >>= firstChar >>= putChar

(snip)

...what's surprising me is that do I really have to turn everything into
an IO action like this just to do things with the String hidden in the IO
String?

-- Mark

Ashley Yakeley

unread,
Aug 16, 2001, 7:32:22 PM8/16/01
to Mark Carroll, Haskell Cafe List
At 2001-08-16 13:30, Mark Carroll wrote:

>in ghci,
>
>let x = readFile "/tmp/foo"

x here has type "IO String"

>putStr x

putStr wants x to be type "String". You want this:

do
{
x <- readFile "/tmp/foo";
putStr x;
}

...which is translated to...

readFile "/tmp/foo" >>= (\x -> putStr x)

>(>>=) :: IO a -> (a -> IO b) -> IO b

This one is correct.

--
Ashley Yakeley, Seattle WA

Ashley Yakeley

unread,
Aug 16, 2001, 7:45:38 PM8/16/01
to Mark Carroll, Haskell Cafe List
At 2001-08-16 16:30, I wrote:

>>(>>=) :: IO a -> (a -> IO b) -> IO b
>
>This one is correct.

Whoops! No it's not. It should be:

(>>=) :: (Monad m) => m a -> (a -> m b) -> m b

The other two are however special cases of this type.

Claus Reinke

unread,
Aug 16, 2001, 7:49:47 PM8/16/01
to Mark Carroll, haskel...@haskell.org
>Let me know if I'm bugging you guys too much. It'd be great if I had
>someone close at hand who knew more about Haskell than I do, but
>unfortunately I don't. Are there any fora that are especially for people
>to help novices, or is haskell-cafe the best fit?

It's quite okay to ask such questions here, but you should not assume
to be the first to run into these problems:-) Have a search through
www.haskell.org, especially the bookshelf, the mailing-list archives
for haskell and haskell-cafe, and the wiki: questions & answers (which
is similar to a list of frequently asked questions, without the ordering
implied by a list..). This particular question has also been raised several
times on comp.lang.functional, so you can find a lot of related discussion
in Google's UseNet search.

You won't find all the answers you need there, and even if your specific
questions have been answered there, the answers may not be helpful
to you. Then, ask here, and say what resources you've tried and why they
didn't help you. That way, the resources at www.haskell.org can be
improved every time the questions are asked on this list.

> ...what's surprising me is that do I really have to turn everything into
> an IO action like this just to do things with the String hidden in the IO
> String?

Part of the answer can already be found in the wiki at www.haskell.org,
but as you say you've tried some monad tutorials, here goes another
longish explanation attempt:

There is no String hidden in an IO String (at least, there need not be one).

If you have a function f :: String -> String, there need not be a String hidden
in f -- a call to f could just give you back something constructed from its
parameter. So f promises a String when passed a String, and the only way
to get at that result String is by applying f to a String.

If you have i :: IO String, the situation is similar:
- f is a function with an explicit parameter and calling this function returns a
String
- i is an IO-action, with implicit access to an IO-environment, and executing it
may do things to the IO-environment, and will produce a String

In both cases, you've got a promise of a String, but not necessarily a String.

The difference is that f only has access to its definition and its parameter,
and
it only returns a String, so you can use it in any context that supplies a
String
parameter and expects a String result. In contrast, i also wants access to an
IO-environment, and it returns a String and may modify the IO-environment,
so you can use it in any context that supplies access to an IO-environment
and expects a String and (potential) changes to that IO-environment.

With this background, your question is easily answered: an IO String action
only promises a String, and to get that String you have to execute the action
in an IO-environment. You can't do that inside an expression that isn't of
type IO something, because expressions that are not of that type shouldn't
have access to an IO-environment (they may pass IO actions around, but
they can't execute them).

So, you don't need to convert everything to an IO action to do something
with the String, but you need to be able to execute the IO action that promises
the String. And you can't embed that IO action in a non-IO expression, so
your overall programm will be an IO action:-( However, you can embed
functional expressions in an IO action:-) And that's just a complicated way
to describe what you've already discovered:

f :: String -> Char
{- no IO here, and f could be an arbitrarily complex functional computation -}
f s = head s

i :: IO String
{- if given access to an IO-environment, this should produce a String -}
i = readFile "input"

(i >>= (\s-> return (f s))) >>= putChar

{- or: do { s <- i; c<- return (f s); putChar c }
or: do { s <- i; putChar (f s) }
-}

The "return" embeds an arbitrary expression into an IO-action that does not
access its IO-environment (as far as that environment is concerned, it is a
null action). And the "s<- i; return (f s)" part binds s to the String returned
by i *and* it composes the effects that i and "return (f s)" might have on the
IO-environment. That's why you can't simply use a let-binding instead of
the monadic binding: let doesn't know about that extra IO-environment.

Or, in monad-speak: function application and let-binding take place in
the identity monad (the monad which doesn't add anything extra). IO actions
and their bindings take place in the IO monad (the monad that adds access
to IO-environments to functional computations). In contrast to other monads,
such as List, MayBe, .., you won't find an operation of type M a -> a if
M is the IO monad (guess what, you will, but it's unsafe;-).

The reason is that other agents observe the IO-environment, so changes
to it won't go unnoticed (you can throw away the evidence that you really
had a list of results instead of just a single result, but you can't throw away
the evidence that you've reached outside your functional program..). This
brings us back to your idea of "tainting": not the Strings themselves are
tainted (they are as pure as anything else), the computation that produces
the String is tainted if it needs IO to produce a String. For "untainted",
purely functional String computations, there is no difference (apart from
resource usage) between the computations and the Strings they produce,
but for "IO-tainted" computations, there is such a difference.

Anyone still reading?-) If yes, and if it should have been helpful, perhaps
someone could condense this and add it to the wiki?

Claus

PS. Once Upon A Long Ago, I tried to put some of the various
functional IO schemes into a logical development (a kind of
"design proof" or "design derivation").. a bit dated, and not
necessarily helpful to those currently struggling with IO in
Haskell, but perhaps of historical interest?-) Those who like
that kind of thing can find it in chapter 3 of someone's thesis:
http://www.cs.ukc.ac.uk/people/staff/cr3/publications/phd.html

Adrian Hey

unread,
Aug 16, 2001, 10:40:19 PM8/16/01
to Mark Carroll, haskel...@haskell.org
Hello again,

On Thursday 16 August 2001 9:39 pm, Mark Carroll wrote:
> That's embarrassing - I hit 'send' before I finished. Sorry. ):
>
> On Thu, 16 Aug 2001, Mark Carroll wrote:
> > I should add that I do seem to have written something that sort of works:
> >
> > firstChar :: [a] -> IO a
> > firstChar xs = return head xs
>
> I mean firstChar xs = return $ head xs
>
> > then,
> >
> > readFile "/tmp/foo" >>= firstChar >>= putChar
>
> (snip)
>
> ...what's surprising me is that do I really have to turn everything into
> an IO action like this just to do things with the String hidden in the IO
> String?
>

No, you could write instead..
(readFile "/tmp/foo") >>= (\foofile -> putChar (head foofile))
or..
(readFile "/tmp/foo") >>= (putChar . head)

HTH

Regards
--
Adrian Hey

Adrian Hey

unread,
Aug 16, 2001, 10:57:57 PM8/16/01
to Mark Carroll, haskel...@haskell.org
Hello,

On Thursday 16 August 2001 9:30 pm, Mark Carroll wrote:
> Let me know if I'm bugging you guys too much. It'd be great if I had
> someone close at hand who knew more about Haskell than I do, but
> unfortunately I don't. Are there any fora that are especially for people
> to help novices, or is haskell-cafe the best fit?
>
> I decided I ought to learn more about monads, so I've been trying to do
> simple IO. However, I'm running into really basic problems that I haven't
> managed to solve with the three monad tutorials I've read so far!
>
> First, given an IO String, I don't seem to be able to take the head of it
> to get an IO Char. I'm guessing that the IO monad is quite unlike the
> simple 'tainting' I first imagined; I reall do seem to have to treat it as
> an abstract polymorphic type. Worse, I can't work out how to write a IO
> String -> IO Char version of head; I can't make my (x:xs) match an IO
> String! Better would be if I could somehow write an IO String -> IO Char
> wrapper for head.
>
> I'm also confused as to why I can write:
>
> readFile "/tmp/foo" >>= putStr
>
> to print out the contents of a file but, in ghci,
>
> let x = readFile "/tmp/foo"
> putStr x
>
> ...doesn't work.

Yep, won't work. As you suspected, your ideas about what IO means are
wrong. IO isn't a simple annotation to indicate an 'impure function',
and you can't use a function of type a -> IO b in the same way as you
would use a function of type a -> b.
(IO b) is an abstract data type. A *value* of type (IO b) stands for
an *action* which, when *executed* 'returns' a value of type b.
You pass the returned value to subsequent actions using the >>=
operator.

In your example..
readFile :: String -> IO String
I.E. readFile is a *function* (not an action)
so..
in x = readFile "/tmp/foo", x is an action of type (IO String).
In order actually read the file you still have to execute the action x.
Something like this should work (prints file backwards)

main :: IO ()
main = let x = readFile "/tmp/foo" -- x is an action which will read the file
in do foofile <- x -- foofile is file contents
putStr (reverse foofile)

Take a look at the Haskell report to see how 'do expressions' get
translated into monadic operators and lambda abstractions. The
above is equivalent to..
main = let x = readFile "/tmp/foo"
in x >>= (\foofile -> putStr (reverse foofile))

> Then again, I'm also confused by the way that Simon
> Thompson's book says that,
>
> (>>=) :: IO a -> (a -> IO a) -> IO a
>
> which I think might be a misprint for,
>
> (>>=) :: IO a -> (a -> IO b) -> IO b
>
> ...?

Yes, I think that's a typo. The second form is correct, though if you
look in the prelude you'll see it's definition applies to all monad
instances, not just the IO monad.

Regards
--
Adrian Hey

Joe English

unread,
Aug 16, 2001, 11:14:38 PM8/16/01
to haskel...@haskell.org

Mark Carroll wrote:

> First, given an IO String, I don't seem to be able to take the head of it
> to get an IO Char.


The bit of plumbing you're looking for is:

fmap :: Functor f => (a -> b) -> (f a -> f c)

Every Monad worthy of the name is also a Functor,
so fmap works for IO too. Here's how:

You've got
foo :: IO [Char]
and
head :: [a] -> a
so
fmap head :: (Functor f) => f [a] -> f A

can be applied to foo to give

fmap head foo :: IO Char


(You can't of course take the head of an IO String; that
wouldn't make sense.)


You could also start with:

bar :: IO Char
bar = do
theString <- foo
return (head theString)

and simplify:

bar
= { definition }
do theString <- foo ; return (head theString)
= { translate 'do' syntax }
foo >>= \ theString -> return (head theString)
= { f (g x) == (f . g) x }
foo >>= \ theString -> (return . head) theString
= { eta-reduce; (\x -> f x) ==> f }
foo >>= return . head
= { Monad law #4 in Haskell report (section 6.3.6 "Class Monad") }
fmap head foo


> I'm also confused as to why I can write:
> readFile "/tmp/foo" >>= putStr
> to print out the contents of a file but, in ghci,
> let x = readFile "/tmp/foo"
> putStr x
> ...doesn't work.

That's because you have

x :: IO String
and
putStr :: String -> IO ()

The types don't match.


> Then again, I'm also confused by the way that Simon
> Thompson's book says that,
>
> (>>=) :: IO a -> (a -> IO a) -> IO a
>
> which I think might be a misprint for,
>
> (>>=) :: IO a -> (a -> IO b) -> IO b

Quite right. Actually the type is a even more general --
it's (>>=) :: (Monad m) => m a -> (a -> m b) -> m b
It works for any Monad, not just I/O.


> I guess that my problem is that I had initially imagined that the
> prefix 'IO' appears as a magical tainting of any data that could depend on
> the result of some IO action. However, I'm coming to the conclusion that
> I'm quite wrong in this.

That's actually a fairly useful way of looking at the IO monad
in my opinion.

> I'd like to be able to manipulate IO Strings as
> if they were just strings that I can't forget came from IO, but I'm
> guessing now that things aren't that simple - they really are quite
> different to strings

The trick is to just write functions that operate on Strings,
and use 'fmap' (and other combinators) to turn them into
functions that work on IO Strings.


--Joe English

jeng...@flightlab.com

Mark Carroll

unread,
Aug 17, 2001, 4:57:31 PM8/17/01
to haskel...@haskell.org
Gosh, thank you everybody! Let me try to summarise where I am, given all
the help.

haskell-cafe is an okay place to ask this stuff. (-: I should also check
out the wiki too; it looked a bit scary to use last time I looked, but it
seems to have relevant stuff. It's a pity that the mailing list archives
aren't more easily searched; maybe applying google to them would work.

Part of my confusion was due to a usual problem with teaching. People come
in to a lesson with preconceived ideas X, the teacher tells them stuff Y,
and instead of coming out of the lesson thinking Y, they come out thinking
f(X,Y), because the teacher didn't tell them not-X. It's instructional to
ask children where they think trees' mass comes from as they grow, after a
lesson on the subject. I originally thought that "IO a" meant "an a whose
value may depend on some IO that was done", as if that data were tainted
by other data it touched which stemmed from IO actions. Instead, "IO a" it
appears to mean "some IO action you can do that will give an a", and you
can only do stuff with the "a" within an "IO-aware" context. A Haskell
program is an IO action.

I got the idea of tainted data from Perl, which is a security aid to
help you ensure that any data drawn from outside your program has to
be approved by you before your program can use it to affect other
things outside your program.

Try to keep most code functional, even if this means that your monadic
code uses very complex referentially-transparent helper functions.

I hadn't bumped into Theo Norvell's monad tutorial yet. I'm guessing that
it's the one at http://www.engr.mun.ca/~theo/Misc/haskell_and_monads.htm ?
I'll take a look.

There is a concept of being in the IO monad. My current concept of "A >>=
B" is that it helps let B into the monad that A's in. This is how 'head'
is let in by

readFile "/etc/hosts" >>= (return . head)

I find fmap deeply strange, though, because I don't really understand
functors. People pointed me towards fmap, and I figured from IO's instance
of Functor that

fmap head $ readFile "/etc/hosts"

also works, so I plan to learn a little category theory to help find out
why this makes sense. (Someone also taught me how to use $. (-:)

>>= actually has the type (Monad m) => m a -> (a -> m b) -> m b
That makes some sense.

I get >> and the syntactic sugar of 'do' looks fairly simple so I'll
probably start using that more once I'm happy with the translation
between that and >>=

All the best,
Mark

0 new messages