On Thursday, 18 February 2016 at 14:58:00 UTC, ixid wrote:
On Thursday, 18 February 2016 at 11:57:59 UTC, Jonathan M Davis
wrote:
The problem that folks frequently want to be able to solve that
they simply cannot solve >with D's const (and headconst
wouldn't help) is that they want to be able to pass an object
to a function that takes it as const or return a const object
from a const member function >and have stuff like reference
counting, caching, mutexes, etc. work - stuff that has to be
part of the object (at least in pure code) but which isn't
part of its logical state and >cannot be const.
Is it not possible to have two objects, the data and the
information about the data? It seems like a mistake to try to
treat metadata as the data. I just ask out of interest as I
lack the experience to have a meaningful view, the category
confusion between data and metadata seems like a path to
excessive complexity.
To some extent yes, to some extent no. Lets' say that we have a
RefCounted wrapper which does reference counting, e.g.
RefCounted!Foo foo;
The ref count is in RefCounted, not Foo, so you can do something
like
RefCounted!(const Foo) foo;
and the ref-count will still work, but if you do
const RefCounted!Foo foo;
or
const RefCounted!(const Foo) foo;
RefCounted can't actually mutate its ref-count anymore, even if
it's copied, because the copy would have a const reference to the
ref-count, because the copy was made from a const RefCounted!Foo
and not a RefCounted!Foo. So, it really only helps you for one
level of const. The only way to get around the problem would be
if the ref-count were in a global or static variable somewhere,
in which case, it can't be used in pure functions, which is
definitely negative.
Similarly, what do you do with cached data? In C++, cached data
would normally go in a mutable member variable so that the
calculation could be saved, even if a const function called it -
but since the calculation was cached and wouldn't change, the
const function would still be logically const. The only way to do
that in D is to either have the cache set only in mutable
functions or to make the function not pure and put the data out
in a global or static variable somewhere, which again, is
definitely bad.
Similarly, mutexes really should be associated with the data that
they're protecting, which often means being passed around with
the object that contains that data. And that won't work with
const. You'd once again be forced to put the mutex it a global or
static variable and forego pure. synchronized works around this
for some cases by having the type system put the data somewhere
that's not treated as const, but it's a special case added by the
language. It wouldn't work at all with explicit mutexes that
didn't have special language support like that.
In C++, these sorts of problems are solved by marking the
ref-count, cached variable, mutex, etc. as mutable. Then that
variable can be mutated in order to do what it needs to do even
though it's being used in a context where the object that it's in
is const. Obviously, that can be abused by marking everything as
mutable and effectively making const meaningless (which is part
of why Walter isn't a big fan of C++'s const), but in practice,
that doesn't normally happen, and without mutable or casting away
const and mutating (neither of which are valid D), you just can't
do those things with const - especially when you add pure into
the mix.
- Jonathan M Davis