Question

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?

Was it helpful?

Solution

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:

  1. 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).

  2. pure functions cannot call any functions which are not pure.

  3. 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:

  • The function's parameters must be 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.

OTHER TIPS

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top