openlang

D's pure lets a function write through its arguments

D's pure is weaker than the word suggests, and I only noticed because an evaluator experiment wanted to cache calls.

int[] log;

pure void bump(int[] a)
{
    a[0]++;          // fine, writes through the argument
    // log ~= a[0];  // rejected, touches a mutable global
}

pure in D means no access to mutable global or static state. It says nothing against modifying memory reachable from the parameters. The spec calls this weak purity. A call only becomes something you could cache when the parameters can't carry mutable indirections, immutable data or plain values, and that's strong purity. Same keyword for both, and the parameter types pick which one you get.

So if the evaluator caches results of pure host functions, keying on the attribute alone would cache bump too, and the second call would skip the write. Key on strongly pure signatures instead? Or does that list of conditions get long enough that an explicit opt-in attribute on the host side is simpler?

The conversation continues 4

Follow the reasoning
  1. Permalink

    Swap the parameter for const(int)[] and see whether the cache gets any safer. bump stops compiling, good. But a function taking const(int)[] still isn't something you can cache, because const only says this function won't write. The caller can hold a mutable reference to the same array, change an element between two calls, and the cached result is stale. Only immutable promises nobody writes. So the rule for the evaluator is about the parameter's qualifier, not about the absence of writes inside the function, and I'd want that spelled out wherever the host registers a function.

  2. Permalink

    Does the compiler itself do anything with strong purity, or is it only permission? If DMD already treats two identical strongly pure calls as one, that's a hint the classification is reliable enough to build on. If it's only something the spec allows, then the evaluator's cache would be the first thing in the chain that actually depends on it, and I'd want a test that fails when someone loosens a parameter type.

  3. Permalink

    C has the opposite arrangement, which may help frame the choice. C23 added the [[reproducible]] and [[unsequenced]] attributes for function types, two strengths of a similar promise. The compiler does not check them. If a function marked that way does not behave as promised, the behavior is undefined. D checks pure and leaves the caching decision to whoever reads the signature. C lets the optimizer act on the promise and checks nothing. For an evaluator cache, the D arrangement is the safer one to copy, provided the key is the checked signature and not a separate flag the host can set freely.

  4. Permalink

    Honest answer, I don't know what DMD does with it, and I'd rather not build on whatever the optimizer happens to do anyway. So yes, the cache would be the first thing that really depends on the classification. A test that registers a host function, flips one parameter from immutable to const, and expects registration to refuse it is cheap to write. Funny side effect of nullbyte's point. If the key is the checked signature, my explicit opt-in attribute has nothing left to do, and I don't miss it.

Other sides of the same question

  1. Braces around expr decide what the bytecode compiler sees

    Unbraced expr is the one Tcl habit I'd ban if I could! It looks like a style nit and it isn't. The braced line prints 0. The…

    Replies 13tcl
  2. A macro can't make a newline end a form

    A macro can't make a newline end a form. I keep wanting it to. The notation is tiny. paint red blue on its own line should re…

  3. How do you replay a rule that reads the clock?

    Tried to replay a pricing decision and couldn't. The rule, eligible(item) in Lispex, read the host clock and whatever catalog…