Thread overview
const confusion
May 31, 2009
Witold Baryluk
Jun 01, 2009
Witold Baryluk
Jun 23, 2009
Witold Baryluk
May 31, 2009
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 = a_;
		b = null;
	}
	this(int a_, in cord b_) {
		a = a_;
		b = b_;
	}
	C m() const { // line 16
	    return b;
	}
}

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

So I have class C which all methods are "const". So it is completly immutable 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 somone 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 const(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 const(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 = new C(1);
	const(C) c2 = new C(2, c1);
	c2 = 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 = new C(1);
	const(C) c2 = new C(2, c1);
	auto c3 = 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 = new C(1);
	const(C) c2 = new C(2, c1);
	while (c2 !is null) {
		c2 = 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 references of local variables?


-- 
Witold Baryluk <baryluk@smp.if.uj.edu.pl>


May 31, 2009
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 {
>        const int a;
>        const C b;
>
>        this(int a_) {
>                a = a_;
>                b = null;
>        }
>        this(int a_, in cord b_) {
>                a = a_;
>                b = b_;
>        }
>        C m() const { // line 16
>            return b;
>        }
> }
>
> void main() {
>        C c1 = new C(1);
>        C c2 = new C(2, c1);
>        c2 = c2.m(); // 23
> }
>
> So I have class C which all methods are "const". So it is completly immutable 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 somone 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 const(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 const(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 = new C(1);
>        const(C) c2 = new C(2, c1);
>        c2 = 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 = new C(1);
>        const(C) c2 = new C(2, c1);
>        auto c3 = 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 = new C(1);
>        const(C) c2 = new C(2, c1);
>        while (c2 !is null) {
>                c2 = 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 references of local variables?

Rebindable.

http://www.digitalmars.com/d/2.0/phobos/std_typecons.html#Rebindable
June 01, 2009
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)?



June 01, 2009
On Mon, Jun 1, 2009 at 4:01 PM, 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)?

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) :\
June 01, 2009
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
June 23, 2009
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