TDPL, p. 167:
as long as the mutable state in a function is entirely transitory (i.e., allocated on the stack) and private (i.e., not passed along by reference to functions that may taint it), then the function can be considered pure.
import std.stdio : writeln;
struct M{
int[4] _data;
pure ref int opIndex(size_t i){ return _data[i]; }
}
pure M foo(ref M m){
m[0] = 1234;
return m;
}
void main(){
M m1 = M([7, 7, 7, 7]);
writeln(m1);
foo(m1);
writeln(m1);
}
// output:
// M([7, 7, 7, 7])
// M([1234, 7, 7, 7])
The mutable state is transitory because it's on the stack, correct? But it's not private. So how is foo()
allowed to modify m1
?
A Pure Function is a function (a block of code) that always returns the same result if the same arguments are passed. It does not depend on any state or data change during a program's execution. Rather, it only depends on its input arguments.
Pure functions and referentially transparent expressions are stateless. A piece of code is stateless when its outcomes are not influenced by previous events.
A function that does not modify any of the objects it receives as arguments. Most pure functions are fruitful. modifier: A function that changes one or more of the objects it receives as arguments. Most modifiers are fruitless.
A pure function does not have side-effects and its result does not depend on anything other than its inputs. A pure function guarantees that for a given input it will produce the same output no matter how many times it is called.
pure
has been expanded a bit since the release of TDPL, since pure
as TDPL describes turns out to be far too restrictive to be useful beyond simple math functions and the like. You can look at the online documentation for the current definition, but it essentially comes down to this:
pure
functions cannot access any module-level or static variables which can be mutated during the course of the program (they must be const
value types or immutable
to be accessed from a pure
function).
pure
functions cannot call any functions which are not pure
.
pure
functions cannot perform I/O.
That's it. There are no other restrictions. However, there are additional restrictions required if a pure
function is going to be optimized such that it only gets called one time even if it's used multiple times within a statement. Namely:
immutable
or implicitly convertible to immutable
.In theory that could be expanded to requiring that the function's arguments must be immutable
or implicitly convertible to immutable
(so that a function with const
parameters could be optimized when it's given immutable
arguments), but that's not currently the case.
Such pure
functions are sometimes referred to as "strongly" pure
, whereas those which cannot be optimized would be referred to as "weakly" pure
. TDPL describes strongly pure
functions. Weakly pure
functions were added in order to make pure
more generally usable.
While weakly pure
functions can alter their arguments, they cannot alter the global state, so when they're called by strongly pure
functions (which can't alter their arguments), the guarantee that the strongly pure
function's return value will always be the same for the same arguments still holds. Essentially, because the weakly pure
functions cannot mutate global state, they're part of the private state of the strongly pure
function that they're called from. So, it's very much in line with what Andrei describes in section 5.11.1.1 pure
is as pure
Does in TDPL, except that the private state of the function has been expanded to allow functions which can alter its private state without altering global state.
Another major thing of note which has been added since TDPL with regards to pure
is function attribute inference. pure
, nothrow
, and @safe
are inferred for templated functions (though not for normal functions). So, if a templated function can be pure
, now it is pure
. Its purity depends on what it's instantiated with. So, it becomes possible to use pure
with templated functions, whereas before, you usually couldn't, because if you made it pure
, it wouldn't work with an impure function. But if you didn't make it pure
, then you couldn't use it with a pure
function, so it was a major problem for pure
. Fortunately, attribute inference fixes that now though. As long as a templated function follows the rules listed above when it's instantiated, then it's considered pure
.
The this
reference is considered part of the function's parameters, and since the function is weakly pure, you can modify the parameters. With the state of this
considered part of the input, the function still fulfills the condition of having the same output with the same input.
Consider this entirely legal example, which outputs 2
:
import std.stdio : writeln;
struct S
{
int foo = 0;
pure void set(size_t i){ foo = i; }
}
void main()
{
S s;
s.set(2);
writeln(s.foo);
}
As far as I know, after TDPL was released, the definition of pure was expanded. The book describes strongly-pure functions. After that, two developments have taken place: weakly-pure functions were added, which are allowed to mutate their parameters. Also, purity-inference has been added for template functions so that you can use a template function's instantiation as long as it's pure even if the template function isn't decorated with pure
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With