www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - const confusion

reply Witold Baryluk <baryluk smp.if.uj.edu.pl> writes:
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable

Hi,

i haven't been here for so long time. So hi all. I'm back again.

I was considering myself as hardcore D hacker, but
in one point I fail completly. Constness.

Consider this simple code:

module ct;

class C {
	const int a;
	const C b;

	this(int a_) {
		a =3D a_;
		b =3D null;
	}
	this(int a_, in cord b_) {
		a =3D a_;
		b =3D b_;
	}
	C m() const { // line 16
	    return b;
	}
}

void main() {
	C c1 =3D new C(1);
	C c2 =3D new C(2, c1);
	c2 =3D c2.m(); // 23
}

So I have class C which all methods are "const". So it is completly immutab=
le object,
one can say.

The problem is with line 16, I define function m which is const (because it
doesn't change anything). But i'm returing some part of it, so actually som=
one
else can change it hypotetically

Actually it is impossible for two reasons:
  * all fields are const,
  * there is no non-consts methods beside constructors.

Of course compiler says here: (dmd 2.028)
ct.d(16): Error: cannot implicitly convert expression (this.b) of type cons=
t(C) to ct.C

 =20

so we change line 16 to:
const(C) m() const { // line 16

And I can agree that this is correct solution.

but then there is another problem, now in main() function:
ct.d(23): Error: cannot implicitly convert expression (c2.m()) of type cons=
t(C) to ct.C

So now, i need to change all occurence of C to const(C), ok no problem:
void main() {
	const(C) c1 =3D new C(1);
	const(C) c2 =3D new C(2, c1);
	c2 =3D c2.m();  // line 23
}

Ok, nice. I can create some alias so it will be easier to use, also.

But now is another problem:
ct.d(23): Error: variable ct.main.c2 cannot modify const


But I wan't to have possibility to change local reference to this class!
Shouldn't const be about content of object?

Ok, go and just create new variable:
void main() {
	const(C) c1 =3D new C(1);
	const(C) c2 =3D new C(2, c1);
	auto c3 =3D c2.m();  // line 23
}

compiler now will be happy.

Ok, it is good for functional style of programing, but consider this:

void main() {
	const(C) c1 =3D new C(1);
	const(C) c2 =3D new C(2, c1);
	while (c2 !is null) {
		c2 =3D c2.m();  // line 23
	}
	writefln(c2.a);
}


So now i need to write tail recursive version of this loop, because compiler
doen't allow me to reuse variable.

I can write tail recursive loop, but it's:
  * not so efficient
  * users of my library are not familiary with functional programing
  * it's unnatural.

Horiblle.

How to ensure constness of data, and still have possibility of changing ref=
erences of local variables?


--=20
Witold Baryluk <baryluk smp.if.uj.edu.pl>
May 31 2009
parent reply Jarrett Billingsley <jarrett.billingsley gmail.com> writes:
On Sun, May 31, 2009 at 3:26 PM, Witold Baryluk
<baryluk smp.if.uj.edu.pl> wrote:
 Hi,

 i haven't been here for so long time. So hi all. I'm back again.

 I was considering myself as hardcore D hacker, but
 in one point I fail completly. Constness.

 Consider this simple code:

 module ct;

 class C {
 =A0 =A0 =A0 =A0const int a;
 =A0 =A0 =A0 =A0const C b;

 =A0 =A0 =A0 =A0this(int a_) {
 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0a =3D a_;
 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0b =3D null;
 =A0 =A0 =A0 =A0}
 =A0 =A0 =A0 =A0this(int a_, in cord b_) {
 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0a =3D a_;
 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0b =3D b_;
 =A0 =A0 =A0 =A0}
 =A0 =A0 =A0 =A0C m() const { // line 16
 =A0 =A0 =A0 =A0 =A0 =A0return b;
 =A0 =A0 =A0 =A0}
 }

 void main() {
 =A0 =A0 =A0 =A0C c1 =3D new C(1);
 =A0 =A0 =A0 =A0C c2 =3D new C(2, c1);
 =A0 =A0 =A0 =A0c2 =3D c2.m(); // 23
 }

 So I have class C which all methods are "const". So it is completly immut=
able object,
 one can say.

 The problem is with line 16, I define function m which is const (because =
it
 doesn't change anything). But i'm returing some part of it, so actually s=
omone
 else can change it hypotetically

 Actually it is impossible for two reasons:
 =A0* all fields are const,
 =A0* there is no non-consts methods beside constructors.

 Of course compiler says here: (dmd 2.028)
 ct.d(16): Error: cannot implicitly convert expression (this.b) of type co=
nst(C) to ct.C
 so we change line 16 to:
 const(C) m() const { // line 16

 And I can agree that this is correct solution.

 but then there is another problem, now in main() function:
 ct.d(23): Error: cannot implicitly convert expression (c2.m()) of type co=
nst(C) to ct.C
 So now, i need to change all occurence of C to const(C), ok no problem:
 void main() {
 =A0 =A0 =A0 =A0const(C) c1 =3D new C(1);
 =A0 =A0 =A0 =A0const(C) c2 =3D new C(2, c1);
 =A0 =A0 =A0 =A0c2 =3D c2.m(); =A0// line 23
 }

 Ok, nice. I can create some alias so it will be easier to use, also.

 But now is another problem:
 ct.d(23): Error: variable ct.main.c2 cannot modify const


 But I wan't to have possibility to change local reference to this class!
 Shouldn't const be about content of object?

 Ok, go and just create new variable:
 void main() {
 =A0 =A0 =A0 =A0const(C) c1 =3D new C(1);
 =A0 =A0 =A0 =A0const(C) c2 =3D new C(2, c1);
 =A0 =A0 =A0 =A0auto c3 =3D c2.m(); =A0// line 23
 }

 compiler now will be happy.

 Ok, it is good for functional style of programing, but consider this:

 void main() {
 =A0 =A0 =A0 =A0const(C) c1 =3D new C(1);
 =A0 =A0 =A0 =A0const(C) c2 =3D new C(2, c1);
 =A0 =A0 =A0 =A0while (c2 !is null) {
 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0c2 =3D c2.m(); =A0// line 23
 =A0 =A0 =A0 =A0}
 =A0 =A0 =A0 =A0writefln(c2.a);
 }


 So now i need to write tail recursive version of this loop, because compi=
ler
 doen't allow me to reuse variable.

 I can write tail recursive loop, but it's:
 =A0* not so efficient
 =A0* users of my library are not familiary with functional programing
 =A0* it's unnatural.

 Horiblle.

 How to ensure constness of data, and still have possibility of changing r=
eferences of local variables? Rebindable. http://www.digitalmars.com/d/2.0/phobos/std_typecons.html#Rebindable
May 31 2009
parent reply Witold Baryluk <baryluk smp.if.uj.edu.pl> writes:
Dnia 2009-05-31, nie o godzinie 15:36 -0400, Jarrett Billingsley pisze:
 On Sun, May 31, 2009 at 3:26 PM, Witold Baryluk
 <baryluk smp.if.uj.edu.pl> wrote:
 Horrible.

 How to ensure constness of data, and still have possibility of changing
references of local variables?
Rebindable. http://www.digitalmars.com/d/2.0/phobos/std_typecons.html#Rebindable
Thanks. I was already thinking about implementing something like this. It is very thin, and probably doesn't eat even single byte more than original reference. So generally we need to cheat: union with const and non-const version + opAssign/opDot, and some hidden casts. If everybody is doing this, why not. Only one problem is that i need to make some wrappers for it: alias Rebindable!(C) CC; first try: auto c1 = CC(new C(1)); auto c2 = CC(new C(2, c1)); // oops doesn't work c2 = c2.b(); second try: auto c1 = CC(new C(1)); auto c2 = CC(new C(2, c1.opDot())); // ok, works c2 = c2.b(); define some function on original data: int something(in C c) { return c.a; } something(c2); // oops, doesn't work something(c2.opDot()); // ok, works So generally now i need to overload all my functions to support also Rebindable!(C), where I will unwrap object and call original function? The same with constructors. Can't be this done more simpler? As I remember there was something like opCast (for explicit casts)? Maybe Rebindable should have it casting to original type (with const)?
Jun 01 2009
next sibling parent Jarrett Billingsley <jarrett.billingsley gmail.com> writes:
On Mon, Jun 1, 2009 at 4:01 PM, Witold Baryluk <baryluk smp.if.uj.edu.pl> w=
rote:
 Dnia 2009-05-31, nie o godzinie 15:36 -0400, Jarrett Billingsley pisze:
 On Sun, May 31, 2009 at 3:26 PM, Witold Baryluk
 <baryluk smp.if.uj.edu.pl> wrote:
 Horrible.

 How to ensure constness of data, and still have possibility of changin=
g references of local variables?
 Rebindable.

 http://www.digitalmars.com/d/2.0/phobos/std_typecons.html#Rebindable
Thanks. I was already thinking about implementing something like this. It is very thin, and probably doesn't eat even single byte more than original reference. So generally we need to cheat: union with const and non-const version + opAssign/opDot, and some hidden casts. If everybody is doing this, why not. Only one problem is that i need to make some wrappers for it: alias Rebindable!(C) CC; first try: auto c1 =3D CC(new C(1)); auto c2 =3D CC(new C(2, c1)); // oops doesn't work c2 =3D c2.b(); second try: auto c1 =3D CC(new C(1)); auto c2 =3D CC(new C(2, c1.opDot())); // ok, works c2 =3D c2.b(); define some function on original data: int something(in C c) { =A0 =A0 =A0 =A0return c.a; } something(c2); // oops, doesn't work something(c2.opDot()); // ok, works So generally now i need to overload all my functions to support also Rebindable!(C), where I will unwrap object and call original function? The same with constructors. Can't be this done more simpler? As I remember there was something like opCast (for explicit casts)? Maybe Rebindable should have it casting to original type (with const)?
This seems like a perfect application for opImplicitCast, a feature that has been bandied about for years and which Andrei seems to have hinted at coming soon. Using implicit casts, you would be able to make a perfectly transparent wrapper type such as Rebindable. For now, you're stuck overloading on Rebindable!(T) :\
Jun 01 2009
prev sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Mon, 01 Jun 2009 16:01:04 -0400, Witold Baryluk  
<baryluk smp.if.uj.edu.pl> wrote:

 Dnia 2009-05-31, nie o godzinie 15:36 -0400, Jarrett Billingsley pisze:
 On Sun, May 31, 2009 at 3:26 PM, Witold Baryluk
 <baryluk smp.if.uj.edu.pl> wrote:
 Horrible.

 How to ensure constness of data, and still have possibility of  
changing references of local variables? Rebindable. http://www.digitalmars.com/d/2.0/phobos/std_typecons.html#Rebindable
Thanks. I was already thinking about implementing something like this. It is very thin, and probably doesn't eat even single byte more than original reference. So generally we need to cheat: union with const and non-const version + opAssign/opDot, and some hidden casts. If everybody is doing this, why not. Only one problem is that i need to make some wrappers for it: alias Rebindable!(C) CC; first try: auto c1 = CC(new C(1)); auto c2 = CC(new C(2, c1)); // oops doesn't work c2 = c2.b(); second try: auto c1 = CC(new C(1)); auto c2 = CC(new C(2, c1.opDot())); // ok, works c2 = c2.b(); define some function on original data: int something(in C c) { return c.a; } something(c2); // oops, doesn't work something(c2.opDot()); // ok, works So generally now i need to overload all my functions to support also Rebindable!(C), where I will unwrap object and call original function? The same with constructors. Can't be this done more simpler? As I remember there was something like opCast (for explicit casts)? Maybe Rebindable should have it casting to original type (with const)?
You are running into limitations that are planned to be fixed. For example, rebindable probably shouldn't use opDot anymore... it should use alias this. With opDot, you don't have implicit casting back to the original type. But alias this provides that, not sure if aliasing a union member has been tested... Also, I believe Rebindable!(const C) is what you really want (I've argued in the past that Rebindable should just assume that it's type should be const). Rebindable!(T) is just an alias to T if T is not const, which is IMO absolutely useless. Another thing I just noticed, which probably should be fixed, there is an alias for get which gets the original item. get's a pretty common member name, I don't think it should be overridden by Rebindable. In fact, I think rebindable needs almost a rewrite with the recent developments of D2. The goal of Rebindable is to transparently implement the sort of "tail-const" behavior you want without any of the pain you are currently experiencing. If it doesn't work seamlessly (except for where you wish to explicitly define "this is a rebindable reference"), then it's not finished. Andrei? -Steve
Jun 01 2009
parent Witold Baryluk <baryluk smp.if.uj.edu.pl> writes:
Dnia 2009-06-01, pon o godzinie 16:44 -0400, Steven Schveighoffer pisze:
 On Mon, 01 Jun 2009 16:01:04 -0400, Witold Baryluk  
 <baryluk smp.if.uj.edu.pl> wrote:
 
 Dnia 2009-05-31, nie o godzinie 15:36 -0400, Jarrett Billingsley pisze:
 On Sun, May 31, 2009 at 3:26 PM, Witold Baryluk
 <baryluk smp.if.uj.edu.pl> wrote:
 Horrible.

 How to ensure constness of data, and still have possibility of  
changing references of local variables? Rebindable. http://www.digitalmars.com/d/2.0/phobos/std_typecons.html#Rebindable
Thanks. I was already thinking about implementing something like this. It is very thin, and probably doesn't eat even single byte more than original reference. So generally we need to cheat: union with const and non-const version + opAssign/opDot, and some hidden casts. If everybody is doing this, why not. Only one problem is that i need to make some wrappers for it: alias Rebindable!(C) CC; first try: auto c1 = CC(new C(1)); auto c2 = CC(new C(2, c1)); // oops doesn't work c2 = c2.b(); second try: auto c1 = CC(new C(1)); auto c2 = CC(new C(2, c1.opDot())); // ok, works c2 = c2.b(); define some function on original data: int something(in C c) { return c.a; } something(c2); // oops, doesn't work something(c2.opDot()); // ok, works So generally now i need to overload all my functions to support also Rebindable!(C), where I will unwrap object and call original function? The same with constructors. Can't be this done more simpler? As I remember there was something like opCast (for explicit casts)? Maybe Rebindable should have it casting to original type (with const)?
You are running into limitations that are planned to be fixed. For example, rebindable probably shouldn't use opDot anymore... it should use alias this. With opDot, you don't have implicit casting back to the original type. But alias this provides that, not sure if aliasing a union member has been tested... Also, I believe Rebindable!(const C) is what you really want (I've argued in the past that Rebindable should just assume that it's type should be const). Rebindable!(T) is just an alias to T if T is not const, which is IMO absolutely useless. Another thing I just noticed, which probably should be fixed, there is an alias for get which gets the original item. get's a pretty common member name, I don't think it should be overridden by Rebindable. In fact, I think rebindable needs almost a rewrite with the recent developments of D2. The goal of Rebindable is to transparently implement the sort of "tail-const" behavior you want without any of the pain you are currently experiencing. If it doesn't work seamlessly (except for where you wish to explicitly define "this is a rebindable reference"), then it's not finished. Andrei?
I have curently something like this in my implementation of rebindable. //alias Rebindable!(const C) CC; // todo: opAssign(Rebindable!(T)) is missing so c4 = c3 // todo: T opCast() is missing for explicit cast(T) // todo: opdot needs const // because of this here is own Rebindable template RBMixin(T, RetT) { static if (is(T X == const(U), U) || is(T X == invariant(U), U)) { private union { T original; U stripped; } RetT opAssign(T another) { stripped = cast(U)another; return this; } RetT opAssign(RetT another) { stripped = another.stripped; return this; } static RetT opCall(T initializer) { //return RetT(initializer); RetT result; result = initializer; return result; } T opDot() const { return original; } static if (!is(T.opCast)) { T opCast() { return original; } } } } template RB(T) { static if (!is(T X == const(U), U) && !is(T X == invariant(U), U)) { alias T RB; } else { struct RB { mixin RBMixin!(T, RB); alias original get; // legacy get } } } Then i can just use RB!(const!(mytype)), or mixin it into own struct. I think i will rewrite it to use "alias this", and remove some other limitation What you think?
 -Steve
Jun 23 2009