www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - immutability and constness

reply "Minas Mina" <minas_mina1990 hotmail.co.uk> writes:
I'm fairly new to D, but I have been using C++ for a while now 
(about 5 years, selftaught).

 From what I have learned, const in C++ is inconsistent.

For example:

int main()
{
    const int x = 0; // could be placed in ROM because it's known 
at compile time
}

void f(int x)
{
   const int y = x * x; // logical const
   // blah blah blah
}

const can be also cast away (const_cast if I remember correctly - 
I wasn't doing it often :) ) and mess everything.

 From what I understand, immutable in D is truly immutable. But I 
have seen some code here on the forum that casts aways 
immutability... (Is that even defined behaviour? What if that 
thing was in ROM?)

D has const as well... This is were it becomes a bit tricky for 
me. To be honest, I haven't got the book about D - it should(does 
it?) have information about that.

Can someone explain what are D's goal about immutable, const - 
and why it is bitwise const and not logical? How does it benefit 
programs? I have read something about that in the "general 
discussions" forum but posted the question here because it's for 
me (and others) to learn about that.

Thanks.
Jul 11 2012
next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Thu, Jul 12, 2012 at 02:14:30AM +0200, Minas Mina wrote:
[...]
 D has const as well... This is were it becomes a bit tricky for me.
 To be honest, I haven't got the book about D - it should(does it?)
 have information about that.

You should get the book, it explains all the basic concepts (and some advanced ones too). Basically the way it works is like this: mutable (unqualified) and immutable can both implicitly convert to const. So it's sorta like the hierarchy: const / \ mutable immutable It's OK to convert immutable to const because const promises never to touch the data. It's also OK to convert mutable to const, because, well, not changing mutable data is OK too. In some cases, you can convert immutable to mutable (if the object has value semantics transitively to all its subparts), but as a rule, you can't. Immutable is bit-wise unchangeable, no matter what.
 Can someone explain what are D's goal about immutable, const - and why
 it is bitwise const and not logical?

Mainly because logical const is not enforceable. Logical const means the object does not _visibly_ change, but some hidden internal state may be changing wildly. Visible, of course, w.r.t. to non-private code that uses the object. But what constitutes a visible change? For example, if I have a class: class C { int x, y; int getX() { return x; } void doStuff() { if (y++ > 100000) x++; } } For the first 99999 times you call doStuff, no visible change is made, but the next time you call it, a visible change happens. So it is not logically const. But how would the compiler be able to tell? There could be very complex code inside doStuff(), and the compiler would essentially have to solve the halting problem (an unsolvable problem) in order to know whether doStuff() is logically const. So the only way logical const can work is to make it const "by convention", that is, you write void doStuff() const{}, but it's up to you the programmer to ensure that it's actually const, since the compiler has no way to verify it. This is bad, because programmers are human, and humans make mistakes. So the const qualifier becomes useless, it's just an annotation, with no real guarantees. The only way to make const enforceable is to make it bitwise const.
 How does it benefit programs? I have read something about that in the
 "general discussions" forum but posted the question here because it's
 for me (and others) to learn about that.

Immutable is a strong guarantee the data will never, ever change. So it's safe to perform a whole range of optimizations, like allowing multiple threads to read the data without any locks (since it doesn't change, there is no chance of race conditions), common subexpression elimination (reading the data multiple times across function calls is the same as reading it once: the function call cannot change the data because it's immutable). The compiler can put this data in ROM, where the data physically can't change -- and will be able to verify there is no attempt to change the data in the code. The problem is, immutable is very restrictive, and hard to work with (you can't change anything, you have to make a copy and change that). So if you have some immutable data and some mutable data, you can't use the same code to read them (because you can't convert between them). So a common ground is introduced: const. Const means whoever has the const object can't change anything, but somebody somewhere else may change it. So mutable can convert to const, obviously, but so can immutable (since in the case of immutable, _nobody_ can change it, so the fact that the current code that handles the const data won't change it either means that the data remains immutable). The advantage of this is that now you can have the same code process both mutable and immutable data. It's OK since you only ever read the data. T -- Spaghetti code may be tangly, but lasagna code is just cheesy.
Jul 11 2012
parent =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
On 07/12/2012 07:13 AM, Minas wrote:
 Thanks a lot!
 So what is the problem with (logical)const? Is it needed that
 much? And why some methods (toString(), toHash()) HAVE to be
 const? I mean, what's the problem if they aren't?

Here is the problem: class C {} // Good foo: Takes as const(C) because it is not going to // change the object void foo(const(C) c) { c.toString(); /* * With dmd 2.059: * * Error: function object.Object.toString() is not * callable using argument types () const */ } void main() {} Notes: 1) The current attempt of making toString() and friends const would fix that problem. 2) It brings a limitation: What job does the base class have constraining the derived class's toString()? What if the derived really needed to mutate the object in toString()? (That is why I almost never make abstract member functions 'const' in my C++ coding. Base can't know mutability needs of the derived.) 3) [This warrants another thread or even an article.] You may argue that foo() API is problematic because despite the sensible const(C), which nicely binds to mutable and immutable, it cannot be forwarded efficiently to another function that takes an immutable: class C {} void foo(const(C) c) { bar(c); /* Error: function deneme.bar (immutable(C) c) is not * callable using argument types (const(C)) */ } void bar(immutable(C) c) {} void main() {} But what if the c that foo() took was immutable to begin with? foo()'s const(C) parameter erases that information. To unerase, it must be a template and its parameter must be smart to copy if the original object was mutable: // Warning: Very raw idea! void foo(T)(CopyableOrForwardableConst!C c) { bar(c); // makes an immutable copy if necessary, zar(c); // or not, depending on // whether original object c is mutable or immutable } Ali -- D Programming Language Tutorial: http://ddili.org/ders/d.en/index.html
Jul 12 2012
prev sibling next sibling parent "Chris NS" <ibisbasenji gmail.com> writes:
On Thursday, 12 July 2012 at 00:37:22 UTC, H. S. Teoh wrote:
 On Thu, Jul 12, 2012 at 02:14:30AM +0200, Minas Mina wrote:
 [...]
 ********** wall o' text **********


You forgot about their quantum uncertainty loving transvestite cousin: inout.
Jul 11 2012
prev sibling next sibling parent "Minas" <minas_mina1990 hotmail.co.uk> writes:
Thanks a lot!
So what is the problem with (logical)const? Is it needed that
much? And why some methods (toString(), toHash()) HAVE to be
const? I mean, what's the problem if they aren't?
Jul 12 2012
prev sibling parent "Minas Mina" <minas_mina1990 hotmail.co.uk> writes:
On Thursday, 12 July 2012 at 14:42:17 UTC, Ali Çehreli wrote:
 On 07/12/2012 07:13 AM, Minas wrote:
 Thanks a lot!
 So what is the problem with (logical)const? Is it needed that
 much? And why some methods (toString(), toHash()) HAVE to be
 const? I mean, what's the problem if they aren't?

Here is the problem: ....

 (That is why I almost never make abstract member functions 
 'const' in my C++ coding. Base can't know mutability needs of 
 the derived.)
 Ali

I see. Just because toString() in object is const, so must be in the derived classes thus limiting them. So now I understand the solution that Andrei is considering (removing those from object). I think this is a good thing to do, those functions don't belong there (I have had some experience with Java's equals(Object o). All I say is that it sucks.) In C++, I would use a templated function. If the type didn't provide those methods it would be a compile error. Nice and clean :) Thanks for the reply :)
Jul 12 2012