
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,