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