Best practices of using const
Yatheendra
3df4 at gmail.ru
Fri Jun 21 18:32:33 UTC 2019
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?).
More information about the Digitalmars-d-learn
mailing list