
On Wednesday 11 August 2010 9:49:07 am mokus@deepbondi.net wrote:
The mixture is not as free as some would like; the fact that Haskell has this distinction between monadic actions and pure values (and the fact that the former can be manipulated as an instance of the latter) means that the programmer must specify whether to evaluate ("=") or execute ("<-") an action, which is a source of endless confusion for beginners and debate over what "pure" means. I don't expect I'll put an end to either, but I would like to point out anyway that, if you accept that distinction (the reality of which is attested by the existence of a computable function - the type checker - for making the distinction), it's fairly easy to see that evaluation is always pure, excepting abuse of unsafePerformIO, et al., and execution is not. Both occur in the context of do-notation. Functions returning monadic actions (whether the resulting action is being evaluated or executed) are still always evaluated to yield an action. That evaluation is pure. The execution of the action yielded may not be, nor should it have to be - that's the whole point of IO! But we still have as much purity as is actually possible, because we know exactly where _execution_ occurs and we don't pretend it doesn't by confusing definition with assignment. "=" always means "=" in Haskell, and "<-" doesn't. In C, "=" always means "<-", even when the RHS is a simple variable reference (consider "x = x;").
This is the important point, I think. Some folks were arguing in #haskell the other day about whether BASIC could be viewed as 'pure,' since it's so simple, it's almost like writing a big IO block. If you go to Sabry's[1] definition of purity, then you could argue that "independence of evaluation order" is trivially satisfied, because there is no "evaluation" only "execution" as people call it. But I think that side-steps something, in that "pure" on its own isn't interesting, certainly if it applies to BASIC that way. To be interesting, you have to look at the whole Sabry thesis, which is "what is a pure *functional* language?" For the second part of that, he identifies the requirement that your language have some sort of lambda calculus (possibly one enriched with datatypes, let, etc. as Haskell does) as a sublanguage. It is only at that point that purity becomes interesting. A plain lambda calculus has certain nice, equational properties to its evaluation. We can inline or abstract out arbitrary expressions without changing the meaning of the program (at least, up to nontermination). The point of remaining "pure," then, is to preserve this aspect of the lambda calculus portion of the language. This obviously means we can't just add rand :: () -> Int, because then: let x = rand () in x + x /= rand () + rand () and that breaks the substitutional nature of the lambda calculus portion of the language (and it's why unsafePerformIO is clearly impure in this sense). Instead, Haskell has a DSL for writing down the sort of effectful programs we want to write in practice, and the expressions in the DSL are first-class in the lambda calculus portion of the language. You can say that from the view internal to the DSL, inlining and abstraction are invalid, because: rand >>= \x -> x + x /= rand >>= \x -> rand >>= \y -> x + y but the important part (at least, for a lot of people) is that we've preserved the property we want for the lambda calculus, which can be used to write large portions of the program. Now, I don't think that this is necessarily tied to functional programming and the lambda calculus. There are probably analogous calculi for logic programming, and one could attempt to preserve its nice properties while adding in a way to do effects for 'real programs', and so on. But, to get back to BASIC, or C, if the language you're extending is an empty language that does nothing, then remaining pure to it isn't interesting. I can't actually write significant portions of my program in such a language, so all I'm left with is the DSL, which doesn't (internally) have the nice properties. (The same applies to the C preprocessor, if you want to try that route. It is not a fragment of the language (even granting that it's a fragment at all) useful for doing actual work in the program---writing actual programs in the preprocessor involves files #including themselves for recursion, and is well in the esoteric category; it is entirely for assembling 'DSL' terms which will do all the actual work.) -- Dan [1] http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.27.7800