That is a comprehensive reply. No pointers to other material required :-)

On Friday, 21 June 2019 at 16:35:50 UTC, H. S. Teoh wrote:
On Fri, Jun 21, 2019 at 06:07:59AM +0000, Yatheendra via Digitalmars-d-learn wrote: Actually, optimizers work best when there is minimal mutation *in the original source*. The emitted code, of course, is free to use mutation however it wants. But the trouble with mutation at the source level is that it makes many code analyses very complex, which hinders the optimizer from doing what it might have been able to do in the absence of mutation (or a reduced usage of mutation).
[...]

(aside: I hope we don't end up advocating the Haskell/Erlang way or the Clojure way!)

Yes, the hindrances of non-const code are documented (are most programmers listening!). I was only pointing out that mutation being part of the design limits what can be logically const. Is the trade-off clear, between (mythical) guaranteed C++-like-const at all the points we remember to put it, versus guaranteed D-const at the fewer points we manage to put it? Does D-const distort the design (but you get all the optimizations possible in that scenario)?

The inability to have a const caching object seems correct. The way around would be to have a wrapper that caches (meh). If that is not possible, then maybe caching objects just aren't meant to be const by their nature? Isn't memoize a standard library feature? I should look at it, but I wouldn't expect it to be const.

It's not as simple as it might seem. Here's the crux of the problem: you have an object that logically never changes (assuming no bugs, of course). Meaning every time you read it, you get the same value, and so multiple reads can be elided, etc.. I.e., you want to tell the compiler that it's OK to assume this object is const (or immutable).

However, it is expensive to initialize, and you'd like it to be initialized only when it's actually needed, and once initialized you'd like it to be cached so that you don't have to incur the initialization cost again. However, declaring a const object in D requires initialization, and after initialization it cannot be mutated anymore. This means you cannot declare it const in the first place if you want caching.

It gets worse, though. Wrappers only work up to a certain point. But when you're dealing with generic code, it becomes problematic. Assume, for instance, that you have a type Costly that's logically const, but lazily initialized (and cached). Since you can't actually declare it const -- otherwise lazy initialization doesn't work -- you have to declare it mutable. Or, in this case, declare a wrapper that holds a const reference to it, say something like this:

        struct Payload {
                // lazily-initialized data
        }

        struct Wrapper {
                const(Payload)* impl;
                ...
        }

However, what if you're applying some generic algorithms to it?
Generic code generally assume that given a type T, if you want to declare a const instance of it, you simply write const(T). But what do you pass to the generic function? If you pass Wrapper, const(Wrapper) means `impl` cannot be rebound, so lazily initialization fails. OK, then let's pass const(Payload) directly. But that means you no longer have a wrapper, so you can't have lazy initialization (Payload must be constructed before you can pass it to the function, thus it must be eagerly initialized at this point).


I should check on std memoize & maybe code something up for understanding before writing more than this - would you mind pointing to an example range algorithm that we would have trouble passing a caching wrapper to?

I hadn't considered pointers as an option. Why wouldn't the following work, if expressible in D?
   struct CostlyComputeResult {
      ... // data fields
      // constructor takes compute results, no postblit
   }

   struct Wrapper {
      const (CostlyComputeResult) *cachee = 0;
      ... // data fields storing compute inputs
      // constructor takes compute inputs
      // pointer to function(compute inputs)
      const ref get() {
         if (!cachee) {
            cachee = new(function(inputs));
         }
         return cachee;
      }
   }

Hopefully jumping through these hoops is worth the while. Instead, maybe just wait until the compiler grows a 'cache pure' function qualifier (move constructor required?).

Reply via email to