The "normal" way of phrasing what a pure function is, is in terms of referential transparency. A function is pure if it is referentially transparent.
Referential Transparency, roughly, means that you can replace the call to the function with its return value or vice versa at any point in the program, without changing the meaning of the program.
So, for example, if C's printf
were referentially transparent, these two programs should have the same meaning:
printf("Hello");
and
5;
and all of the following programs should have the same meaning:
5 + 5;
printf("Hello") + 5;
printf("Hello") + printf("Hello");
Because printf
returns the number of characters written, in this case 5.
It gets even more obvious with void
functions. If I have a function void foo
, then
foo(bar, baz, quux);
should be the same as
;
I.e. since foo
returns nothing, I should be able to replace it with nothing without changing the meaning of the program.
It is clear, then, that neither printf
nor foo
are referentially transparent, and thus neither of them are pure. In fact, a void
function can never be referentially transparent, unless it is a no-op.
I find this definition much easier to handle as the one you gave. It also allows you to apply it at any granularity you want: you can apply it to individual expressions, to functions, to entire programs. It allows you, for example, to talk about a function like this:
func fib(n):
return memo[n] if memo.has_key?(n)
return 1 if n <= 1
return memo[n] = fib(n-1) + fib(n-2)
We can analyze the expressions that make up the function and easily conclude that they are not referentially transparent and thus not pure, since they use a mutable data structure, namely the memo
array. However, we can also look at the function and can see that it is referentially transparent and thus pure. This is sometimes called external purity, i.e. a function that appears pure to the outside world, but is implemented impure internally.
Such functions are still useful, because while impurity infects everything around it, the external pure interface builds a kind of "purity barrier", where the impurity only infects the three lines of the function, but does not leak out into the rest of the program. These three lines are much easier to analyze for correctness than the entire program.