I find it most useful to define referential transparency in terms of substitution (which is the definition Wikipedia starts with). Namely: if I substitute a function call with its result is there an observable different in program behaviour? If there is, the program is not referentially transparent.
This is clearly the case with `println`.
(println "Hi there")
is clearly not the same program as
nil
as the former causes output to the console, while the latter does not -- an observable difference.
Using Scheme (because I don't know Clojure that well)
(define (foo x)
(let ([y 0])
(set! y 10)
(+ x 1)))
is a referentially transparent program. Although it contains a side-effect (the assignment to y) this effect is not observable outside `foo`, and thus any call to `foo` can be substituted with its result.
Pure usually has a stricter definition. First we need the ability to state whether two functions are equal. Then we need a function which is constantly unit. Finally we need composition such that (f >> g) is "f then g". Now, a function f is pure if and only if
constantly_unit = f >> constantly_unit
If you unpack that a bit it might translate as "if we throw away the return value of a function, it is exactly the same as if nothing is happening at all".
If your notion of equality differentiates "println" and "constantly_unit" then we cannot call "println" pure.
Note that this is a very powerful notion of purity. It's so powerful as to render Haskell impure as if we have the function
loop x = loop x
then `loop >> constantly_unit` never returns and is therefore easy to distinguish from `constantly_unit` itself. This just drives home that non-termination is an effect itself!
Hmm I wonder if that loop example can be rehabilitated. If we interpret it, not as a description of how to evaluate loop, but rather as a constraint on loop. Then we see that the statement is simply a non-condition on loop.
This would mean that non-termination is not a property of the function, but of the compiler/runtime, in that they failed to notice that loop was a partial function called with an input value for which it was not defined!
The definition of purity in this post isn't quite correct. The common definition (which this post seems to be using) is that a pure function is one that has no side effects. This doesn't have much of an implication for reasoning about almost-pure functions like println, which have to effects on the program execution, but do cause some distinction. For example, with a pure function, the following are equivelent:
How so? println returns `Unit` so certainly, what you say above doesn't hold.
Even if `println` returned a value, it would still be considered pure as long as that value is strictly calculated from its input parameter and that parameter alone (say, the size of the string).
Another often used criterion to determine purity of a function is whether inlining it everywhere in your program produces a similar program. There again, `println` passes this test.
The only way `println` could not be pure is if you call it with the same string twice and it returns a different value.
My (quite possibly wrong) understanding is that purity has nothing to say about side effects. It's simply concerned with the inputs and outputs of functions, specifically whether the function will always return the same value given the same input.
What zak_mc_kracken is saying is that under this definition println is obviously and trivially pure because it returns the same value for every input.
You're correct that it won't have the proper side effects when optimized but under this definition that doesn't have anything to do with purity.
Edit:
If you want the "side effects" of the println statement to be considered part of the "output" then you want something like Haskell's semantics where the println statements are IO actions.
You are correct under this particular interpretation of purity, there are just competing ways to assess said purity. Wikipedia only lists two (the one you just showed and the one I described) but is missing the third one (the inlining approach).
In Haskell, all functions in the IO monad are by definition pure, and that includes all the println functions.
That would be discarding the effect of print, since inputs aren't just ignored, but used to modify the system somehow somewhere. Replace print* by set!, `:=` if that helps.
Yes the relationship between input and output is clearly defined, though. Your point of view raises a good point about precision when talking about purity and functions.
I see that if there's a kernel panic, or power outage that f(x) might return something insane or nothing at all, but those aren't things I'm usually worried about. What's the practical upside to this?
Is breaking the kernel something I should actually consider likely?
Compiler optimization are allowed to change how often a pure function is called, and you can freely change it in your code. If it had a printf inside it, you'd need a way to say you don't care about the side effect of printing a specific line one time, and not zero or two times.
Of course there is lots of code that has side effects in implementation but not in their interface. Like malloc, or a read only data structure that has an internal cache it updates.
It always returns nil (for inputs that it doesn't fail on).
Doesn't that mean it's pure?
Unfortunately not because it also modifies global state (the state of the console). A pure function implies that it is referentially transparent, meaning that:
a = foo()
b = a + a
Is the exact same as:
b = foo() + foo()
But if foo() modifies global state, then this statement is not true. In this case the difference is printing something twice vs. once. If a function is pure, a compiler can optimize the second example into the first example.
For a function to be pure, it must always return the same value given the same input and not modify any state that is observable outside of its definition.
Since println is just an example, perhaps it isn't really a good example to use when trying to give an example of how impure functions can have side effects. Sending some ink to a printer or altering pixels on a screen isn't exactly a compelloing argument for side effects.
Maybe a better example function is one that deletes data from your hard drive, or sens control signals to a robot that locks the doors or changes the A/C or starts some industrial process, releasing toxic chemicals.
The point is println does have a side effect, it's just not a very interesting one.
Or maybe the definition of side effect is what is causing the problem, after all - executing any function does have physical side effects - electrons move, electromagnetic styate chabnes, ambient temperature changes. Maybe the problem is where we draw the line for observable side effects.
Comments
I think I'm missing something: How is the function println not pure? It always returns nil (for inputs that it doesn't fail on).
Doesn't that mean it's pure?
Edit:
I'd define pure in this sense as the same thing as referentially transparent, meaning f(x) will always return the same thing for a given x.
I find it most useful to define referential transparency in terms of substitution (which is the definition Wikipedia starts with). Namely: if I substitute a function call with its result is there an observable different in program behaviour? If there is, the program is not referentially transparent.
This is clearly the case with `println`.
is clearly not the same program as as the former causes output to the console, while the latter does not -- an observable difference.Using Scheme (because I don't know Clojure that well)
is a referentially transparent program. Although it contains a side-effect (the assignment to y) this effect is not observable outside `foo`, and thus any call to `foo` can be substituted with its result.Ah, but
doesn't return nil, it returns a value of the type which can be transparently substituted for the call.Yes, but you're writing in Haskell whereas my example is in Clojure. Hence the difference :)
Dammit! Sorry, carry on!
Pure usually has a stricter definition. First we need the ability to state whether two functions are equal. Then we need a function which is constantly unit. Finally we need composition such that (f >> g) is "f then g". Now, a function f is pure if and only if
If you unpack that a bit it might translate as "if we throw away the return value of a function, it is exactly the same as if nothing is happening at all".If your notion of equality differentiates "println" and "constantly_unit" then we cannot call "println" pure.
Note that this is a very powerful notion of purity. It's so powerful as to render Haskell impure as if we have the function
then `loop >> constantly_unit` never returns and is therefore easy to distinguish from `constantly_unit` itself. This just drives home that non-termination is an effect itself!Hmm I wonder if that loop example can be rehabilitated. If we interpret it, not as a description of how to evaluate loop, but rather as a constraint on loop. Then we see that the statement is simply a non-condition on loop.
This would mean that non-termination is not a property of the function, but of the compiler/runtime, in that they failed to notice that loop was a partial function called with an input value for which it was not defined!
That's probably possibly in this particular case but it sounds a lot like you're heading toward Halting Problem territory here :)
Ah, but Haskell is lazy so loop will never be evaluated and a
will return unit no matter what.Ah, yeah, I was being fast and loose with laziness. You have to `seq` the argument to `constantly_unit`, too.
The definition of purity in this post isn't quite correct. The common definition (which this post seems to be using) is that a pure function is one that has no side effects. This doesn't have much of an implication for reasoning about almost-pure functions like println, which have to effects on the program execution, but do cause some distinction. For example, with a pure function, the following are equivelent:
however, these are not the same when f=println.How so? println returns `Unit` so certainly, what you say above doesn't hold.
Even if `println` returned a value, it would still be considered pure as long as that value is strictly calculated from its input parameter and that parameter alone (say, the size of the string).
Another often used criterion to determine purity of a function is whether inlining it everywhere in your program produces a similar program. There again, `println` passes this test.
The only way `println` could not be pure is if you call it with the same string twice and it returns a different value.
Consider the following program.
This should output "Hello \n World\n" However, assuming println is pure, we can optimize this to just Which produces a different output. We could also convert: intoMy (quite possibly wrong) understanding is that purity has nothing to say about side effects. It's simply concerned with the inputs and outputs of functions, specifically whether the function will always return the same value given the same input.
What zak_mc_kracken is saying is that under this definition println is obviously and trivially pure because it returns the same value for every input.
You're correct that it won't have the proper side effects when optimized but under this definition that doesn't have anything to do with purity.
Edit: If you want the "side effects" of the println statement to be considered part of the "output" then you want something like Haskell's semantics where the println statements are IO actions.
Edit: zak_mc_kracken beat me to it.
You are correct under this particular interpretation of purity, there are just competing ways to assess said purity. Wikipedia only lists two (the one you just showed and the one I described) but is missing the third one (the inlining approach).
In Haskell, all functions in the IO monad are by definition pure, and that includes all the println functions.
That would be discarding the effect of print, since inputs aren't just ignored, but used to modify the system somehow somewhere. Replace print* by set!, `:=` if that helps.
Yes the relationship between input and output is clearly defined, though. Your point of view raises a good point about precision when talking about purity and functions.
Well, if somebody breaks the kernel so that printing doesn't work, it will most likely return something strange, in the case of Clojure, an exception.
A pure function always returns the same result because it only depends on its input parameters. An impure IO function does not.
I see that if there's a kernel panic, or power outage that f(x) might return something insane or nothing at all, but those aren't things I'm usually worried about. What's the practical upside to this?
Is breaking the kernel something I should actually consider likely?
Compiler optimization are allowed to change how often a pure function is called, and you can freely change it in your code. If it had a printf inside it, you'd need a way to say you don't care about the side effect of printing a specific line one time, and not zero or two times.
Of course there is lots of code that has side effects in implementation but not in their interface. Like malloc, or a read only data structure that has an internal cache it updates.
Unfortunately not because it also modifies global state (the state of the console). A pure function implies that it is referentially transparent, meaning that:
Is the exact same as: But if foo() modifies global state, then this statement is not true. In this case the difference is printing something twice vs. once. If a function is pure, a compiler can optimize the second example into the first example.For a function to be pure, it must always return the same value given the same input and not modify any state that is observable outside of its definition.
Since println is just an example, perhaps it isn't really a good example to use when trying to give an example of how impure functions can have side effects. Sending some ink to a printer or altering pixels on a screen isn't exactly a compelloing argument for side effects.
Maybe a better example function is one that deletes data from your hard drive, or sens control signals to a robot that locks the doors or changes the A/C or starts some industrial process, releasing toxic chemicals.
The point is println does have a side effect, it's just not a very interesting one.
Or maybe the definition of side effect is what is causing the problem, after all - executing any function does have physical side effects - electrons move, electromagnetic styate chabnes, ambient temperature changes. Maybe the problem is where we draw the line for observable side effects.
Surprised no one's brought up Haskell's Debug.Trace, which effectively does this, but returning whichever value you tell it to.