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

Do blocks, monads, and sequences

1 view
Skip to first unread message

Mike Austin

unread,
Nov 22, 2009, 3:40:33 PM11/22/09
to
Hi all, I'm trying to understand monads a little more, so I tried
deconstructing a do block into continuation monads into use of no monads. I'm
having trouble getting the last ones to execute non-lazily, any pointers?

Thanks much!
Mike


import System.IO.Unsafe

test1 = do
print "What's your name?"
name <- getLine
print ("Hello, " ++ name ++ "!")

test2 = do {
print "What's your name?" >>
getLine >>= \name ->
print ("Hello, " ++ name ++ "!")
}

test3 = do {
(\name -> print ("Hello, " ++ name ++ "!"))
(head (sequence [unsafePerformIO getLine]))
}

--test4 = do {
-- map (sequence [\name -> print ("Hello, " ++ name ++ "!")]) ["joe"]
--}

main = do
test1
test2
test3

Paul Rubin

unread,
Nov 22, 2009, 4:28:08 PM11/22/09
to
Mike Austin <mi...@mike-nospam-austin.com> writes:
> Hi all, I'm trying to understand monads a little more, so I tried
> deconstructing a do block into continuation monads into use of no
> monads. I'm having trouble getting the last ones to execute
> non-lazily, any pointers?

They are always executed lazily. An I/O action is just a value that
is returned to the runtime system by "main", which then lazily evaluates
any pure values in the action.

Dirk Thierbach

unread,
Nov 23, 2009, 3:24:55 AM11/23/09
to
Mike Austin <mi...@mike-nospam-austin.com> wrote:
> Hi all, I'm trying to understand monads a little more, so I tried
> deconstructing a do block into continuation monads into use of no monads.

That will probably not be possible. I don't think the IO-Monad steps
can be expressed as pure continuations (and, at least for me, a
"continuation monad" is something else).

> I'm having trouble getting the last ones to execute non-lazily, any
> pointers?

Purely functional code in Haskell is always lazy (unless your force
evaluation with seq etc.).

Before monads where invented, one problem with I/O in Haskell as a
silent side-effect (as in other languages, like Lisp and OCaml) was
that due to lazyness, one couldn't enforce the order of execution of
I/O actions (or be sure that they were executed at all). The I/O-Monad
solved this problem, by conceptually adding an extra top-level
"I/O-layer", with the "bind" operation ensuring proper sequencing.

I may be wrong, but it looks like you're trying to translate that back
to the old way that didn't work. Which doesn't make a lot of sense to me :-)

> import System.IO.Unsafe
>
> test1 = do
> print "What's your name?"
> name <- getLine
> print ("Hello, " ++ name ++ "!")
>
> test2 = do {
> print "What's your name?" >>
> getLine >>= \name ->
> print ("Hello, " ++ name ++ "!")
> }

BTW, you can drop the do-block once you expanded everything into (>>)
and (>>=).

- Dirk

Dan Doel

unread,
Nov 23, 2009, 12:47:59 PM11/23/09
to
Dirk Thierbach wrote:

> Before monads where invented, one problem with I/O in Haskell as a
> silent side-effect (as in other languages, like Lisp and OCaml) was
> that due to lazyness, one couldn't enforce the order of execution of
> I/O actions (or be sure that they were executed at all). The I/O-Monad
> solved this problem, by conceptually adding an extra top-level
> "I/O-layer", with the "bind" operation ensuring proper sequencing.

There were other I/O systems in Haskell prior to the IO monad that worked
with relative success. For instance, one gave main the type:

main :: [Response] -> [Request]

Then, one would produce a list of I/O requests, and lazily consume the list
of responses within that list (which was in turn lazily produced in response
to the runtime consuming the requests). For instance:

foo resp = GetLine : case resp of
Line l : resp' -> PutStrLn l : case resp' of
Done () : resp'' -> ...

Another solution (which could probably be inter-defined with the
request/response model) used continuation passing, so one could write
programs like:

foo = getLine (\l -> putStrLn l (\() -> ...))

I seem to recall the continuation-passing combinators being defined over-top
the request/response model (from what I've read), but I'm not certain. The
punchline is that both of these (among others) can be used as
implementations of the current IO type, and operations on monads are a
convenient set of ways of building up such programs. For instance, the
second foo above should look suspiciously close to

foo = getLine >>= \l -> putStrLn l >>= \() -> ...

So it's not so much that I/O side effects were just flying around in Haskell
prior to the introduction to monads, but that monads put the existing
solutions into a nice framework.

-- Dan

Dirk Thierbach

unread,
Nov 23, 2009, 1:44:17 PM11/23/09
to
Dan Doel <dan....@gmail.com> wrote:
> Dirk Thierbach wrote:

>> Before monads where invented, one problem with I/O in Haskell as a
>> silent side-effect (as in other languages, like Lisp and OCaml) was
>> that due to lazyness, one couldn't enforce the order of execution of
>> I/O actions (or be sure that they were executed at all). The I/O-Monad
>> solved this problem, by conceptually adding an extra top-level
>> "I/O-layer", with the "bind" operation ensuring proper sequencing.

> So it's not so much that I/O side effects were just flying around in


> Haskell prior to the introduction to monads,

I didn't want to create the impression that this was case (because as you
say, it wasn't). The above was meant to be hypothetical ("if Haskell
had I/O as a silent side effect, as in other languages..."). Bad wording on
my part, probably.

- Dirk

Mike Austin

unread,
Nov 24, 2009, 12:42:42 AM11/24/09
to
Dirk Thierbach wrote:
> Mike Austin <mi...@mike-nospam-austin.com> wrote:
>> Hi all, I'm trying to understand monads a little more, so I tried
>> deconstructing a do block into continuation monads into use of no monads.
>
> That will probably not be possible. I don't think the IO-Monad steps
> can be expressed as pure continuations (and, at least for me, a
> "continuation monad" is something else).
>
>> I'm having trouble getting the last ones to execute non-lazily, any
>> pointers?
>
> Purely functional code in Haskell is always lazy (unless your force
> evaluation with seq etc.).

I wanted to express the same thing as the two functions above by using
sequence. But my efforts were not quite successful.

> Before monads where invented, one problem with I/O in Haskell as a
> silent side-effect (as in other languages, like Lisp and OCaml) was
> that due to lazyness, one couldn't enforce the order of execution of
> I/O actions (or be sure that they were executed at all). The I/O-Monad
> solved this problem, by conceptually adding an extra top-level
> "I/O-layer", with the "bind" operation ensuring proper sequencing.
>
> I may be wrong, but it looks like you're trying to translate that back
> to the old way that didn't work. Which doesn't make a lot of sense to me :-)

I'm just trying to get an understanding by decomposing from higher level to
lower level, not that I would want to do that in real code.

>> import System.IO.Unsafe
>>
>> test1 = do
>> print "What's your name?"
>> name <- getLine
>> print ("Hello, " ++ name ++ "!")
>>
>> test2 = do {
>> print "What's your name?" >>
>> getLine >>= \name ->
>> print ("Hello, " ++ name ++ "!")
>> }
>
> BTW, you can drop the do-block once you expanded everything into (>>)
> and (>>=).

Noted, thanks! So how would I use 'sequence' to force early binding?

import System.IO.Unsafe

test1 = do
print "What's your name?"
name <- getLine
print ("Hello, " ++ name ++ "!")

test2 = print "What's your name?" >>


getLine >>= \name ->
print ("Hello, " ++ name ++ "!")

test3 = do {
(\_ -> (\name -> print ("Hello, " ++ name ++ "!"))
(unsafePerformIO getLine)) (print "What's your name?")
}

--test4 = do {
-- map (sequence [\name -> print ("Hello, " ++ name ++ "!")]) ["joe"]
--}

main = do
test1
test2
test3


> - Dirk

Dirk Thierbach

unread,
Nov 24, 2009, 3:32:35 AM11/24/09
to
Mike Austin <mi...@mike-nospam-austin.com> wrote:

>>> test1 = do
>>> print "What's your name?"
>>> name <- getLine
>>> print ("Hello, " ++ name ++ "!")

> I wanted to express the same thing as the two functions above by using

> sequence. But my efforts were not quite successful.

If you insist on using "sequence", it won't work: This function
just executes the list inside the monad and collects the results (which
are required to have the same type). So

foo = sequence [m1,m2,m3]

where m1..m3 all have a simple monadic type "m a" with the same result
type "a" is equivalent to

foo = do
x1 <- m1
x2 <- m2
x3 <- m3
return [x1,x2,x3]

But you want to use the result "name" of the second line in the third line
(or, equivalently, x2 in m3). But the sequence-form (unlike the do-syntax)
doesn't even mention x2, so this can't work.

> I'm just trying to get an understanding by decomposing from higher level to
> lower level, not that I would want to do that in real code.

I was afraid of that, but IMHO it doesn't make a lot of sense to try to
understand it that way. My mental image is to think of the IO-monad as
special in the sense that any Haskell program at the top-level looks
(eventually, after evaluating everything) something like

m1 >> m2 >> m3 >> ... >> mk :: IO ()

where all the m's are primitive I/O actions that are executed in
sequence (I only used (>>) for simplicity, same idea for (>>=)). And
that already is low level, you can't get any more low level than that.

So it's really a two-tier execution model: I/O on top, pure functions
underneath.

And that's conceptually different from the way I/O is done in strict
functional languages, where I/O is just a silent side effect (ignoring
the history of Haskell for the moment). If you're used to thinking this
way, just stop doing that and try to look at it from the new angle :-)

A consequence of that different concept is that Haskell programs
usually seperate I/O from pure computations; you'll have a bunch of
top-level functions doing the I/O, and a bunch of pure functions for
algorithms and operations on data, where the top-level functions call
the pure functions, but not vice versa. If you don't think of it this
way, if you insist of I/O operations as low-level functions you just
can call anywhere, you'll be fighting with the system all the time,
which is no fun :-)

So what are trying to do is really contraproductive for understanding.

- Dirk

0 new messages