[Haskell-beginners] Re: clarification on IO

Will Ness will_n48 at yahoo.com
Mon Mar 2 14:28:41 EST 2009


Gregg Reynolds <dev <at> mobileink.com> writes:

> 
> 
> 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_n48 <at> yahoo.com> 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 
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


> Referring back to the mathematical definition of monad, there's no evaluation 
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.  

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




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

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, 




More information about the Beginners mailing list