Re: [Haskell-cafe] On the purity of Haskell

Sorry to cut most of this out, but I'm trying to focus on the central point here. On Thu, 2011-12-29 at 22:01 +0000, Steve Horne wrote:
In pure functional terms, the result should be equivalent to a fully evaluated value - but putStrLn isn't pure. It cannot be fully evaluated until run-time.
And here it is, I think. You're insisting on viewing the performing of some effect as part of the evaluation of an expression, even though the language is explicitly and intentionally designed not to conflate those two ideas. Effects do not happen as a side-effect of evaluating expressions. Instead they happen because you define the symbol 'main' to be the effect that you want to perform, and then set the runtime system to work on performing it by running your program. Evaluation and effects are just not the same thing, and it makes no sense to say something isn't "evaluated" just because the effect it describes haven't been performed. It's exactly that distinction -- the refusal to conflate evaluation with performing effects -- that is referred to when Haskell is called a pure language. -- Chris Smith

On 29/12/2011 23:30, Chris Smith wrote:
Sorry to cut most of this out, but I'm trying to focus on the central point here.
On Thu, 2011-12-29 at 22:01 +0000, Steve Horne wrote:
In pure functional terms, the result should be equivalent to a fully evaluated value - but putStrLn isn't pure. It cannot be fully evaluated until run-time. And here it is, I think. You're insisting on viewing the performing of some effect as part of the evaluation of an expression, even though the language is explicitly and intentionally designed not to conflate those two ideas. Effects do not happen as a side-effect of evaluating expressions. Instead they happen because you define the symbol 'main' to be the effect that you want to perform, and then set the runtime system to work on performing it by running your program. So, to resurrect an example from earlier...
f :: Int -> IO Int f = getAnIntFromTheUser >>= \i -> return (i+1) Are you claiming that the expression (i+1) is evaluated without knowing the value of i? If not, at run-time your Haskell evaluates those expressions that couldn't be fully evaluated at compile-time. If you do, we're back to my original model. The value returned by main at compile-time is an AST-like structure wrapped in an IO monad instance. The "AST nodes" are partially evaluated functions, but since they can't be evaluated in a sense that knows the precise result of that + operator, we say "this unevaluated function can be treated as a value in itself - let's compose bigger ASTs out of smaller ones". In that model, the 'i' above - the argument to the lambda - never gets an Int value because it's really just a placeholder for tracking the intended flow of data that won't actually flow until run-time. But that AST is just a translated description of the same program. It isn't the end result - it's just an intermediate step along the road to being able to run the thing. The unevaluated function is just falling back on returning a representation of its unevaluated self. That model is still compiled to executable code. That executable code, when run, still interacts with it's environment. That is as much an aspect of what Haskell defines as the functional core. Switching mental models doesn't change the logic any more than switching number bases. They are different descriptions of the same thing. The models are superficially different, but the logic is equivalent. It really doesn't matter whether you call something an AST node or an unevaluated function. An AST node can represent an unevaluated function. An unevaluated function can be implemented as a closure - which is just a collection of data, the same as an AST node. The two things are really both *still* the exact same thing. Even when it's translated to binary executable code, it is *still* the unevaluated function - right up until it gets executed (and in the same moment, evaluated). Either way, at run-time, Haskell is impure.

Le 30/12/2011 01:44, Steve Horne a écrit :
Switching mental models doesn't change the logic any more than switching number bases. Especially when your mental model operates with Dybbuks which do horrible things behind your back... If this is your vision of logic, it is a peculiar one.
Either way, at run-time, Haskell is impure. Most people here, some VERY knowledgeable, do not agree with you. /Errare humanum est, perseverare diabolicum/. Either way, since you insist to prove that you DON'T WANT to understand other people view, I wish you good luck.
Jerzy Karczmarczuk

On Fri, 2011-12-30 at 00:44 +0000, Steve Horne wrote:
So, to resurrect an example from earlier...
f :: Int -> IO Int f = getAnIntFromTheUser >>= \i -> return (i+1)
Did you mean f :: IO Int ? If not, then I perhaps don't understand your example, and your monad is not IO. I'll continue assuming the former.
Are you claiming that the expression (i+1) is evaluated without knowing the value of i?
I'm not sure what you mean by "evaluated" here. I'd say it's in normal form, but it has free variables so it's not even meaningful by itself; it doesn't have a value in the first place. On the other hand, the larger expression, \i -> return (i+1), is closed *and* effectively in normal form, so yes, I'd definitely say it is evaluated so far as that word has any meaning at all.
If not, at run-time your Haskell evaluates those expressions that couldn't be fully evaluated at compile-time.
I certainly agree that the GHC runtime system, and any other Haskell implementation's runtime system as well, evaluates expressions (some representation of them anyway), and does lots of destructive updates to boot. This isn't at issue. What is at issue is whether to shoehorn those effects into the language semantics as a side-effect of evaluation (or equivalently, force evaluation of expressions to be seen as an effect -- when you only allow for one of these concepts, it's a silly semantic game as to which name you call it by), or to treat effects as semantically first-class concepts in their own right, different from the simplification of expressions into values.
If you do, we're back to my original model. The value returned by main at compile-time is an AST-like structure wrapped in an IO monad instance.
Here you're introducing implementation detail here that's rather irrelevant to the semantics of the language. Who knows whether compiler and the runtime implementation build data structures corresponding to an AST and run a reduction system on them, or use some other mechanism. One could build implementations that do it many different ways. In fact, what most will do is generate machine code that directly performs the desired effects and use closures with pointers to the generated machine code. But that's all beside the point. If you need to know how your compiler is implemented to answer questions about language semantics, you've failed already. Purity isn't about the RTS implementation, which is of course plenty effectful and involves lots of destructive updates. It's about the language semantics. -- Chris Smith

On 30/12/2011 01:37, Chris Smith wrote:
So, to resurrect an example from earlier...
f :: Int -> IO Int f = getAnIntFromTheUser>>= \i -> return (i+1) Did you mean f :: IO Int ? If not, then I perhaps don't understand your example, and your monad is not IO. I'll continue assuming the
On Fri, 2011-12-30 at 00:44 +0000, Steve Horne wrote: former. Oops - I meant...
I certainly agree that the GHC runtime system, and any other Haskell implementation's runtime system as well, evaluates expressions (some representation of them anyway), and does lots of destructive updates to boot. This isn't at issue. What is at issue is whether to shoehorn those effects into the language semantics as a side-effect of evaluation (or equivalently, force evaluation of expressions to be seen as an effect -- when you only allow for one of these concepts, it's a silly semantic game as to which name you call it by), or to treat effects as semantically first-class concepts in their own right, different from the simplification of expressions into values. Well, we're playing a semantic game anyway. Treating effects as first-class concepts in themselves is fine, but IMO doesn't make Haskell
f :: Int -> IO Int f x = getAnIntFromTheUser >>= \i -> return (i+x) Your version should be fine, only the lack of a parameter makes a superficial difference. pure.
If you do, we're back to my original model. The value returned by main at compile-time is an AST-like structure wrapped in an IO monad instance. Here you're introducing implementation detail here that's rather irrelevant to the semantics of the language. Think of it as an analogy - using the terminology of compiler design. I might describe arithmetic in terms of an abacus too - it doesn't mean I'd only get those results if I used an abacus.
The implementation details don't matter - the behaviour of the program does.
Purity isn't about the RTS implementation, which is of course plenty effectful and involves lots of destructive updates. It's about the language semantics.
And the semantics of primitive IO actions *include* the run-time effects - those semantics are defined in the Haskell Report. For example... Computation hGetChar hdl reads a character from the file or channel managed by hdl. If you have another meaning in mind for the word "semantics", well that's fine. As already discussed with "side effects", natural language is sadly ambiguous. It can be confusing, but it doesn't change the facts or the logic. I don't know the first thing about denotational semantics, but I do know this - if you place run-time behaviour outside the scope of your model of program semantics, that's just a limitation of your model. It doesn't change anything WRT the program itself - it only limits the understanding you can derive using that particular model.

On Fri, 2011-12-30 at 02:40 +0000, Steve Horne wrote:
Well, we're playing a semantic game anyway. Treating effects as first-class concepts in themselves is fine, but IMO doesn't make Haskell pure.
Okay, so if you agree that: (a) IO actions are perfectly good values in the Haskell sense. (b) They are first class (can passed to/returned from functions, etc.). (c) When used as plain values, they have no special semantics. (d) An IO action is no more a Haskell function than an Int is. (e) All effects of a Haskell program are produced by the runtime system performing the IO action called "main" (evaluating expressions lazily as needed to do so), and NOT as a side-effect of the evaluation of expressions. Then we are completely in agreement on everything except whether the word "pure" should apply to a programming language with those semantics. Certainly the rest of the Haskell community describes this arrangement, with effects being first-class values and performing those effects being something done by the runtime system completely separate from evaluating expressions, as Haskell being pure. You can choose your terminology.
I don't know the first thing about denotational semantics, but I do know this - if you place run-time behaviour outside the scope of your model of program semantics, that's just a limitation of your model. It doesn't change anything WRT the program itself - it only limits the understanding you can derive using that particular model.
The important bit about purity is that programs with I/O fit in to the pure model just fine! The pure model doesn't fully explain what the I/O actions do, of course, but crucially, they also do not BREAK the pure model. It's a separation of concerns: I can figure out the higher-level stuff, and when I need to know about the meaning of the values of specific hairy and opaque data types like IO actions, or some complex data structure, or whatever... well, then I can focus in and work out the meaning of that bit when the time comes up. The meanings of values in those specific complex types doesn't affect anything except those expressions that deal explicitly with that type. THAT is why it's so crucial that values of IO types are just ordinary values, not some kind of magic thing with special evaluation rules tailored to them. -- Chris Smith

[Interaction with its environment] is as much an aspect of what Haskell defines as the functional core.
Switching mental models doesn't change the logic But it does. Other languages do not support the distinction between pure functions and I/O effects. In those languages a function call is what
On 2011-12-29 19:44, Steve Horne wrote: triggers I/O. Haskell uses a different set of types for I/O. It does not use functions for this. The distinction between pure functions and impure code, supported by the language, is a valuable logical tool. You refer to the fact that as part of executing the Haskell program, it is translated into an AST that does not make that distinction. The effect getAnIntFromTheUser is translated into a function. The type of the function says nothing about whether the function has an effect. In that sense Haskell is impure, but so what? That doesn't take away the power of Haskell's distinction between pure functions and impure types, for reasoning about Haskell code.
Either way, at run-time, Haskell is impure. No big deal. Who would want to use a language that you would call "pure"? Haskell has referential transparency. In Haskell, you have assurance that any function without IO in its type is as pure as the lambda calculus.

On 30/12/2011 01:40, Scott Turner wrote:
On 2011-12-29 19:44, Steve Horne wrote:
[Interaction with its environment] is as much an aspect of what Haskell defines as the functional core.
Switching mental models doesn't change the logic But it does. Other languages do not support the distinction between pure functions and I/O effects. Agreed. I said basically the same thing right at the start. This doesn't change any logic, though.
Either way, at run-time, Haskell is impure. No big deal. Who would want to use a language that you would call "pure"? Haskell has referential transparency. In Haskell, you have assurance that any function without IO in its type is as pure as the lambda calculus.
Absolutely. In my original post, of course, I made my "big implicit IORef parameter" argument that says C too is referentially transparent, but I also pointed out another view of transparency - the politician who buries the relevant in a huge pile of the irrelevant is not being transparent - and pointed out that in Haskell you can have zero, one or many IORefs - you can focus in on what is relevant. I actually thought this point would make Haskell advocates happy, but no sign of that yet.
participants (4)
-
Chris Smith
-
Jerzy Karczmarczuk
-
Scott Turner
-
Steve Horne