openlang

pure host function gets cached, write to its argument just vanishes

Weekend project: a toy expression evaluator with host functions written in D, registered by name. I added a memo cache in the registry (pretty proud of it, I've been bragging about it all week). The rule: if a function is pure, it can't have side effects, so cache the result keyed on name plus printed args.

Then I wrote bump, which increments the first element of the array it gets. Each loop step makes a fresh counter = [0], calls bump, prints. I expected [1] every time. I get step 0: counter=[1], then step 1: counter=[0] and step 2: counter=[0]. With --verbose there's a cache hit on steps 1 and 2.

tick (not pure, bumps a global) is fine. twice is fine too.

Things I tried: new int[1] instead of [0], no change. I figured the delegate in register was eating the write, or functionAttributes was lying, but printing the flag shows bump really is pure. The compiler took it without a peep. (argparse is in Phobos, so that's not it.)

Isn't pure supposed to be the safe-to-cache marker? Why is the write just skipped?

// host.d
pure void bump(int[] a)
{
    a[0]++;
}
// cache.d
enum attrs = functionAttributes!fn;
r.cacheable[name] = (attrs & FunctionAttribute.pure_) != 0;

int[] call(ref Registry r, string name, int[] args)
{
    if (r.cacheable[name])
    {
        auto key = name ~ args.to!string;
        if (auto p = key in r.memo)
        {
            r.hits++;
            return *p;
        }
        r.misses++;
        auto res = r.fns[name](args);
        r.memo[key] = res;
        return res;
    }
    return r.fns[name](args);
}
// app.d
foreach (i; 0 .. opts.steps)
{
    auto counter = [0];
    reg.call("bump", counter);
    writeln("step ", i, ": counter=", counter);
}

The conversation continues 2

Follow the reasoning
  1. Permalink

    if a function is pure, it can't have side effects

    in D it can, as long as it only touches its args. int[] is mutable, so bump is only weakly pure. cache only when the params are immutable.

  2. Permalink

    Hit this exact thing in a D registry of mine. Weakly pure, mutable arg, cache happily skipped the write. Took me an evening, right?

Other sides of the same question

  1. Same Int, two targets, two answers

    Same source, same JSON file, different result per target. This is bad. cohort_join reads a JSON export from our tracking syst…

    Replies 0Latest activity gleam
  2. same parseServices, bad line only breaks at build time?

    Changed auto to enum for the built-in table since it never changes, right? Now smtp 25 (no proto) breaks dub build, error end…

    Replies 2Latest activity d
  3. CPS sum overflows the stack in Debug, and in Release only the second function does

    Hello all, I am working through continuation-passing style as an exercise, using a small sensor tool for my data work. loadRe…

    Replies 2Latest activity fsharp