7

Is this a reasonable view of Haskell IO?

When given a program, the Haskell runtime does the following:

  1. Calls main to get back an "IO computation"
  2. It then executes or "runs" that computation thereby performing all of the side-effects the computation contains.

This two-stage approach allows main to remain a pure function.

An IO computation in this case is like a special version of Haskell that has explicit sequencing - or perhaps there is a better way to describe this?

ErikR
  • 51,541
  • 9
  • 73
  • 124
  • This is a quite good explanation indeed. But, in Haskell, main is not a function, just a value. (In Frege, OTOH, it is a function, because it gets the list of command line arguments passed) – Ingo Feb 23 '12 at 23:54
  • 1
    This doesn't quite work, because results of computations can affect future computations (e.g. `getLine`). – ehird Feb 23 '12 at 23:58
  • 6
    @ehird Yes it does. The `>>=` is just a way to construct IO values. The hard parts come when dealing with things like exceptions – Philip JF Feb 24 '12 at 00:02
  • @ehird: That's implicit - all monads can be context-sensitive. – porges Feb 24 '12 at 00:05
  • @Porges true, but that doesn't help the guy who "deals with main". He still has to know how to chain operations. – Owen Feb 24 '12 at 00:15
  • I have written [this](http://stackoverflow.com/questions/3117583/) some time ago, you might find it useful – sdcvvc Feb 24 '12 at 01:24
  • @Owen: I'm just pointing out that ehird's complaint doesn't mean that this definition of IO doesn't work. Simply because IO results are sensitive to previous results doesn't make the model invalid. In any monad, results can depend on previous results (not necessarily 'previous' in a temporal sense), and many of these monads are pure. – porges Feb 24 '12 at 02:09
  • @Porges That's right, sorry I misinterpreted. I just mean to say it's a monad because it chains, not that it chains because it's a monad. But I think we are saying the same thing. – Owen Feb 24 '12 at 03:32

2 Answers2

12

Yeah, that's a decent semantic model of how the programs are executed. The implementation doesn't work like that, of course, but you can still use that model to reason about the programs.

But more generally, what IO does is to allow you to treat imperative programs as pure values. The Monad operations then allow you to compose imperative programs from smaller imperative programs (or to use the usual term in this context, actions) and pure functions. So the purely functional model, while it cannot execute imperative programs, can still describe them as expressions of type IO a, and the compiler can translate these descriptions into imperative code.

Or you could say this:

  • The compiler (not the runtime) evaluates main.
  • The result of that evaluation is an imperative program.
  • This program is saved to the target executable.
  • You then execute the target program.

I.e., the "evaluate main" part of your model is pushed to the compiler, and is not in the runtime as you first describe it.

Luis Casillas
  • 29,802
  • 7
  • 49
  • 102
  • +1 I like the explanation that "the compiler evaluates main", although you could say that about any programming language with a compiler. But you hit the main point right on the head: Haskell is different because you *compose* IO actions from smaller IO actions. – Dan Burton Feb 24 '12 at 00:11
  • @Dan Burton my problem with this is that appeals to "the compiler" or "the runtime" impose a particular way of evaluating Haskell. After all, Hugs doest "compile" main, it interprets it. As much as possible we should talk about what program means, not what some mythical compiler does with it. Compilers come into the picture when we need detailed cost models, but really only then. – Philip JF Feb 24 '12 at 00:17
  • @PhilipJF I'm sympathetic to your position there, but still, talking about "the compiler" or "the runtime" is still of didactic value to many people. Think of it as a stepping stone. Or this way: often the best way to teach an advanced topic to a newcomer is to "lie" to them, and then, after they've absorbed the "lies," explain how the initial lie falls short. In this case, well, the shortcoming is that we want to be able to reason about programs independently of how the language is implemented. – Luis Casillas Feb 24 '12 at 00:36
  • 2
    I half agree with you about this, and think your answer is rather good. Although, at least for people without an imperative background, I think a pure language based approach can be a suitable way to teach these things. In fact, for some time I have been nursing a fantasy of writing a textbook on computer architecture for people with only a math background. Starting with lambda calculus, functional programming, and a little algebra (categories, monads, etc) we would derive logic gates, simple circuits, RISC computers, compilers, and basic operating systems. – Philip JF Feb 24 '12 at 01:05
  • 2
    I'm not convinced that "the compiler evaluates main"; for example the expressions `if elem 10000000 [1..] then getLine else putStrLn "hello"` and `getLine` evaluate to the same thing, yet `main = if elem 10000000 [1..] then getLine else putStrLn "hello"` and `main = getLine` will most probably result in different executables. That would not be true if the compiler really evaluated `main`. – sdcvvc Feb 24 '12 at 15:34
  • @PhilipJF If you write this book let us know. I am engineer but I would be very interested in how to think mathematically about computers. Maybe there is some decent book on the topic -- I am not afraid of math and I like it. – Trismegistos Feb 25 '12 at 12:07
  • @PhilipJF As a mathematician who only recently came to programming, I would be delighted if you wrote that book! – Chris Taylor Apr 30 '12 at 17:03
6

Your view of IO is good, but I have a problem with this line

Calls main to get back an "IO computation"

The best way to think about Haskell is that functions dont do anything. Rather, you declaratively describe what values are. A program consists of a description of an IO value called main. The only sense to which it "calls main" is that the declaration of main is reduced to Weak Head Normal Form (or something similar).

IO is the type of arbitrary side effect-full computations. The pure subset of Haskell is a purely declarative description of values, that happens to allow for undecidable descriptions. Think of Haskell as a mathematical language like set theory. Statements in set theory dont do anything, but they might involve complicated computations like "the smallest set which contains Akerman's_function(30)". They can also contain undecidable statements like "S = the set of all sets that do not contain themselves"

@amindfv is half right: main is not a "pure function". It is not a function at all. It is a value, defined by a pure reduction, encoding unpure computation.

Philip JF
  • 28,199
  • 5
  • 70
  • 77
  • I think the poster is aiming for more of a summary for imperative programmers. In Haskell we'd say "Evaluates" instead of "Calls", but otherwise it's pretty much the same. "IO computation" is also just a variant of "IO monad". I think in 'true Haskellspeak' we'd say the runtime "evaluates main to obtain a value in the IO monad", but this is just being pedantic. The original sentence works fine for explaining it to imperative programmers. :) – porges Feb 24 '12 at 03:37
  • I actually think "IO computation" is preferable to "IO monad" most of the time. As to being pedantic: the poster's understanding was already pretty good, I wanted to dig in deeper. From my perspective, the most important thing about "thinking Haskelly" is not thinking of the language as "doing" but thinking about it as "being". "Calls" carries to much procedural baggage, and isn't necessarily true: see sacundim's description of main being "compiled" instead of "called". I think "evaluates" sticks to the denotation without brining in as much about how. The idea is a deeper understanding. – Philip JF Feb 24 '12 at 03:47