[Haskell-cafe] On the purity of Haskell
Steve Horne
sh006d3592 at blueyonder.co.uk
Mon Jan 2 04:43:56 CET 2012
On 01/01/2012 22:57, Jerzy Karczmarczuk wrote:
> Dan Doel :
> ...
>> Also, the embedded IO language does not have this property.
>>
>> do x<- m ; f x x
>>
>> is different from
>>
>> do x<- m ; y<- m ; f x y
>>
>> and so on. This is why you shouldn't write your whole program with IO
>> functions; it lacks nice properties for working with your code.
> Sorry, what are you trying to suggest?
>
> You show two OBVIOUSLY different pieces of code, and you say that they
> are different.
> If, by chance, some newbie reads that and gets the impression that
> (<-) is something equivalent to (=), you are serving the devil.
>
Speaking as the devil...
The do-notation sugar may confuse the issue - the <- looks like an
operator, but translating to binds-and-lambdas form suggests otherwise.
Quick translations (I hope no mistakes) with lots of parens...
m >>= (\x -> (f x x))
m >>= (\x -> (m >>= (\y -> (f x y))))
At first sight, these two expressions can give different results for
reasons other than evaluation order. In particular, there are two bind
operators, not just one.
That is, x and y could get different values for reasons other than the
two m references referring to different things. So... is that true?
Of course even the bind operator arguably isn't primitive. We could
translate to get rid of those too, and see what lies underneath. This is
where we start seeing functions of type...
World -> (x, World)
Problem - this level of abstraction is hypothetical. It is not part of
the Haskell language. Haskell specifically defines the IO monad to be a
black box.
I look at this World parameter as purely hypothetical, a trick used to
gain an intuition. Whereas Jerzy (I think) uses it to claim Haskell is
referentially transparent - those differing x and y values come from
different worlds, or different world-states. I'm not entirely sure,
though, as we got sidetracked.
If main returns a function of type "World -> (x, World)" wrapped in a
monad context, then there is referential transparency as defined in
computer science. But is that a fair claim?
In this model, Haskell is an interpreted language for compositing
functions. We can call those functions programs. The executable is a
translation of the function returned by main, but *not* a translation of
the source code.
But GHC is called a compiler, and compilation is usually considered a
kind of translation - the executable is a translation of the source
code. GHCi is an interpreter, but it doesn't stop at returning a
function of type World -> (x, World) - it does the I/O. And the reason
we use these terms is because, as programmers, we think of the
executable as the program - as a translation of the source code.
So what main returns - that hypothetical function World -> (x, World) -
isn't just a product of the program, it's also a representation of the
program.
I've made similar points before, but how do they work out this time...
So...
when evaluate what effects referentially
transparent
------------- ------------------------ -------
-------------------------
compile-time main no yes
run-time main someParticularWorld yes yes(?)
I've proved effects at run-time, but in this model, the intermediate and
final world-states are products of the evaluation of that "main
someParticularWorld" expression. Even the results extracted from input
actions are referentially transparent - or if not, we're dealing with
the philosophy of determinism.
It's probable that Jerzy told me this earlier and I wasn't ready to hear
it then.
However - we can say basically the same things about C. The World
parameter is implicit in C but then it's implicit in Haskell too.
Everything inside the IO monad black box is outside the scope of the
Haskell language except in that semantics are defined for the primitive
IO actions - basically what happens when a result is extracted out as
part of evaluating a bind. That "(?)" in the "yes(?)" is because this is
all contingent on that hypothetical World -> (x, World) function hidden
inside the IO monad context, which is not specified in the Haskell language.
When I say that Haskell lacks referential transparency because the
execution of primitive IO actions is tied to the evaluation of the bind
operators that extract out their results, and different executions of
the same action yield different results, I'm only appealing to the
defined semantics of the Haskell language. I'm not appealing to a
hypothetical model where the world is passed as a parameter.
OTOH, this World -> (x, World) model is much more appealing than my
partially-evaluated-functions-as-AST-nodes model.
So - the issue seems to be whether the IO monad is a context holding
world-manipulating functions, or whether it's a black box with semantics
specified at the bind level. And if referential transparency is decided
at this level, what practical relevance does it have?
It's probably better to stick with "the functional core is referentially
transparent - IO actions break that, so don't overuse the IO monad". You
can argue "may or may not break that depending on your viewpoint", but
if you can't objectively decide which viewpoint is correct, then you
haven't proven referential transparency.
More information about the Haskell-Cafe
mailing list