Send Beginners mailing list submissions to beginners@haskell.org To subscribe or unsubscribe via the World Wide Web, visit http://www.haskell.org/mailman/listinfo/beginners or, via email, send a message with subject or body 'help' to beginners-requ...@haskell.org
You can reach the person managing the list at beginners-ow...@haskell.org When replying, please edit your Subject line so it is more specific than "Re: Contents of Beginners digest..." Today's Topics: 1. Re: clarification on IO (Will Ness) 2. Re: Re: clarification on IO (Gregg Reynolds) ---------------------------------------------------------------------- Message: 1 Date: Mon, 2 Mar 2009 15:27:19 +0000 (UTC) From: Will Ness <will_...@yahoo.com> Subject: [Haskell-beginners] Re: clarification on IO To: beginners@haskell.org Message-ID: <loom.20090302t152519-...@post.gmane.org> Content-Type: text/plain; charset=utf-8 (something happened at my previous attempt to post; my apologies) Gregg Reynolds <dev <at> mobileink.com> writes: > > > On Sun, Mar 1, 2009 at 11:59 AM, Will Ness <will_n48 <at> yahoo.com> 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, 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. :) >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. 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). I think we've established that compiler and runtime should be kept completely separate. >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. >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 > ------------------------------ Message: 2 Date: Mon, 2 Mar 2009 10:26:38 -0600 From: Gregg Reynolds <d...@mobileink.com> Subject: Re: [Haskell-beginners] Re: clarification on IO To: Will Ness <will_...@yahoo.com> Cc: beginners@haskell.org Message-ID: <75cc17ac0903020826k42254862if62e50d55b6da...@mail.gmail.com> Content-Type: text/plain; charset="utf-8" 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 <will_...@yahoo.com> wrote: > > >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! -------------- next part -------------- An HTML attachment was scrubbed... URL: http://www.haskell.org/pipermail/beginners/attachments/20090302/018e69c8/attachment.htm ------------------------------ _______________________________________________ Beginners mailing list Beginners@haskell.org http://www.haskell.org/mailman/listinfo/beginners End of Beginners Digest, Vol 9, Issue 4 ***************************************