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
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.
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
> 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
>> 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
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
>>> 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