
Folks, I've been reading RWH and this http://www.haskell.org/haskellwiki/IO_inside#Haskell_is_a_pure_language I think I understand monads. I think I understand how IO is much different from, e.g. Maybe, State, etc. However there are some turns of phrase with respect to IO that have me baffled. I am giving a presentation on monads next week, and hope you can help. (I'm not quite as lost as this might imply!) Q1: The web page mentions that normal Haskell functions cannot cause side-effects, yet later talks about side-effects with putStrLn. I assume the key point here that IO actions are, by definition, _not_ normal functions? Q2: Is it true to say that *any* monadic action *could *cause side-effects, depending on the design of that monad? i.e. Does one generalize from the IO monad to (possibly) an arbitrary monad? *Musing* This must be true as using State must surely be considered a side-effect. Q3: The web page mentions IO as being a baton, or token, that is used to thread/order the actions. Is true that this is merely one simple perspective, with respect to order of evaluation? This is hard to articulate, but it seems to me that "in the IO monad" there is a large subsystem of (inaccessible) state, machinery, etc. Is it really a token? Q4: Is the following idea accurate: a Haskell program is partitioned into 2 spaces. One is a sequence of IO actions; the other is a space of pure functions and 'normal' Haskell operations. The execution of a program begins with the main :: IO () action and, effectively, crosses from one space to the other. In the pure space, the math-like functions can be highly optimized but only insofar as they do not disrupt the implied order of the IO actions. Because of the type system, the program recognizes when it enters "back" into the IO space and follows different, less optimized rules. My concern is that the above is *not* accurate, but I don't know why. thanks so much for your help Michael Easter -- ---------------------- Michael Easter http://codetojoy.blogspot.com: Putting the thrill back in blog http://youtube.com/ocitv -> Fun people doing serious software engineering

I'll take a shot at answering some of your questions by explaining how I understand it, and we'll see if it helps or makes it worse. Let's talk about monads first. Monads can be thought of as a way of sort of hiding "side effects". That is, there is nothing inherently impure about monads. The "side effects" happen in the bind function, essentiall. For example, in the case of state, the state is carried from one function to another. The bind function actually says how to do this; You just don't usually see it because it's "hidden" in the do notation. In the Maybe function, the plumbing hides the fact that we quit at any point the computation fails, and so on. So while in an impure language, any statement can have any side effect, in haskell, if you know what monad you're in, you know exactly what the "side effect" will occur - which means, it's not really a side effect at all, but part of the actual intended effect. Now for IO. You can think of IO as being essentially State RealWorld. That is, every operation is dependent on the entire state of the world, including what you're thinking, and what kind of bug is crawling on the 18th blade of grass in your yard. If we could actually represent the whole world this way, Haskell would truly be a completely pure language. The only reason IO, and thus Haskell, is impure at all, is because we can't literally represent the whole world. EVERYTHING ELSE, INCLUDING EVERY OTHER TYPE OF MONADIC ACTION IN HASKELL, is completely pure. Ok, so let's address your questions a little more specifically. Q1: The web page mentions that normal Haskell functions cannot cause
side-effects, yet later talks about side-effects with putStrLn. I assume the key point here that IO actions are, by definition, _not_ normal functions?
Right, IO actions can have side effects because they can take into account, and modify, the RealWorld.
Q2: Is it true to say that *any* monadic action *could *cause side-effects, depending on the design of that monad? i.e. Does one generalize from the IO monad to (possibly) an arbitrary monad? *Musing* This must be true as using State must surely be considered a side-effect.
Again, yes, this is accurate, but it's different from most impure languages in that the side effect is completely baked in by the monad you're in, so that you can't really say that the effect is a "side effect" at all.
Q3: The web page mentions IO as being a baton, or token, that is used to thread/order the actions. Is true that this is merely one simple perspective, with respect to order of evaluation? This is hard to articulate, but it seems to me that "in the IO monad" there is a large subsystem of (inaccessible) state, machinery, etc. Is it really a token?
Again, in many ways, it's easier to think of the IO monad as a state monad. In that sense, the state of the world is indeed being passed from one action to the next, as defined by bind. That's the inaccessible state machinery I suspect you're sensing.
Q4: Is the following idea accurate: a Haskell program is partitioned into 2 spaces. One is a sequence of IO actions; the other is a space of pure functions and 'normal' Haskell operations. The execution of a program begins with the main :: IO () action and, effectively, crosses from one space to the other. In the pure space, the math-like functions can be highly optimized but only insofar as they do not disrupt the implied order of the IO actions. Because of the type system, the program recognizes when it enters "back" into the IO space and follows different, less optimized rules.
I think it would be easier to talk about haskell in terms of pure and impure code. All Haskell code is pure except for IO. Pure functions are easier to reason about and to optimize, because you don't have to take into account the RealWorld state, or other possible, REAL side effects..
My concern is that the above is *not* accurate, but I don't know why.
thanks so much for your help Michael Easter
-- ---------------------- Michael Easter http://codetojoy.blogspot.com: Putting the thrill back in blog
http://youtube.com/ocitv -> Fun people doing serious software engineering
_______________________________________________ Beginners mailing list Beginners@haskell.org http://www.haskell.org/mailman/listinfo/beginners

Thanks Andrew, this is really great...
My main revelation here is that a "side-effect" in other monads is still
pure. e.g. The Logger example in RWH builds up a list of log strings
"behind the scenes" but this is much different than writing to disk, or
launching missiles, to quote SP Jones.
'Revelation' is a good word.... The tectonic plates are coming together! I
needed this.
thanks!
Mike
ps. re: bind/hidden. Monads strike me as a glorious instance of
encapsulation that is not OO, which appropriated the term in the 1990s.
On Fri, Feb 27, 2009 at 9:30 PM, Andrew Wagner
I'll take a shot at answering some of your questions by explaining how I understand it, and we'll see if it helps or makes it worse.
Let's talk about monads first. Monads can be thought of as a way of sort of hiding "side effects". That is, there is nothing inherently impure about monads. The "side effects" happen in the bind function, essentiall. For example, in the case of state, the state is carried from one function to another. The bind function actually says how to do this; You just don't usually see it because it's "hidden" in the do notation. In the Maybe function, the plumbing hides the fact that we quit at any point the computation fails, and so on. So while in an impure language, any statement can have any side effect, in haskell, if you know what monad you're in, you know exactly what the "side effect" will occur - which means, it's not really a side effect at all, but part of the actual intended effect.
Now for IO. You can think of IO as being essentially State RealWorld. That is, every operation is dependent on the entire state of the world, including what you're thinking, and what kind of bug is crawling on the 18th blade of grass in your yard. If we could actually represent the whole world this way, Haskell would truly be a completely pure language. The only reason IO, and thus Haskell, is impure at all, is because we can't literally represent the whole world. EVERYTHING ELSE, INCLUDING EVERY OTHER TYPE OF MONADIC ACTION IN HASKELL, is completely pure.
Ok, so let's address your questions a little more specifically.
Q1: The web page mentions that normal Haskell functions cannot cause
side-effects, yet later talks about side-effects with putStrLn. I assume the key point here that IO actions are, by definition, _not_ normal functions?
Right, IO actions can have side effects because they can take into account, and modify, the RealWorld.
Q2: Is it true to say that *any* monadic action *could *cause side-effects, depending on the design of that monad? i.e. Does one generalize from the IO monad to (possibly) an arbitrary monad? *Musing* This must be true as using State must surely be considered a side-effect.
Again, yes, this is accurate, but it's different from most impure languages in that the side effect is completely baked in by the monad you're in, so that you can't really say that the effect is a "side effect" at all.
Q3: The web page mentions IO as being a baton, or token, that is used to thread/order the actions. Is true that this is merely one simple perspective, with respect to order of evaluation? This is hard to articulate, but it seems to me that "in the IO monad" there is a large subsystem of (inaccessible) state, machinery, etc. Is it really a token?
Again, in many ways, it's easier to think of the IO monad as a state monad. In that sense, the state of the world is indeed being passed from one action to the next, as defined by bind. That's the inaccessible state machinery I suspect you're sensing.
Q4: Is the following idea accurate: a Haskell program is partitioned into 2 spaces. One is a sequence of IO actions; the other is a space of pure functions and 'normal' Haskell operations. The execution of a program begins with the main :: IO () action and, effectively, crosses from one space to the other. In the pure space, the math-like functions can be highly optimized but only insofar as they do not disrupt the implied order of the IO actions. Because of the type system, the program recognizes when it enters "back" into the IO space and follows different, less optimized rules.
I think it would be easier to talk about haskell in terms of pure and impure code. All Haskell code is pure except for IO. Pure functions are easier to reason about and to optimize, because you don't have to take into account the RealWorld state, or other possible, REAL side effects..
My concern is that the above is *not* accurate, but I don't know why.
thanks so much for your help Michael Easter
-- ---------------------- Michael Easter http://codetojoy.blogspot.com: Putting the thrill back in blog
http://youtube.com/ocitv -> Fun people doing serious software engineering
_______________________________________________ Beginners mailing list Beginners@haskell.org http://www.haskell.org/mailman/listinfo/beginners
-- ---------------------- Michael Easter http://codetojoy.blogspot.com: Putting the thrill back in blog http://youtube.com/ocitv -> Fun people doing serious software engineering

Michael Easter
Thanks Andrew, this is really great...My main revelation here is that a "side-
effect" in other monads is still pure. e.g. The Logger example in RWH builds up a list of log strings "behind the scenes" but this is much different than writing to disk, or launching missiles, to quote SP Jones. May be this is exactly how we ought to look at the IO monad - as a Logger monad? Each IO-bound chain of action-functions defining an IO value that holds a record of what it is the IO primitives that we used promised us they will do when run by the system. That's it. (?) After all, we can have a definition of such a value, and have it run multiple times for us, so _as definition_ it's no different than any other definition in Haskell. It's just that _its value_ can cause the system to actually perform these IO actions in some circumstances. As for terminology: we've got to have some special name for functions that are chainable by bind. Calling them actions confuses them with the real world actions performed by IO. May be to call them "action functions"?

On Sun, Mar 1, 2009 at 4:28 AM, Will Ness
Michael Easter
writes: ... After all, we can have a definition of such a value, and have it run multiple times for us, so _as definition_ it's no different than any other definition in Haskell. It's just that _its value_ can cause the system to actually perform these IO actions in some circumstances.
But it isn't a definition. "Reference" would be better; "getChar" is a term that references a value.
As for terminology: we've got to have some special name for functions that are chainable by bind. Calling them actions confuses them with the real world actions performed by IO.
Correction: special name for IO "functions" (actually "IO terms" would be better). The monad just organizes stuff, so the IO monad, as monad, is no different than any other monad.
May be to call them "action functions"?
This was a big problem for me; I find terms like "action", "computation", "function" completely misleading for IO terms/values. You might find "Computation" considered harmful. "Value" not so hot eitherhttp://syntax.wikidot.com/blog:5useful; see also the comment "Another try at the key sentence". There are a few other articles on the blog that address this terminology problem. -gregg

On Sun, Mar 1, 2009 at 4:28 AM, Will Ness
wrote: Michael Easter
writes: ... After all, we can have a definition of such a value, and have it run multiple times for us, so _as definition_ it's no different than any other definition in Haskell. It's just that _its value_ can cause the system to actually perform these IO actions in some circumstances. But it isn't a definition. "Reference" would be better; "getChar" is a term
Gregg Reynolds
As for terminology: we've got to have some special name for functions that are chainable by bind. Calling them actions confuses them with the real world actions performed by IO.
What I seek here is to demystify the monad, any monad, and IO monad in particular, and for that a clear and consisent terminology must be employed. We should be able to name things, in English, that we talk about - in English.
Correction: special name for IO "functions" (actually "IO terms" would be better).
The monad just organizes stuff, so the IO monad, as monad, is no different
Why? They are just fuctions, of type (Monad m => a -> m b). What I'm saying, they are of special type, chainable by the M monad, so it seems logical to have a special name for such M-chainable functions, e.g. "M-action functions" (whatever the M). The monad M is chaining and combining something. It is M-action functions, of type (a -> M b), that get chained by its bind, and their hidden data combined by it, behind the curtain. Usually when we have a name for some concept, it becomes clearer. And vice versa. than any other monad.
May be to call them "action functions"?
This was a big problem for me; I find terms
like "action", "computation", "function" completely misleading for IO terms/values. Why? A function of type (a -> M b) is a function that returns a value, (:: M b), tagged with some monadic hidden data. In case of IO, it is a promise to perform some actual I/O that's passed around, hidden. But the M-action function itself is just a regular Haskell function. It can be defined elsewhere, anywhere. IO values themselves are no mystery too. They just carry along with them a log of promised I/O activity as specified at time of their creation (definition).
You might find ["Computation" considered harmful. "Value" not so hot either] useful; see also the comment "Another try at the key sentence". There are a few other articles on the blog that address this terminology problem.
Thank you. Will do.

On Sun, Mar 1, 2009 at 11:59 AM, Will Ness
The IO-action is not the I/O operation in the real world, but an action of recording a promise to perform it.
Any IO primitive can be seen as latching this hidden promise onto its explicit return value, thus creating a monadic value (:: IO a), carrying along this hidden promise. IO bind combines these promises into a combined record, or log, of promises to perform actual I/O activity, if called upon by the system.
That recording of a promise is the IO-action that IO monad is about, from pure Haskell standpoint.
Ok, I kinda like the idea of accumulating a "log" of promised actions, although I'd suggest a different term since log usually means history. Maybe antilog or prelog or future trace or the like. In any case, I think that's useful for explaining lazy evaluation, but it's not directly implicated by monad semantics. IOW monad semantics and evaluation strategy are (mostly) orthogonal.
Another monad will have another meaning for its actions, latching another hidden data on their results, but they still can be seen as actions, in context of being sequenced and combined by that monad's bind.
I see, you're thinking of action as something like the promised eval. As opposed to the action of performing actual IO. I fear that might confuse newcomers, though; probably better to stick with "function" terminology and discuss evaluation separately.
Correction: special name for IO "functions" (actually "IO terms" would be better).
Why? They are just fuctions, of type (Monad m => a -> m b). What I'm saying, they are of special type, chainable by the M monad, so it seems logical to have a special name for such M-chainable functions, e.g. "M-action functions" (whatever the M).
Technically they cannot be functions - there's no "same input, same output" (at least not for input operations). No referential transparency. That's the problem.
This was a big problem for me; I find terms
like "action", "computation", "function" completely misleading for IO terms/values.
Why? A function of type (a -> M b) is a function that returns a value, (:: M b), tagged with some monadic hidden data. In case of IO, it is a promise to
perform some actual I/O that's passed around, hidden. But the M-action
function itself is just a regular Haskell function. It can be defined elsewhere, anywhere.
But the "promise to perform" is a matter of evaluation semantics, not denotational semantics. Denotationally these things cannot be functions. I saw your other note too. I think the idea that the runtime builds a "future log" is useful - simple and pretty easy to grok. But I would recommend keeping a clear distinction between Haskell's language semantics (denotational) and its compiler/runtime semantics (evaluational). Denotationally, all a monad does is ensure sequencing, which is necessary to properly order the (non-deterministic) IO values. With lazy eval this gets translated into the building of a "future log" etc. Thanks, -gregg

Gregg Reynolds
On Sun, Mar 1, 2009 at 11:59 AM, Will Ness
wrote:
The IO-action is not the I/O operation in the real world, but an action of recording a promise to perform it. Any IO primitive can be seen as latching this hidden promise onto its explicit return value, thus creating a monadic value (:: IO a), carrying along this hidden promise. IO bind combines these promises into a combined record, or
of promises to perform actual I/O activity, if called upon by the system. That recording of a promise is the IO-action that IO monad is about, from pure Haskell standpoint.
Ok, I kinda like the idea of accumulating a "log" of promised actions, although I'd suggest a different term since log usually means history. Maybe antilog or prelog or future trace or the like. In any case, I think that's useful for explaining lazy evaluation, but it's not directly implicated by monad semantics. IOW monad semantics and evaluation strategy are (mostly) orthogonal.
Another monad will have another meaning for its actions, latching another hidden data on their results, but they still can be seen as actions, in context of being sequenced and combined by that monad's bind.
I see, you're thinking of action as something like the promised eval. As opposed to the action of performing actual IO. I fear that might confuse newcomers, though; probably better to stick with "function" terminology and discuss evaluation separately.
Correction: special name for IO "functions" (actually "IO terms" would be better). Why? They are just fuctions, of type (Monad m => a -> m b). What I'm saying, they are of special type, chainable by the M monad, so it seems logical to have a special name for such M-chainable functions, e.g. "M-action functions" (whatever the M).
Technically they cannot be functions - there's no "same input, same output" (at least not for input operations). No referential transparency. That's the
log, problem.
This was a big problem for me; I find terms
like "action", "computation", "function" completely misleading for IO terms/values. Why? A function of type (a -> M b) is a function that returns a value, (:: M b), tagged with some monadic hidden data. In case of IO, it is a promise to
perform some actual I/O that's passed around, hidden. But the M-action
function
itself is just a regular Haskell function. It can be defined elsewhere, anywhere.
But the "promise to perform" is a matter of evaluation semantics, not denotational semantics. Denotationally these things cannot be functions.I saw your other note too. I think the idea that the runtime builds a "future log" is useful - simple and pretty easy to grok. But I would recommend keeping a clear distinction between Haskell's language semantics (denotational) and its compiler/runtime semantics (evaluational). Denotationally, all a monad does is ensure sequencing, which is necessary to properly order the (non-deterministic) IO values. With lazy eval this gets translated into the building of a "future log" etc.Thanks,-gregg
_______________________________________________ Beginners mailing list Beginners <at> haskell.org http://www.haskell.org/mailman/listinfo/beginners

Hi Will,
I can tell I'm talking to a kindred spirit - we oughta be able to describe
all this stuff in plain, simple, clear English. It's a great challenge for
a prose writer.
On Mon, Mar 2, 2009 at 9:21 AM, Will Ness
Maybe antilog or prelog or future trace or the like. In any case, I think that's useful for explaining lazy evaluation, but it's not directly implicated by monad semantics. IOW monad semantics and evaluation strategy are (mostly) orthogonal.
I don't follow. Monad semantics in general is to chain together its action- functions (:: a -> M b). IO monad's semantics is that it promises to perform the recorded requests, if called upon. Not only is it directly implicated by its semantics, it IS its semantics. It is what IO-bind is. Other monad's binds will mean something else.
Right, but this is //Haskell// monad semantics. It's an artifact of lazy evaluation. Referring back to the mathematical definition of monad, there's no evaluation process or promise, only denotation.
To recapture, the only difference IO monad has, is that it refers EXPLICITLY to a compiler, and its runtime execution operations. But from inside Haskell it's just anther monad (I've said that already haven't I? :) ).
Mmm, I wouldn't say explicitly. Again, this is because of lazy evaluation, not because of the nature of monads. They just put stuff in order, regardless of evaluation strategy.
That's why I propose to call these bind-chainable functions action functions, not just actions, and actual I/O acivities to call just that, activity. Also notice I write I/O there where it belongs to the actual world action (... the terminology really MUST be refined here!). I think anything else is confusing.
Agreed, that's the key distinction.
I'm open for another suggestion for how to name these "action-functions", but it's time the definite name is finalized; it's very confusing to see these IO- action-functions referred to, in all these tutorials, as performing real world I/O actions. They don't, of course.
Alas, this is where taste enters into it. You can come up with the perfect set of names and somebody won't like it. ;) My own preference is to dispense with the action/function idiom and just say that IO //terms// are essentially unbound (but typed) variables, that will be bound to values when interpreted at runtime. So I wouldn't even call "getChar" an action; I'd just say it's a term denoting an IO Char value to be provided later. In contrast with a constant term like '3'.
Better to avoid "evaluation" altogether (that seems to be your another idea from that log, is it?).
Yep. Hate evaluation. ;)
I think the KEY is to always keep a clear separation of what is inside the pure Haskell world, and what is executed by its run-time, as an imperative program iving inside the real volatile world (capable of calling back into Haskell).
Yeah, I don't recall many tutorials zeroing in on this. I think it's very helpful.
And when inside the pure Haskell world, IO monad is ABSOLUTELY in NO RESPECT no different than any other. The operational log metaphor help keep this part of its semantics clear, from the other part - the fact that its operational log will actually get executed by run-time.
Correction: special name for IO "functions" (actually "IO terms" would be better). Why? They are just fuctions, of type (Monad m => a -> m b). What I'm saying, they are of special type, chainable by the M monad, so it seems logical to have a special name for such M-chainable functions, e.g. "M-action functions" (whatever the M).
Technically they cannot be functions - there's no "same input, same output" (at least not for input operations).
Yes there is. There's the whole point I'm driving at. We are not performing a computation with our code. We describe the computation that will be performed. Our values are functions. The usage of actual input is deferred to the runtime system.
What's the domain of "getChar"? We can't mess with the mathematical definition of function, so we really can't use it for IO stuff (or any non-deterministic value, e.g. random). To quibble yet more: there's no computation involved in IO, strictly speaking, since it's analog. A Turing machine (as he originally described it) can't do IO.
It's just like Show functions that (will) add their output onto a hidden parameter, the string-being-built (when called). Same here, with the log-being- built. Assentially, we're dealing here with the delayed application of carried functions, that's all.
Here's again a simple outline of how an IO monad might look like, inside Haskell. It helped clarify things for me (dealing with output only, but still):
________________________________ data IO a = IORec -> (a,IORec) -- building the record of I/O activities to be performed
instance Monad IO where return a rec = (a,rec) -- return :: a -> IO a (m »= g) rec = uncurry g $ m rec -- g :: a -> IO b
putStrLn :: a -> IO () putStrLn a rec = ((),rec ++ [("putStrLn", a)]) ================================
No referential transparency. That's the problem.
Everything is referentially transparent. There are no side effects in Haskell. You know that. :) You wrote as much yourself (assuming you're the author of that blog). _______________________________________ IO value describes the computation that WILL BE performed OUTSIDE of Haskell. =======================================
That is a statement that is easy to understand, and is not at all confusing. I think.
But also logically inconsistent: how can an expression "inside" of Haskell refer to something outside of Haskell? More specifically, Haskell expressions can only denote values in the Haskell semantic universe. IO processes (not computations) lie outside of that universe, so Haskell cannot say anything about them. But the //result// of an IO process is a value within the semantic universe, so it can be referenced.
This was a big problem for me; I find terms like "action", "computation", "function" completely misleading for IO terms/values. Why? A function of type (a -> M b) is a function that returns a value, (:: M b), tagged with some monadic hidden data. In case of IO, it is a promise to perform some actual I/O that's passed around, hidden. But the M-action function itself is just a regular Haskell function. It can be defined elsewhere, anywhere.
But the "promise to perform" is a matter of evaluation semantics, not denotational semantics. Denotationally these things cannot be functions.
But they are. They describe future computation to be performed outside of
Haskell. Their values - inside Haskell - are one and the same - it's (:: IO a) entities. Which encapsulate the record, the _sceleton_of_future_computation_to_be_performed, which is ONE and only. It's just that it has holes in it, where the actual values will go into. It's like back into Prolog with its yet-unassigned variables. Or to any imperative language with set-once.
The whole future/promise thing comes from lazy evaluation. With strict evaluation, there would be no such promise; expressions would be evaluated (reduced) on the spot, so there would be no log of promised execution. Language semantics (denotational) and evaluation strategy (operational?) are orthogonal. Evaluation strategy doesn't change the meaning (denotation) of the program, but it does affect its execution profile - memory consumption, etc. - so programmers have to think about it. Except of course it does change the behavior of the program where IO is concerned. In a lazy language you can write IO expressions that will never get evaluate/performed, but not so in a strict language. But behavior and meaning are different things. Take another example: the strict application operator '$!'. It doesn't change the denotation of a program but it does change its behavior, by which I mean the interpretational process. Such operators don't denote, really; they're more like meta-syntax or pragmas than Haskell syntax.
Denotationally, all a monad does is ensure sequencing, which is necessary to properly order the (non-deterministic) IO values.
No it does more than that. It ascribes actual meaning to what its M-action- functions mean, and it defines what it means for them to be combined in a chain. They are of course kept in sequence, in that chain.
Ok, then for the IO monad all it does is ensure sequencing. The behavior of getChar comes from its implementation, not from the monad it is wrapped in. Remember GHC's implementation of IO as a state transformer is not the only possible implementation.
With lazy eval this gets translated into the building of a "future log" etc.
Right, only better not to use "eval" - ever. Haskell has expressions which get reduced; values belong to its runtime system. They are OUTSIDE of Haskell world.
We do not "evaluate" anything. It would be an imperative. :)
We're probably stuck with it, practically speaking, but where extra clarity is needed I suggest "reduction" instead of "evaluation", from the lambda calculus.
Thanks,-gregg
Thank you, for a great and enlightening discussion.
Same here!

Gregg Reynolds
Hi Will,I can tell I'm talking to a kindred spirit - we oughta be able to
describe all this stuff in plain, simple, clear English. It's a great challenge for a prose writer.
On Mon, Mar 2, 2009 at 9:21 AM, Will Ness
wrote: Monad semantics in general is to chain together its action- functions (:: a -> M b). IO monad's semantics is that it promises to perform the recorded requests, if called upon. Not only is it directly implicated by its semantics, it IS its semantics. It is what IO-bind is. Other monad's binds will mean something else.
Right, but this is //Haskell// monad semantics. It's an artifact of lazy
Referring back to the mathematical definition of monad, there's no evaluation
evaluation. I don't think so, no. This value can be forced just like any other: Prelude> let x = do { c <- getChar; putChar c; return c } Prelude> const 1 $! x 1 Prelude> :t x x :: IO Char process or promise, only denotation. Right, it denotes lists of requests that come from chained action-functions, in case of my metaphoric "IO".
________________________________ data IO a = IORec -> (a,IORec) -- building the record of I/O activities to be performed
instance Monad IO where return a rec = (a,rec) -- return :: a -> IO a (m »= g) rec = uncurry g $ m rec -- g :: a -> IO b putStrLn :: a -> IO () putStrLn a rec = ((),rec ++ [("putStrLn", a)]) ================================
_______________________________________ IO value describes the computation that WILL BE performed OUTSIDE of Haskell. =======================================
But also logically inconsistent: how can an expression "inside" of Haskell
refer to something outside of Haskell? More specifically, Haskell expressions can only denote values in the Haskell semantic universe. IO processes (not computations) lie outside of that universe, so Haskell cannot say anything about them. But the //result// of an IO process is a value within the semantic universe, so it can be referenced. No, it is just described, symbolically, to be interpreted by some external interpreter, outside of Haskell realm (in our example). The actual I/O hasn't got a chance to be performed yet. The "holes" in the computation structure, ready to receive their values, stay empty. IOW the function is built but not applied yet, its argument(s) not yet bound, computation not yet performed. But the definition that defines this computation is already there. It can stay lazy, it can be forced too.
The whole future/promise thing comes from lazy evaluation.
No, not at all. We could force the value totally that is produced by the above monad. All it does is it produces a symbolic description of things to do (in my metaphor). It has nothing to do with Haskell being lazy or strict. The whole thing could be strictly computed, and still be describing - symbolically - requests to perform I/O (and pure Haskell calculations that go with them, working with thus received values).
With strict evaluation, there would be no such promise; expressions would be evaluated (reduced) on the spot, so there would be no log of promised execution.
Language semantics (denotational) and evaluation strategy (operational?) are orthogonal. Evaluation strategy doesn't change the meaning (denotation) of the
No, this can only be done with impure language. Strict or not, doesn't matter. Since Haskell is pure, it records these requests to be performed later by the impure run-time system. It's got nothing to do with delay/promise of lazy evaluation. There is no evaluation in Haskell. Eval is an imperative. :) That's the whole central point about it. The computation gets defined (as a function) - but not yet performed (function not called). ___________________________________________ It is all about separating pure and impure, =========================================== not about doing it strictly or non-strictly. It'll be performed when the run-time system will call that function. It may do this twice, or never. The functions is defined just as well. Its definition can be forced to be more strict, to be fleshed out more fully. It's still a function wating to be called, so that the computation process it describes will get performed. See? what is promised, is actual I/O operations to be performed - **by the impure run-time system**. That's the promises I'm talking about, and that's the reason it's all put aside into a function. It's to separate the pure and the impure, not to delay some //calculations//. We're not talking about no delayed evaluation. :) program, but it does affect its execution profile - memory consumption, etc. - so programmers have to think about it. Except of course it does change the behavior of the program where IO is concerned. In a lazy language you can write IO expressions that will never get evaluate/performed, but not so in a strict language. No, not so. You can have function in a strict language, calling the I/O primitives. This function might never get called. So yes, you can do that in a strict language. Never once in this whole discussion was I talking about "evaluation strategy". ____________________________________________ It's not about strictness, it's about purity. ============================================
Denotationally, all a monad does is ensure sequencing, which is necessary to properly order the (non-deterministic) IO values. No it does more than that. It ascribes actual meaning to what its M-action- functions mean, and it defines what it means for them to be combined in a chain. They are of course kept in sequence, in that chain.
Ok, then for the IO monad all it does is ensure sequencing. The behavior of
getChar comes from its implementation, not from the monad it is wrapped in. Yes. But the fact that the primitive _io_get_char (or whatever) actually gets _called_ later, *does* come from the monad it is wrapped in. Or else the I/O would get performed by the following (and it doesn't): Prelude> const 1 $! getChar 1
With lazy eval this gets translated into the building of a "future log" etc. Right, only better not to use "eval" - ever. Haskell has expressions which get reduced; values belong to its runtime system. They are OUTSIDE of Haskell world. We do not "evaluate" anything. It would be an imperative. :)
We're probably stuck with it, practically speaking, but where extra clarity
is needed I suggest "reduction" instead of "evaluation", from the lambda calculus. "Reduction" is always better. Not every rewrite simplifies the code though. Cheers,

(something happened at my previous attempt to post; my apologies)
Gregg Reynolds
On Sun, Mar 1, 2009 at 11:59 AM, Will Ness
wrote: The IO-action is not the I/O operation in the real world, but an action of recording a promise to perform it.
Ok, I kinda like the idea of accumulating a "log" of promised actions,
Maybe antilog or prelog or future trace or the like. In any case, I think
although I'd suggest a different term since log usually means history. It is a log of history of promises _requested_. Or it can be a plan then. An operational record. A list. :) that's useful for explaining lazy evaluation, but it's not directly implicated by monad semantics. IOW monad semantics and evaluation strategy are (mostly) orthogonal. I don't follow. Monad semantics in general is to chain together its action- functions (:: a -> M b). IO monad's semantics is that it promises to perform the recorded requests, if called upon. Not only is it directly implicated by its semantics, it IS its semantics. It is what IO-bind is. Other monad's binds will mean something else. We really ought to look at IO monad as just another kind of regular Haskell monad first. Here your idea from the blog helps, of separating the two worlds, of compiler and of its run-time system implementation (in my re-formulation). The operational plan built by IO monad may have multiple entries interspersed, going back and force to one world or another (getting new input into its placeholder and then going into Haskell to perform a chunk of pure computation with it, then possibly going back for another input etc.). Or in case of output only of known in advance values it can be compiled down to just one output instruction. Which is recorded and stored in an (:: IO a) value. Which can be run SEVERAL times by the run-time, or none at all. It's really helpful to imagine this operational plan as just a symbolic list of operations to be performed by some interpreter, capable of taking input, and calling back into Haskell code with it. The next step is to imagine it compiled into some kind of C with set-once semantics. To recapture, the only difference IO monad has, is that it refers EXPLICITLY to a compiler, and its runtime execution operations. But from inside Haskell it's just anther monad (I've said that already haven't I? :) ).
Another monad will have another meaning for its actions, latching another hidden data on their results, but they still can be seen as actions, in context of being sequenced and combined by that monad's bind.
I see, you're thinking of action as something like the promised eval.
It is an action of entering a request (for future I/O activity) to be recorded by Haskell, to be performed later by its run-time system (notice I avoid the word evaluated which is confusing as it alludes back to the Haskell-world calculations). It _is_ the meaning of IO-action-function's associated ...well, action.
As opposed to the action of performing actual IO. I fear that might confuse newcomers, though; probably better to stick with "function" terminology and discuss evaluation separately.
That's why I propose to call these bind-chainable functions action functions, not just actions, and actual I/O acivities to call just that, activity. Also notice I write I/O there where it belongs to the actual world action (... the terminology really MUST be refined here!). I think anything else is confusing. I'm open for another suggestion for how to name these "action-functions", but it's time the definite name is finalized; it's very confusing to see these IO- action-functions referred to, in all these tutorials, as performing real world I/O actions. They don't, of course. Better to avoid "evaluation" altogether (that seems to be your another idea from that log, is it?). I think the KEY is to always keep a clear separation of what is inside the pure Haskell world, and what is executed by its run-time, as an imperative program iving inside the real volatile world (capable of calling back into Haskell). And when inside the pure Haskell world, IO monad is ABSOLUTELY in NO RESPECT no different than any other. The operational log metaphor help keep this part of its semantics clear, from the other part - the fact that its operational log will actually get executed by run-time.
Correction: special name for IO "functions" (actually "IO terms" would be better). Why? They are just fuctions, of type (Monad m => a -> m b). What I'm saying, they are of special type, chainable by the M monad, so it seems logical to have a special name for such M-chainable functions, e.g. "M-action functions" (whatever the M).
Technically they cannot be functions - there's no "same input, same output" (at least not for input operations).
Yes there is. There's the whole point I'm driving at. We are not performing a computation with our code. We describe the computation that will be performed. Our values are functions. The usage of actual input is deferred to the runtime system. It's just like Show functions that (will) add their output onto a hidden parameter, the string-being-built (when called). Same here, with the log-being- built. Assentially, we're dealing here with the delayed application of carried functions, that's all. Here's again a simple outline of how an IO monad might look like, inside Haskell. It helped clarify things for me (dealing with output only, but still): ________________________________ data IO a = IORec -> (a,IORec) -- building the record of I/O activities to be performed instance Monad IO where return a rec = (a,rec) -- return :: a -> IO a (m »= g) rec = uncurry g $ m rec -- g :: a -> IO b putStrLn :: a -> IO () putStrLn a rec = ((),rec ++ [("putStrLn", a)]) ================================
No referential transparency. That's the problem.
Everything is referentially transparent. There are no side effects in Haskell. You know that. :) You wrote as much yourself (assuming you're the author of that blog). _______________________________________ IO value describes the computation that WILL BE performed OUTSIDE of Haskell. ======================================= That is a statement that is easy to understand, and is not at all confusing. I think.
This was a big problem for me; I find terms like "action", "computation", "function" completely misleading for IO terms/values. Why? A function of type (a -> M b) is a function that returns a value, (:: M b), tagged with some monadic hidden data. In case of IO, it is a promise to perform some actual I/O that's passed around, hidden. But the M-action function itself is just a regular Haskell function. It can be defined elsewhere, anywhere.
But the "promise to perform" is a matter of evaluation semantics, not denotational semantics. Denotationally these things cannot be functions.
But they are. They describe future computation to be performed outside of Haskell. Their values - inside Haskell - are one and the same - it's (:: IO a) entities. Which encapsulate the record, the _sceleton_of_future_computation_to_be_performed, which is ONE and only. It's just that it has holes in it, where the actual values will go into. It's like back into Prolog with its yet-unassigned variables. Or to any imperative language with set-once.
I saw your other note too. I think the idea that the runtime builds a "future log" is useful - simple and pretty easy to grok. But I would recommend keeping a clear distinction between Haskell's language semantics (denotational) and its compiler/runtime semantics (evaluational).
Denotationally, all a monad does is ensure sequencing, which is necessary to
I think we've established that compiler and runtime should be kept completely separate. properly order the (non-deterministic) IO values. No it does more than that. It ascribes actual meaning to what its M-action- functions mean, and it defines what it means for them to be combined in a chain. They are of course kept in sequence, in that chain.
With lazy eval this gets translated into the building of a "future log" etc.
Right, only better not to use "eval" - ever. Haskell has expressions which get reduced; values belong to its runtime system. They are OUTSIDE of Haskell world. We do not "evaluate" anything. It would be an imperative. :)
Thanks,-gregg
Thank you, for a great and enlightening discussion.
_______________________________________________ Beginners mailing list Beginners <at> haskell.org http://www.haskell.org/mailman/listinfo/beginners

On Sat, Feb 28, 2009 at 3:50 AM, Michael Easter
Q1: The web page mentions that normal Haskell functions cannot cause side-effects, yet later talks about side-effects with putStrLn. I assume the key point here that IO actions are, by definition, _not_ normal functions?
putStrLn is a perfectly pure function (there is no other kind in Haskell), it takes a String and return a value of type IO (), this value is like any other first class object in Haskell and can be manipulated as thus, the only difference is that this value can be computed in the context of the IO monad and then have a side-effect. The value of type IO a are often called actions but they aren't fundamentally different from other values.
Q2: Is it true to say that any monadic action could cause side-effects, depending on the design of that monad? i.e. Does one generalize from the IO monad to (possibly) an arbitrary monad? *Musing* This must be true as using State must surely be considered a side-effect.
"Side-effect" isn't really appropriate in my opinion, since everything is still perfectly pure (except in IO) and the "effects" one obtain by using a monad are perfectly determined by the nature of the monad.
Q3: The web page mentions IO as being a baton, or token, that is used to thread/order the actions. Is true that this is merely one simple perspective, with respect to order of evaluation? This is hard to articulate, but it seems to me that "in the IO monad" there is a large subsystem of (inaccessible) state, machinery, etc. Is it really a token?
This "token" is an useful concept to understand how the IO monad works, indeed that is how it is encoded in GHC where the IO monad is just a variant of the ST monad with a special type for the state : RealWorld. Of course the primitives used in the IO monad can't be written in pure Haskell.
Q4: Is the following idea accurate: a Haskell program is partitioned into 2 spaces. One is a sequence of IO actions; the other is a space of pure functions and 'normal' Haskell operations. The execution of a program begins with the main :: IO () action and, effectively, crosses from one space to the other. In the pure space, the math-like functions can be highly optimized but only insofar as they do not disrupt the implied order of the IO actions. Because of the type system, the program recognizes when it enters "back" into the IO space and follows different, less optimized rules.
You can effectively see it like that, but it must be noted that the ordering of IO actions isn't special in any sense, it is just implied by the strict data dependence encoded in the monad, the compiler don't optimize differently the IO portion of the program. "main" is the only function that is evaluated unconditionally, any other value and/or function in a program is only evaluated if main needs it. -- Jedaï

On 28 Feb 2009, at 10:13, Chaddaï Fouché wrote:
On Sat, Feb 28, 2009 at 3:50 AM, Michael Easter
wrote: Q1: The web page mentions that normal Haskell functions cannot cause side-effects, yet later talks about side-effects with putStrLn. I assume the key point here that IO actions are, by definition, _not_ normal functions?
putStrLn is a perfectly pure function (there is no other kind in Haskell), it takes a String and return a value of type IO (), this value is like any other first class object in Haskell and can be manipulated as thus, the only difference is that this value can be computed in the context of the IO monad and then have a side-effect. The value of type IO a are often called actions but they aren't fundamentally different from other values.
Note, take this information with a pretty gigantic grain of salt. It is true that you are writing a pure program when using IO functions, but you should probably think about what that means. The advantage of pure programs is that you get certain neat benefits like not having to bother about "am I doing this in the right order" or "will the system do weird random shit while this function is executed". This is not true about IO typed functions. An IO typed function generates an IO value which the runtime runs. That means, that they essentially get us no further forward than C. In C we write pure functions called C preprocessor macros. These pure functions generate imperative programs (C programs) which are run by the runtime (the C compiler and your OS). This is no different to IO. So, the lesson – IO is in theory pure. But that doesn't mean that we get all the nice benefits of functional programming while using the IO monad. One can say pretty much the same thing about all stateful monads, although IO is by far the worse, in that the state can be influenced by chunks of reality totally outside your control. Bob

On Fri, Feb 27, 2009 at 8:50 PM, Michael Easter
Q1: The web page mentions that normal Haskell functions cannot cause side-effects, yet later talks about side-effects with putStrLn. I assume the key point here that IO actions are, by definition, _not_ normal functions?
Right, the IO primitives are not functions.
Q2: Is it true to say that *any* monadic action *could *cause side-effects, depending on the design of that monad? i.e. Does one generalize from the IO monad to (possibly) an arbitrary monad? *Musing* This must be true as using State must surely be considered a side-effect.
It might be helpful to distinguish between internal side effects (e.g. updating a global var in an imperative language) and external (IO). Haskell uses monads to simulate the former and to serialize the latter.
Q3: The web page mentions IO as being a baton, or token, that is used to thread/order the actions. Is true that this is merely one simple perspective, with respect to order of evaluation? This is hard to articulate, but it seems to me that "in the IO monad" there is a large subsystem of (inaccessible) state, machinery, etc. Is it really a token?
Bear in mind that's the GHC implementation. I'm told another implementation uses some kind of continuation monad. The only requirement as I understand it is that the IO monad, however it is designed, must impose an order of evaluation on the IO primitives. GHC does hide the implementation details.
Q4: Is the following idea accurate: a Haskell program is partitioned into 2 spaces. One is a sequence of IO actions; the other is a space of pure functions and 'normal' Haskell operations. The execution of a program begins with the main :: IO () action and, effectively, crosses from one space to the other. In the pure space, the math-like functions can be highly optimized but only insofar as they do not disrupt the implied order of the IO actions. Because of the type system, the program recognizes when it enters "back" into the IO space and follows different, less optimized rules.
Hmm, to me there's something fishy about thinking in terms of two spaces. IO expressions are normal Haskell expressions; they just happen to get sequenced because they get chained together by monad ops. I.e. as far has Haskell semantics is concerned they're not special; Haskell needn't know they're impure. The impure part is external the Haskell semantics. Then remember lazy evaluation; the whole program can be optimized at compile time to some extent, and at run time evaluation is forced by the ordering of the "IO chain" that leads to main. Be careful about thinking of the program as "starting at main" and then proceeding through the IO chain. That's perilously close to imperative thinking. It's more accurate (IMO) to say that the program gets evaluated at run time, which means main gets evaluated, and since the value of main depends on the series of IO actions chained to it, they get forced in order. So main isn't really the "first thing that happens"; it's the ONLY thing that happens, since the meaning of the program (i.e. main) is equivalent to the evaluation of the IO chain. Actual IO is a side effect of evaluating main. Hope it helps, gregg

Michael Easter
Q1: The web page mentions that normal Haskell functions cannot cause side-
effects, yet later talks about
side-effects with putStrLn. I assume the key point here that IO actions are, by definition, _not_ normal functions?
The way I look at it, a monad is a way of saying, "we'll be carrying along and combining, in some specific manner, values with some extra information attached to them, which most functions don't need to worry about". _How_ it carries along these values and combines them, and the corresponding hidden data, is what makes the particular Monad type to be what it is. Let's call a function an M-action function if its type is (:: a -> M b), for some monad M. Such functions can get chained by M's bind, as in ( (x :: M a) >>= (f :: a -> M b) ) :: M b M can also provide us with special functions that will reveal its hidden information, e.g. (reveal :: (hidden_stuff -> a -> M b) -> a -> M b), so that for a user-defined (g :: hidden_stuff -> a -> M b), (reveal g) can be chained and the function g will get called providing us with the information, if the monad so chooses. For example, Philip Wadler in his paper "The Essence of Functional Programming" gives an example of a "position" monad P which defines its own special function, resetPosition, enabling us to reset its hidden data - in this case, a code position. IO primitives like putStrLn, instead of revealing anything to us, carry along a promise to take notice of the value they are supplied with and to perform an actual real world IO action with them, when called upon to do so by the system. We can create chains of IO-action functions and store them in variables, without them getting executed by the system - and thus without causing any real- world IO action, e.g. x = do {putStrLn "1";return 2} somewhere in your program will just create a definition that will just hold an IO-action function in it. It's just a feature of an interactive loop that when is sees a value of type (IO a) it will not just return it, but will also execute the promised actions held inside it: -- in HUGS -- Prelude> [ do {putStrLn "1";return 2} ] -- a value just gets returned [<<IO action>>] :: [IO Integer] Prelude> do {putStrLn "1";return 2} -- actual IO action performed 1 :: IO Integer -- in GHCi -- Prelude> let x=[do {putStrLn "1"; return 2}] Prelude> head x 1 2 Prelude> head x 1 2 Prelude> :t it it :: Integer The same goes to the special identifier (main :: IO a) that will also get treated in this special way - getting executed on sight. :)
participants (6)
-
Andrew Wagner
-
Chaddaï Fouché
-
Gregg Reynolds
-
Michael Easter
-
Thomas Davie
-
Will Ness