[Haskell-cafe] Monad explanation
ok at cs.otago.ac.nz
Mon Feb 9 20:47:58 EST 2009
On 9 Feb 2009, at 9:56 pm, Gregg Reynolds wrote:
> Here's an analogy that will make the logical contradiction clear.
> Forget computers and assume you have been asked to referee a paper
> containing a proof with the following passage:
> Let x = ___ (Please fill in the blank)
> I think you will agree that would be plainly nonsensical. It's
> logically equivalent to an input operation ("getInt").
No, I do not agree that it would be nonsensical at all.
Why shouldn't a paper describe a family of proofs?
(paper :: Int -> Proof)
> Now back to computers. Given a program text containing the symbol
> '3', the computer will provide a physical representation: an
> electromagnetic pattern in a bit of silicon. That's a value; the
> pattern is to be interpreted as a datum; it is not to be executed.
Who says? It is part of the essence of the stored program computer
that the *same* bit pattern may be computed as numbers and executed
as code. There have certainly been virtual machines where the bit
pattern for the character '+' meant, when executed, ADD.
> For "getChar", the computer will also provide such a pattern, but
> this pattern is to be interpreted as executable code, not as a datum.
"The computer" here must be understood as "The composite of a physical
machine, HAL/BIOS/whatever-maybe, hypervisor-maybe, operating system-
maybe, Haskell compiler, and Haskell libraries. (Yes, I know that I
The expression (getChar) may be represented by executable code,
but then, Haskell being lazy, so may the expression (1+1) be
represented by executable code.
Your problem seems to be based on two fundamental assumptions:
(1) there is an intrinsic difference between code and data,
(2) evaluating getChar reads a character.
Neither assumption is true.
Code and data are interchangeable.
Evaluating getChar yields (without any side effects) a value,
let's call it Gamaliel, and it is the environment's performance
of Gamaliel that reads a character.
So you are seeing a difference when there is none,
and not seeing one where there is one.
> Now suppose we also have an ordinary function like "Add2"; it too
> will be represented as an electromagnetic pattern, to be interpreted
> as executable code.
*AND* as data.
Ever heard of genetic programming?
Genetic programming is a form of evolutionary computing
where members of the population are functions.
Koza's original book used lisp-style trees to represent
these functions (so something was at one and the same time
a tree resulting from genetic operations and used as input
to genetic operations) and executable code.
There have been many variations of the idea since then.
Nowadays some GP systems represent functions by native
machine code. In such systems, native machine code is
- the result of genetic operators
- data that is input to genetic operators
- native code to be executed
all in the same program within a single "generation".
> getChar and Add2 are not data, except in the trivial sense that
> all code is data.
The sense in which all code is data is far from trivial.
It's what makes things like Windows *possible*.
(Try to imagine booting Windows by plugging wires into
a city-sized backplane!)
It's certainly what makes Genetic Programming possible.
The interesting thing here is that since getChar need not
be a function, its representation inside a computer need
not be machine code.
> In all three cases, the symbolic representation is isomorphic to the
> physical representation.
Eh? This is a very strong and extremely dubious claim.
> The 3 will not be executed.
Who says? I once used (and still love) a computer which
had Zero and One instructions. I don't see why you couldn't
have a Three instruction.
> When Add2 is executed, the ensuing process is isomorphic to the
> mathematical function so defined.
The *process* is isomorphic to the *function*?
I think not.
> But when getChar is executed, the ensuing process is not
> isomorphic to a mathematical function.
Yes it is. You are confusing two very different things here:
Performing the result of (getChar).
For what it's worth, the analogue of getChar in Clean and Mercury
*is* a mathematical function.
> The process interacts with the non-mathematical world, which a
> mathematical function can never do.
But a mathematical function can *describe* that process,
and a computing engine can have its interactions governed
by such a description.
And that's what Haskell does.
> So it has a side effect along with its ordinary representational
No. [The result of] (getChar) is a pure mathematical value.
(It might be or contain a function, but it need not.
It could be the number 42 in drag.) When the Haskell
environment does what that [result] says to do, then
reading happens. But the computation of (getChar) and
the reading are DIFFERENT events (notionally) carried
out by DIFFERENT execution engines and happening at
> The point being that the metalanguage commonly used to describe IO
> in Haskell contains a logical contradiction.
No, you have read a contradiction into it, but the contradiction
is not there.
> A thing cannot be both a value and a function,
Yes it can. ALL functions are values.
> but e,g, getChar behaves like a function and has the type signature
> of a value.
getChar behaves like a value.
You can evaluate getChar 500 million times and not one
character will be read.
> I believe this is part of the reason the IO monad is troublesome
> for beginners (I speak from experience).
When I read the "How to declare an imperative" paper, I fell about
laughing. It was so *simple*, and yet, so astronomically far from
anything I could have invented.
When you have understood that there really isn't anything magical
about getChar (and there most CERTAINLY isn't anything remotely
resembling a contradiction or inconsistency or type incorrectness),
you will have begun to understand monads.
More information about the Haskell-Cafe