>A pure function does not read or write any mutable data that is not accessible via the function's arguments [but it can read/write mutable data pointed to by its arguments]
That's not a particularly strong or interesting definition of purity. It just means you can't have mutable globals. That is not really powerful or helpful in any way that I can see.
I feel like this is a bit of a disingenuous (or perhaps just misunderstanding) attempt to latch onto a buzzword from the functional programming realm that means something almost completely different. Purity has a much stronger definition than "don't mutate global variables". See http://en.wikipedia.org/wiki/Pure_function
A function simply annotated with pure is sometimes called "weakly pure". If its arguments are declared immutable, it is "strongly pure" which essentially matches your definition.
All languages I know (e.g. Haskell) make concessions to pragmatism. For example, even pure function can somehow create debug output. Every memory allocation does mutate global state.
The weakly pure class of functions is useful. It limits the potential side-effects to stuff reachable from mutable arguments. For example, a image filter function can be weakly pure while it mutates the image provided as an argument. It removes the need allocate a return image or wrap computations in monads and thunks.
To get really strong guarantees in D, you declare your functions pure, nothrow, and @safe. The nothrow means no exceptions are thrown and @safe means all the low-level tricks (casts,assembly,etc) to break type safety are disabled.
That still means the function can't be treated as referentially transparent (whereas a Haskell function with debug output still can be, for the most part).
Don't get me wrong, that's still somewhat useful - it tells me "this function doesn't do anything too crazy", but it doesn't tell me "it should be safe to refactor this without having to read the function bodies of the entire call graph below this point", which makes calling it "pure" seem a bit disingenuous.
Given pure + @safe + immutable arguments, how is it not referentially transparent? (we ignore the practical concessions: debug statements, memory management, termination, floating point hardware state)
I'm not saying it isn't - it's certainly good that it's also possible to declare something as referentially transparent, but defining the keyword "pure" to imply something much less than referential transparency is still a poor choice.
Edit: To be clear, I don't really feel too strongly about it. It's still leagues ahead of many other languages - I just think that particular choice of keyword is likely to confuse things a bit.
In D, if a function is pure, can it call a function that modifies global state?
That is to say (in not necessarily real D, just close enough) given:
int global_var
void modify_global(int z) {
global_var += z;
return global_var;
}
pure int foo(int x) {
return x + modify_global(2);
}
What happens? Does the compiler barf because it causes modification to something not passed? If so, assuming that you can only modify the object passed (via methods of it, and presumably, the things that object points to in its members),
then it maintains something much closer to referential transparency than you are implying.
But that's a pretty low bar. Using just 'pure', can you mutate an argument in a function called in a conditional expression (making it unsafe to check again)? Can it matter what order a list gets processed in a call to `map` or `filter`? Preventing mutation of global variables narrows the scope of the damage, but having just that still means that if you're maintaining someone else's code, you need to double check everything for side effects, even if they'd be limited to the inputs.
Declare the parameter as 'const' and you know the pure function cannot mutate it. Declare it as 'immutable' and you know that nobody else can mutate it, either.
> All languages I know (e.g. Haskell) make concessions to pragmatism. For example, even pure function can somehow create debug output. Every memory allocation does mutate global state.
This still satisfies the definition of a pure function because debug output via trace is not a "semantically observable" side-effect. I think that D would be better off inventing some new terminology instead of misusing existing formalisms: a function which modifies its own arguments isn't even close to being pure.
>All languages I know (e.g. Haskell) make concessions to pragmatism. For example, even pure function can somehow create debug output.
From the Debug.Trace docs: "should not be used in production code."
>Every memory allocation does mutate global state.
Yes, Haskell is approximately pure just like computers are approximately Turing machines. Computers are technically FSMs, but they're practically so close to Turing machines that we usually don't care. Haskell is so close to pure that we don't care. It's also guaranteed to be semantically pure, until you start to run into those edge cases where computers don't act like Turing machines (i.e. when you run out of RAM).
>It removes the need allocate a return image or wrap computations in monads and thunks.
No one is arguing that impurity/mutability is (frequently) useful. I'm just arguing that it's disingenuous to call what you've described "pure", or that this extra "purity" is very useful.
The reason for "weakly pure" was first discovered by Don Clugston (as far as I can tell). Suppose I have a struct with a member function:
struct S { int s; pure void set(int i) { s = i; }
Now, even in a strongly pure function, I can use the weakly pure function even though it is mutating state through its arguments:
pure int foo(int i) {
S s;
s.set(i);
...
return whatever;
}
I.e. when the effects of a weakly pure function are localized like in the example, they can be called from a strongly pure function and the strongly pure one will remain strongly pure.
It's a really cool idea and it took me a bit to get it, but Don was persistent in explaining it to me :-)
Walter Bright is indeed a really bright mind. Having worked with D recently, I really enjoyed the richness and empowerment the D language offers.
I believe the recent rising interest in functional languages is in part due to the elegant blend of expressiveness and performance. Performance is always a delicate topic nevertheless there are some aspects of functional languages which tend to make them more performant.
Yes, although one can explicitly mark code as @system. (I've never liked the "unsafe" attribute, as it implies the code is actually broken.)
Safety is something that becomes more desirable the larger and more complex a program is. I wanted D to also be convenient to use for quick, simple programs without burdening the user with having to add annotations.
It is unfortunate that the length of the `immutable` seems to ergonomically discourage its use. I know it's really hard to go back on language design decisions, but have there been any thoughts about providing a shorter alternative?
25 comments
[ 5.4 ms ] story [ 64.2 ms ] thread- http://klickverbot.at/blog/2012/05/purity-in-d/
That's not a particularly strong or interesting definition of purity. It just means you can't have mutable globals. That is not really powerful or helpful in any way that I can see.
I feel like this is a bit of a disingenuous (or perhaps just misunderstanding) attempt to latch onto a buzzword from the functional programming realm that means something almost completely different. Purity has a much stronger definition than "don't mutate global variables". See http://en.wikipedia.org/wiki/Pure_function
All languages I know (e.g. Haskell) make concessions to pragmatism. For example, even pure function can somehow create debug output. Every memory allocation does mutate global state.
The weakly pure class of functions is useful. It limits the potential side-effects to stuff reachable from mutable arguments. For example, a image filter function can be weakly pure while it mutates the image provided as an argument. It removes the need allocate a return image or wrap computations in monads and thunks.
To get really strong guarantees in D, you declare your functions pure, nothrow, and @safe. The nothrow means no exceptions are thrown and @safe means all the low-level tricks (casts,assembly,etc) to break type safety are disabled.
Don't get me wrong, that's still somewhat useful - it tells me "this function doesn't do anything too crazy", but it doesn't tell me "it should be safe to refactor this without having to read the function bodies of the entire call graph below this point", which makes calling it "pure" seem a bit disingenuous.
Edit: To be clear, I don't really feel too strongly about it. It's still leagues ahead of many other languages - I just think that particular choice of keyword is likely to confuse things a bit.
That is to say (in not necessarily real D, just close enough) given:
What happens? Does the compiler barf because it causes modification to something not passed? If so, assuming that you can only modify the object passed (via methods of it, and presumably, the things that object points to in its members),then it maintains something much closer to referential transparency than you are implying.
Nope (it would be a useless feature if it could).
This still satisfies the definition of a pure function because debug output via trace is not a "semantically observable" side-effect. I think that D would be better off inventing some new terminology instead of misusing existing formalisms: a function which modifies its own arguments isn't even close to being pure.
const types in D are transitive, meaning that any data reachable through those types is also const. I.e. "turtles all the way down."
From the Debug.Trace docs: "should not be used in production code."
>Every memory allocation does mutate global state.
Yes, Haskell is approximately pure just like computers are approximately Turing machines. Computers are technically FSMs, but they're practically so close to Turing machines that we usually don't care. Haskell is so close to pure that we don't care. It's also guaranteed to be semantically pure, until you start to run into those edge cases where computers don't act like Turing machines (i.e. when you run out of RAM).
>It removes the need allocate a return image or wrap computations in monads and thunks.
No one is arguing that impurity/mutability is (frequently) useful. I'm just arguing that it's disingenuous to call what you've described "pure", or that this extra "purity" is very useful.
It's a really cool idea and it took me a bit to get it, but Don was persistent in explaining it to me :-)
I believe the recent rising interest in functional languages is in part due to the elegant blend of expressiveness and performance. Performance is always a delicate topic nevertheless there are some aspects of functional languages which tend to make them more performant.
It's an interesting design decision that D code is (presumably) @unsafe by default.
Safety is something that becomes more desirable the larger and more complex a program is. I wanted D to also be convenient to use for quick, simple programs without burdening the user with having to add annotations.
Bowing to the obvious, we changed it to "immutable". This has been highly successful in that people immediately understand it.
Nobody ever found a shorter, equally clear term.