www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - How to break const

reply "Mehrdad" <wfunction hotmail.com> writes:
Is it just me, or did I subvert the type system here?


import std.stdio;

struct Const
{
	this(void delegate() increment)
	{ this.increment = increment; }
	int a;
	void delegate() increment;
	void oops() const { this.increment(); }
}

void main()
{
	Const c;
	c = Const({ c.a++; });
	writeln(c.a);
	c.oops();
	writeln(c.a);
}
Jun 17 2012
next sibling parent reply Matthias Walter <xammy xammy.homelinux.net> writes:
On 06/18/2012 07:36 AM, Mehrdad wrote:
 Is it just me, or did I subvert the type system here?
 
 
 import std.stdio;
 
 struct Const
 {
     this(void delegate() increment)
     { this.increment = increment; }
     int a;
     void delegate() increment;
     void oops() const { this.increment(); }
 }
 
 void main()
 {
     Const c;
     c = Const({ c.a++; });
     writeln(c.a);
     c.oops();
     writeln(c.a);
 }
 

I don't think so. When calling oops you have two references to the object c: - The this-pointer of the object itself which is not allowed to change the object in the const-call. - The reference from within main which is allowed to change it and can be reached via the frame pointer of the delegate. I see this as perfectly valid code. Of course, opinions may differ here. Matthias
Jun 17 2012
next sibling parent reply deadalnix <deadalnix gmail.com> writes:
Le 18/06/2012 07:59, Matthias Walter a écrit :
 On 06/18/2012 07:36 AM, Mehrdad wrote:
 Is it just me, or did I subvert the type system here?


 import std.stdio;

 struct Const
 {
      this(void delegate() increment)
      { this.increment = increment; }
      int a;
      void delegate() increment;
      void oops() const { this.increment(); }
 }

 void main()
 {
      Const c;
      c = Const({ c.a++; });
      writeln(c.a);
      c.oops();
      writeln(c.a);
 }

I don't think so. When calling oops you have two references to the object c: - The this-pointer of the object itself which is not allowed to change the object in the const-call. - The reference from within main which is allowed to change it and can be reached via the frame pointer of the delegate. I see this as perfectly valid code. Of course, opinions may differ here. Matthias

The hidden parameter of the delegate is stored in c. This hidden parameter must be qualified with const when c is made const, for transitivity. However, it isn't. In short : - c is made const - the frame pointer is stored in c - the frame pointer must be made const for transitivity. => The type system is broken. You'll find many examples of this behavior with delegates.
Jun 18 2012
next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 06/18/2012 02:45 PM, deadalnix wrote:
 Le 18/06/2012 07:59, Matthias Walter a écrit :
 On 06/18/2012 07:36 AM, Mehrdad wrote:
 Is it just me, or did I subvert the type system here?


 import std.stdio;

 struct Const
 {
      this(void delegate() increment)
      { this.increment = increment; }
      int a;
      void delegate() increment;
      void oops() const { this.increment(); }
 }

 void main()
 {
      Const c;
      c = Const({ c.a++; });
      writeln(c.a);
      c.oops();
      writeln(c.a);
 }

I don't think so. When calling oops you have two references to the object c: - The this-pointer of the object itself which is not allowed to change the object in the const-call. - The reference from within main which is allowed to change it and can be reached via the frame pointer of the delegate. I see this as perfectly valid code. Of course, opinions may differ here. Matthias

The hidden parameter of the delegate is stored in c. This hidden parameter must be qualified with const when c is made const, for transitivity. However, it isn't. In short : - c is made const - the frame pointer is stored in c - the frame pointer must be made const for transitivity. => The type system is broken. You'll find many examples of this behavior with delegates.

The type system is not broken. You cannot modify an immutable object using this behaviour. Delegates are type checked once and for all when they are declared. Transitive const does not necessarily need to apply to their context pointers. I think it is useful to reason about delegates at a higher abstraction level than function pointer and context pointer.
Jun 18 2012
parent reply deadalnix <deadalnix gmail.com> writes:
Le 18/06/2012 15:03, Timon Gehr a écrit :
 On 06/18/2012 02:45 PM, deadalnix wrote:
 Le 18/06/2012 07:59, Matthias Walter a écrit :
 On 06/18/2012 07:36 AM, Mehrdad wrote:
 Is it just me, or did I subvert the type system here?


 import std.stdio;

 struct Const
 {
 this(void delegate() increment)
 { this.increment = increment; }
 int a;
 void delegate() increment;
 void oops() const { this.increment(); }
 }

 void main()
 {
 Const c;
 c = Const({ c.a++; });
 writeln(c.a);
 c.oops();
 writeln(c.a);
 }

I don't think so. When calling oops you have two references to the object c: - The this-pointer of the object itself which is not allowed to change the object in the const-call. - The reference from within main which is allowed to change it and can be reached via the frame pointer of the delegate. I see this as perfectly valid code. Of course, opinions may differ here. Matthias

The hidden parameter of the delegate is stored in c. This hidden parameter must be qualified with const when c is made const, for transitivity. However, it isn't. In short : - c is made const - the frame pointer is stored in c - the frame pointer must be made const for transitivity. => The type system is broken. You'll find many examples of this behavior with delegates.

The type system is not broken. You cannot modify an immutable object using this behaviour. Delegates are type checked once and for all when they are declared. Transitive const does not necessarily need to apply to their context pointers. I think it is useful to reason about delegates at a higher abstraction level than function pointer and context pointer.

You cannot use this to modify immutable data, granted. But you can use that to break transitivity (ie, make immutable data refers mutable datas). It have many hidden traps. For instance, data that isn't shared or immutable can be shared anyway between thread throw delegates. Not being able to ensure transitivity with delegate break all benefit of transitivity. However, delegate is a special beast, because it cannot be safely « constified », but can be safely « unconstified ».
Jun 18 2012
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 06/18/2012 04:33 PM, deadalnix wrote:
 Le 18/06/2012 15:03, Timon Gehr a écrit :
 On 06/18/2012 02:45 PM, deadalnix wrote:
 Le 18/06/2012 07:59, Matthias Walter a écrit :
 On 06/18/2012 07:36 AM, Mehrdad wrote:
 Is it just me, or did I subvert the type system here?


 import std.stdio;

 struct Const
 {
 this(void delegate() increment)
 { this.increment = increment; }
 int a;
 void delegate() increment;
 void oops() const { this.increment(); }
 }

 void main()
 {
 Const c;
 c = Const({ c.a++; });
 writeln(c.a);
 c.oops();
 writeln(c.a);
 }

I don't think so. When calling oops you have two references to the object c: - The this-pointer of the object itself which is not allowed to change the object in the const-call. - The reference from within main which is allowed to change it and can be reached via the frame pointer of the delegate. I see this as perfectly valid code. Of course, opinions may differ here. Matthias

The hidden parameter of the delegate is stored in c. This hidden parameter must be qualified with const when c is made const, for transitivity. However, it isn't. In short : - c is made const - the frame pointer is stored in c - the frame pointer must be made const for transitivity. => The type system is broken. You'll find many examples of this behavior with delegates.

The type system is not broken. You cannot modify an immutable object using this behaviour. Delegates are type checked once and for all when they are declared. Transitive const does not necessarily need to apply to their context pointers. I think it is useful to reason about delegates at a higher abstraction level than function pointer and context pointer.

You cannot use this to modify immutable data, granted. But you can use that to break transitivity (ie, make immutable data refers mutable datas). It have many hidden traps. For instance, data that isn't shared or immutable can be shared anyway between thread throw delegates. Not being able to ensure transitivity with delegate break all benefit of transitivity. However, delegate is a special beast, because it cannot be safely « constified », but can be safely « unconstified ».

The issue is that immutable objects can contain impure delegates as fields. This should probably be banned.
Jun 18 2012
prev sibling next sibling parent reply deadalnix <deadalnix gmail.com> writes:
Le 18/06/2012 16:28, Artur Skawina a écrit :
 It's fine, if you view a delegate as opaque.

No it isn't. You cannot ensure transitivity anywhere. This have obvious, and severe drawback for concurrent programing (implicit sharing is back) and GC performances (GC can eb crazy fast when it come to transitive immutable data, see OCaml's GC performances for instance).
 'this' being const does not preclude accessing the object that 'this'
 points to via another, mutable, reference.

 Consider the alternative - you'd have to forbid storing any delegate
 with a non-const non-value argument inside any object.

 And "breaking" const would then _still_ be possible and trivial.

No, and your example don't demonstrate that in any way. Transitivity is maintained in the example below, because g isn't a member of s, and if it were, then the example would break at compile time.
     import std.stdio;

     S*[const(S)*] g;

     struct S {
        int i;
        this(int i) { this.i = i; g[&this] =&this; }
        void f() const { g[&this].i=666; }
     }

     void main() {
        const s = S(42);
        writeln(s);
        s.f();
        writeln(s);
     }

 Yes, D's "pure" could help here, only it's misnamed (even if in
 this particular case that term would be fitting).

purity is another beast altogether.
Jun 18 2012
next sibling parent reply deadalnix <deadalnix gmail.com> writes:
Le 18/06/2012 16:44, Mehrdad a écrit :
 Interesting, making the delegate `pure' doesn't change anything either.

 So 'pure' doesn't let you "infer something just by looking at the code
 either", right?

It does ! It tell you that the function have no side effect, and that the function called with identical arguments will return identical results. pure will not ensure constness or immutability. const and immutable are made for that. The fact that D decouple purity and immutability is a very nice design decision and is explained nicely here : http://klickverbot.at/blog/2012/05/purity-in-d/
Jun 18 2012
next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 06/18/2012 04:55 PM, Mehrdad wrote:
 Identical calls giving identical results? What?


 import std.stdio;
 struct S
 {
           this(int a)
           {
                   this.a = a;
                   this.increment = { return this.a++; };
           }
           int a;
           int delegate() pure increment;
           auto oops() const { return this.increment(); }
 }
 void main()
 {
           auto c = immutable(S)(0);
           writeln(c.oops()); // 0
           writeln(c.oops()); // 1
           writeln(c.oops()); // 2
           writeln(c.oops()); // 3
           writeln(c.oops()); // 4
           writeln(c.oops()); // 5
 }

Now you have managed to break the type system. The underlying issue is unrelated to delegates though.
Jun 18 2012
next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 06/18/2012 05:15 PM, Mehrdad wrote:
 On Monday, 18 June 2012 at 15:11:00 UTC, Timon Gehr wrote:
 On 06/18/2012 04:55 PM, Mehrdad wrote:
 Identical calls giving identical results? What?


 import std.stdio;
 struct S
 {
          this(int a)
          {
                  this.a = a;
                  this.increment = { return this.a++; };
          }
          int a;
          int delegate() pure increment;
          auto oops() const { return this.increment(); }
 }
 void main()
 {
          auto c = immutable(S)(0);
          writeln(c.oops()); // 0
          writeln(c.oops()); // 1
          writeln(c.oops()); // 2
          writeln(c.oops()); // 3
          writeln(c.oops()); // 4
          writeln(c.oops()); // 5
 }

Now you have managed to break the type system. The underlying issue is unrelated to delegates though.

Yeah, I didn't mean to say it's a delegate issue either. That's why the title was saying "how to break _const_". Delegates were just a means to an end. :) So (**IMHO**) if that's really the case, we should really spend some time fixing the /design/ of const before the implementation...

This is mostly about the design of object initialisation.
 good idea or no?

Certainly.
Jun 18 2012
next sibling parent deadalnix <deadalnix gmail.com> writes:
Le 18/06/2012 17:28, Mehrdad a écrit :
 On Monday, 18 June 2012 at 15:24:31 UTC, Mehrdad wrote:
 On Monday, 18 June 2012 at 15:21:36 UTC, Timon Gehr wrote:
 So (**IMHO**) if that's really the case, we should really spend some
 time fixing the /design/ of const before the implementation...

This is mostly about the design of object initialisation.
 good idea or no?

Certainly.

My initial instinct would be to require a "const constructor" in order for an object to be const-able, but I'm not sure if that would work correctly or not..

Come to think of it, that would play REALLY nicely with 'scope' -- a reference to a non-const object can be escaped from a 'const constructor' if and only if the reference is scope! Bingo! Does that work??

Indeed, this should be scope for ctor (avoid partially initialized object in 3rd party code) /dtor (avoid resurrection, which is a real pain for any GC, and a very good way to ends up with alive object in invalid state).
Jun 18 2012
prev sibling next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 06/18/2012 05:28 PM, Mehrdad wrote:
 On Monday, 18 June 2012 at 15:24:31 UTC, Mehrdad wrote:
 On Monday, 18 June 2012 at 15:21:36 UTC, Timon Gehr wrote:
 So (**IMHO**) if that's really the case, we should really spend some
 time fixing the /design/ of const before the implementation...

This is mostly about the design of object initialisation.
 good idea or no?

Certainly.

My initial instinct would be to require a "const constructor" in order for an object to be const-able, but I'm not sure if that would work correctly or not..

Come to think of it, that would play REALLY nicely with 'scope' -- a reference to a non-const object can be escaped from a 'const constructor' if and only if the reference is scope! Bingo! Does that work??

Yes, but it requires proper enforcement of 'scope'. - which is undecidable/must be done conservatively - code would have to become 'scope correct'
Jun 18 2012
parent deadalnix <deadalnix gmail.com> writes:
Le 18/06/2012 17:50, Timon Gehr a écrit :
 On 06/18/2012 05:28 PM, Mehrdad wrote:
 On Monday, 18 June 2012 at 15:24:31 UTC, Mehrdad wrote:
 On Monday, 18 June 2012 at 15:21:36 UTC, Timon Gehr wrote:
 So (**IMHO**) if that's really the case, we should really spend some
 time fixing the /design/ of const before the implementation...

This is mostly about the design of object initialisation.
 good idea or no?

Certainly.

My initial instinct would be to require a "const constructor" in order for an object to be const-able, but I'm not sure if that would work correctly or not..

Come to think of it, that would play REALLY nicely with 'scope' -- a reference to a non-const object can be escaped from a 'const constructor' if and only if the reference is scope! Bingo! Does that work??

Yes, but it requires proper enforcement of 'scope'. - which is undecidable/must be done conservatively - code would have to become 'scope correct'

Come on, what can't we infers qualifiers ?
Jun 18 2012
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 6/18/12 10:24 AM, Mehrdad wrote:
 On Monday, 18 June 2012 at 15:21:36 UTC, Timon Gehr wrote:
 So (**IMHO**) if that's really the case, we should really spend some
 time fixing the /design/ of const before the implementation...

This is mostly about the design of object initialisation.
 good idea or no?

Certainly.

My initial instinct would be to require a "const constructor" in order for an object to be const-able, but I'm not sure if that would work correctly or not..

The constructor of a const or immutable object progressively evolves the object from a raw state to a fixed, constructed state. During the process the object has not discernable type. This is what needs to be fixed. Andrei
Jun 18 2012
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 6/18/12 10:15 AM, Mehrdad wrote:
 So (**IMHO**) if that's really the case, we should really spend some
 time fixing the /design/ of const before the implementation... good idea
 or no?

It's the implementation (not design) of constructors typechecking that's the problem. Andrei
Jun 18 2012
prev sibling parent reply deadalnix <deadalnix gmail.com> writes:
Le 18/06/2012 16:55, Mehrdad a écrit :
 On Monday, 18 June 2012 at 14:48:37 UTC, deadalnix wrote:
 Le 18/06/2012 16:44, Mehrdad a écrit :
 Interesting, making the delegate `pure' doesn't change anything either.

 So 'pure' doesn't let you "infer something just by looking at the code
 either", right?

It does ! It tell you that the function have no side effect, and that the function called with identical arguments will return identical results. pure will not ensure constness or immutability. const and immutable are made for that. The fact that D decouple purity and immutability is a very nice design decision and is explained nicely here : http://klickverbot.at/blog/2012/05/purity-in-d/

Identical calls giving identical results? What? import std.stdio; struct S { this(int a) { this.a = a; this.increment = { return this.a++; }; } int a; int delegate() pure increment; auto oops() const { return this.increment(); } } void main() { auto c = immutable(S)(0); writeln(c.oops()); // 0 writeln(c.oops()); // 1 writeln(c.oops()); // 2 writeln(c.oops()); // 3 writeln(c.oops()); // 4 writeln(c.oops()); // 5 }

They are not call with the same parameters. The hidden parameter have changed (I know this is tricky).
Jun 18 2012
next sibling parent reply deadalnix <deadalnix gmail.com> writes:
Le 18/06/2012 17:16, Mehrdad a écrit :
 On Monday, 18 June 2012 at 15:13:33 UTC, deadalnix wrote:
 Identical calls giving identical results? What?


 import std.stdio;
 struct S
 {
 this(int a)
 {
 this.a = a;
 this.increment = { return this.a++; };
 }
 int a;
 int delegate() pure increment;
 auto oops() const { return this.increment(); }
 }
 void main()
 {
 auto c = immutable(S)(0);
 writeln(c.oops()); // 0
 writeln(c.oops()); // 1
 writeln(c.oops()); // 2
 writeln(c.oops()); // 3
 writeln(c.oops()); // 4
 writeln(c.oops()); // 5
 }

They are not call with the same parameters. The hidden parameter have changed (I know this is tricky).

Explain it however you want. The bottom line I'm getting at is, you can't re-order the calls, EVEN IF by "looking at them" you can tell they're pure safe nothrow const...

Yes, but this isn't a problem with pure here, this is a problem with const. The sample code should not compile.
Jun 18 2012
parent deadalnix <deadalnix gmail.com> writes:
Le 18/06/2012 17:20, Mehrdad a écrit :
 On Monday, 18 June 2012 at 15:19:26 UTC, deadalnix wrote:
 Yes, but this isn't a problem with pure here, this is a problem with
 const.

Hence the title...
 The sample code should not compile.

What should the error be? Is it correct in the language spec?

The specification lack on that point, because we are here facing a specification bug. Depending on how we choose to fix that, the error can be different. Fix the initializer to dissalow the way you initialize c is probably the way to go on that specific case. Andrei says that work have been done in that direction, but I'm not aware of where it is now. It also require a way to qualify the frame pointer; which isn't possible ATM.
Jun 18 2012
prev sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 06/18/2012 05:13 PM, deadalnix wrote:
 Le 18/06/2012 16:55, Mehrdad a écrit :
 On Monday, 18 June 2012 at 14:48:37 UTC, deadalnix wrote:
 Le 18/06/2012 16:44, Mehrdad a écrit :
 Interesting, making the delegate `pure' doesn't change anything either.

 So 'pure' doesn't let you "infer something just by looking at the code
 either", right?

It does ! It tell you that the function have no side effect, and that the function called with identical arguments will return identical results. pure will not ensure constness or immutability. const and immutable are made for that. The fact that D decouple purity and immutability is a very nice design decision and is explained nicely here : http://klickverbot.at/blog/2012/05/purity-in-d/

Identical calls giving identical results? What? import std.stdio; struct S { this(int a) { this.a = a; this.increment = { return this.a++; }; } int a; int delegate() pure increment; auto oops() const { return this.increment(); } } void main() { auto c = immutable(S)(0); writeln(c.oops()); // 0 writeln(c.oops()); // 1 writeln(c.oops()); // 2 writeln(c.oops()); // 3 writeln(c.oops()); // 4 writeln(c.oops()); // 5 }

They are not call with the same parameters. The hidden parameter have changed (I know this is tricky).

The whole problem is that the hidden parameter has changed. It is immutable!
Jun 18 2012
parent deadalnix <deadalnix gmail.com> writes:
Le 18/06/2012 17:23, Timon Gehr a écrit :
 On 06/18/2012 05:13 PM, deadalnix wrote:
 Le 18/06/2012 16:55, Mehrdad a écrit :
 On Monday, 18 June 2012 at 14:48:37 UTC, deadalnix wrote:
 Le 18/06/2012 16:44, Mehrdad a écrit :
 Interesting, making the delegate `pure' doesn't change anything
 either.

 So 'pure' doesn't let you "infer something just by looking at the code
 either", right?

It does ! It tell you that the function have no side effect, and that the function called with identical arguments will return identical results. pure will not ensure constness or immutability. const and immutable are made for that. The fact that D decouple purity and immutability is a very nice design decision and is explained nicely here : http://klickverbot.at/blog/2012/05/purity-in-d/

Identical calls giving identical results? What? import std.stdio; struct S { this(int a) { this.a = a; this.increment = { return this.a++; }; } int a; int delegate() pure increment; auto oops() const { return this.increment(); } } void main() { auto c = immutable(S)(0); writeln(c.oops()); // 0 writeln(c.oops()); // 1 writeln(c.oops()); // 2 writeln(c.oops()); // 3 writeln(c.oops()); // 4 writeln(c.oops()); // 5 }

They are not call with the same parameters. The hidden parameter have changed (I know this is tricky).

The whole problem is that the hidden parameter has changed. It is immutable!

As said, this is effectively a const/immutable issue. Not a purity issue. I'm not claiming that this code is fine, just explaining that the code is fine in regard to purity.
Jun 18 2012
prev sibling parent reply Don Clugston <dac nospam.com> writes:
On 18/06/12 17:00, Artur Skawina wrote:
 On 06/18/12 16:41, deadalnix wrote:
 Le 18/06/2012 16:28, Artur Skawina a écrit :
 It's fine, if you view a delegate as opaque.

No it isn't. You cannot ensure transitivity anywhere. This have obvious, and severe drawback for concurrent programing (implicit sharing is back) and GC performances (GC can eb crazy fast when it come to transitive immutable data, see OCaml's GC performances for instance).
 'this' being const does not preclude accessing the object that 'this'
 points to via another, mutable, reference.

 Consider the alternative - you'd have to forbid storing any delegate
 with a non-const non-value argument inside any object.


So how would you like to handle this? And, no, allowing only the cases that /can/ be statically checked is not ok - it would result in black magic - delegates would be accepted or not depending on the contents of the object (think templates and composition).
 And "breaking" const would then _still_ be possible and trivial.

No, and your example don't demonstrate that in any way. Transitivity is maintained in the example below, because g isn't a member of s, and if it were, then the example would break at compile time.

The word "breaking" is in quotes for a reason. Const is not an immutability guarantee. If you treat delegates as opaque then there's no practical difference between using them or accessing the data via another external reference.
 purity is another beast altogether.

D's "weak pure" can help; I just don't like the redefinition of the term "purity"; another name for "weak purity" would be better.

So would I. Can you think of one? It was the best name I could come up with, given that the 'pure' was the keyword. We want a word that means 'no hidden state'.
Jun 19 2012
next sibling parent reply travert phare.normalesup.org (Christophe Travert) writes:
Iain Buclaw , dans le message (digitalmars.D:170145), a crit:
 On 19 June 2012 09:18, Don Clugston <dac nospam.com> wrote:
 So would I. Can you think of one?
 It was the best name I could come up with, given that the 'pure' was the
 keyword.
 We want a word that means 'no hidden state'.

I thought that was what pure was for. :~) -- Iain Buclaw *(p < e ? p++ : p) = (c & 0x0f) + '0';

A delegate does have a frame pointer, and it's not that well hidden. If you want no such 'hidden state', you do not want a delegate, you want a function pointer. That means all delegates are weakly pure (until they have an immutable frame pointer qualifier). If you want that this 'hidden state' does not change, that is another story. pure for a delegate could mean that the frame pointer does not change, but then, pure methods wouldn't allow you to make pure delegates: struct S { int i; int foo() pure { return i; } } S s; int delegate() pure dg = &s.foo; // error if pure change meaning when applied to a delegate
Jun 19 2012
next sibling parent deadalnix <deadalnix gmail.com> writes:
Le 19/06/2012 12:49, Iain Buclaw a crit :
 So we have a few combinations then:

 pure  - as in weakly pure, guarantees not to change global state, but
 may alter it's own hidden state.

 pure nothrow - as in strongly pure, where is guaranteed not to have
 any side effects, so is suitable for constant folding / the usual
 optimisations for a function typically marked as __pure__ in C.

 pure const - similar to strongly pure, as is guaranteed not to be able
 to alter its own state (as it's const), but still may have side
 effects / throw an exception.


 Make much sense? :-)

You know that it doesn't make any sense, right ?
Jun 19 2012
prev sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 06/19/2012 12:49 PM, Iain Buclaw wrote:
 On 19 June 2012 10:47, Christophe Travert<travert phare.normalesup.org>  wrote:
 Iain Buclaw , dans le message (digitalmars.D:170145), a crit :
 On 19 June 2012 09:18, Don Clugston<dac nospam.com>  wrote:
 So would I. Can you think of one?
 It was the best name I could come up with, given that the 'pure' was the
 keyword.
 We want a word that means 'no hidden state'.

I thought that was what pure was for. :~) -- Iain Buclaw *(p< e ? p++ : p) = (c& 0x0f) + '0';

A delegate does have a frame pointer, and it's not that well hidden. If you want no such 'hidden state', you do not want a delegate, you want a function pointer. That means all delegates are weakly pure (until they have an immutable frame pointer qualifier). If you want that this 'hidden state' does not change, that is another story. pure for a delegate could mean that the frame pointer does not change, but then, pure methods wouldn't allow you to make pure delegates: struct S { int i; int foo() pure { return i; } } S s; int delegate() pure dg =&s.foo; // error if pure change meaning when applied to a delegate

So we have a few combinations then: pure - as in weakly pure, guarantees not to change global state, but may alter it's own hidden state. pure nothrow - as in strongly pure, where is guaranteed not to have any side effects, so is suitable for constant folding

This would need to be qualified 'pure immutable nothrow', otherwise it might change it's own hidden state.
 / the usual optimisations for a function typically marked as __pure__ in C.

Some of them can even be applied if it is just 'pure immutable '. loop invariant code motion, for instance.
 pure const - similar to strongly pure, as is guaranteed not to be able
 to alter its own state (as it's const), but still may have side
 effects / throw an exception.

Therefore, you think 'pure const' should be allowed to have side effects?
 Make much sense? :-)

Jun 19 2012
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 06/19/2012 01:24 PM, Iain Buclaw wrote:
 On 19 June 2012 12:04, Timon Gehr<timon.gehr gmx.ch>  wrote:
 On 06/19/2012 12:49 PM, Iain Buclaw wrote:
  ...
 pure const - similar to strongly pure, as is guaranteed not to be able
 to alter its own state (as it's const), but still may have side
 effects / throw an exception.

Therefore, you think 'pure const' should be allowed to have side effects?

Throwing exceptions is a bit of a side effect...

It is not. Any pure function can be turned into a pure nothrow function by making the thrown exception an 'out' parameter and updating all callers accordingly.
Jun 19 2012
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 06/19/2012 01:29 PM, Timon Gehr wrote:
 On 06/19/2012 01:24 PM, Iain Buclaw wrote:
 On 19 June 2012 12:04, Timon Gehr<timon.gehr gmx.ch> wrote:
 On 06/19/2012 12:49 PM, Iain Buclaw wrote:
 ...
 pure const - similar to strongly pure, as is guaranteed not to be able
 to alter its own state (as it's const), but still may have side
 effects / throw an exception.

Therefore, you think 'pure const' should be allowed to have side effects?

Throwing exceptions is a bit of a side effect...

It is not. Any pure function can be turned into a pure nothrow function by making the thrown exception an 'out' parameter and updating all callers accordingly.

Except 'main'. So throwing exceptions is a bit of a side effect after all.
Jun 19 2012
prev sibling next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 06/19/2012 10:18 AM, Don Clugston wrote:
 On 18/06/12 17:00, Artur Skawina wrote:
 ...

 D's "weak pure" can help; I just don't like the redefinition of the term
 "purity"; another name for "weak purity" would be better.

So would I. Can you think of one? It was the best name I could come up with, given that the 'pure' was the keyword. We want a word that means 'no hidden state'.

nostatic stateless monadic
Jun 19 2012
prev sibling parent Don Clugston <dac nospam.com> writes:
On 19/06/12 11:02, Iain Buclaw wrote:
 On 19 June 2012 09:18, Don Clugston<dac nospam.com>  wrote:
 So would I. Can you think of one?
 It was the best name I could come up with, given that the 'pure' was the
 keyword.
 We want a word that means 'no hidden state'.

I thought that was what pure was for. :~)

This page is interesting. http://en.wikipedia.org/wiki/Pure_function There's never any hidden state, even in a weakly pure function. So it satisfies rule 1. A weakly pure function may modify anything it can reach through the parameters, though. Does this violate rule 2? I guess that if you define all mutable arguments of a weakly pure function as being 'pass by reference', then even 'weakly pure' is pure according to that definition. The page states that according to that definition, the expressions which call the pure function are not pure. But again in the terms of that page, D has an "Effect System" which allow us to prove that a function is pure even though it has impure expressions in it. Wikipedia isn't very authoritative though. So I don't know -- although D 'pure' is very different to what FP people call 'pure', I'm no longer certain that we're abusing the term. After all, even this function isn't pure in the traditional FP view: int foo(int n) pure { int r = n*n; r += n; return r; }
Jun 20 2012
prev sibling parent Artur Skawina <art.08.09 gmail.com> writes:
On 06/18/12 16:41, deadalnix wrote:
 Le 18/06/2012 16:28, Artur Skawina a écrit :
 It's fine, if you view a delegate as opaque.

No it isn't. You cannot ensure transitivity anywhere. This have obvious, and severe drawback for concurrent programing (implicit sharing is back) and GC performances (GC can eb crazy fast when it come to transitive immutable data, see OCaml's GC performances for instance).
 'this' being const does not preclude accessing the object that 'this'
 points to via another, mutable, reference.

 Consider the alternative - you'd have to forbid storing any delegate
 with a non-const non-value argument inside any object.


So how would you like to handle this? And, no, allowing only the cases that /can/ be statically checked is not ok - it would result in black magic - delegates would be accepted or not depending on the contents of the object (think templates and composition).
 And "breaking" const would then _still_ be possible and trivial.

No, and your example don't demonstrate that in any way. Transitivity is maintained in the example below, because g isn't a member of s, and if it were, then the example would break at compile time.

The word "breaking" is in quotes for a reason. Const is not an immutability guarantee. If you treat delegates as opaque then there's no practical difference between using them or accessing the data via another external reference.
 purity is another beast altogether.

D's "weak pure" can help; I just don't like the redefinition of the term "purity"; another name for "weak purity" would be better. artur
Jun 18 2012
prev sibling next sibling parent Artur Skawina <art.08.09 gmail.com> writes:
On 06/18/12 14:45, deadalnix wrote:
 Le 18/06/2012 07:59, Matthias Walter a écrit :
 On 06/18/2012 07:36 AM, Mehrdad wrote:
 Is it just me, or did I subvert the type system here?


 import std.stdio;

 struct Const
 {
      this(void delegate() increment)
      { this.increment = increment; }
      int a;
      void delegate() increment;
      void oops() const { this.increment(); }
 }

 void main()
 {
      Const c;
      c = Const({ c.a++; });
      writeln(c.a);
      c.oops();
      writeln(c.a);
 }

I don't think so. When calling oops you have two references to the object c: - The this-pointer of the object itself which is not allowed to change the object in the const-call. - The reference from within main which is allowed to change it and can be reached via the frame pointer of the delegate. I see this as perfectly valid code. Of course, opinions may differ here. Matthias

The hidden parameter of the delegate is stored in c. This hidden parameter must be qualified with const when c is made const, for transitivity. However, it isn't. In short : - c is made const - the frame pointer is stored in c - the frame pointer must be made const for transitivity. => The type system is broken. You'll find many examples of this behavior with delegates.

It's fine, if you view a delegate as opaque. 'this' being const does not preclude accessing the object that 'this' points to via another, mutable, reference. Consider the alternative - you'd have to forbid storing any delegate with a non-const non-value argument inside any object. And "breaking" const would then _still_ be possible and trivial. import std.stdio; S*[const(S)*] g; struct S { int i; this(int i) { this.i = i; g[&this] = &this; } void f() const { g[&this].i=666; } } void main() { const s = S(42); writeln(s); s.f(); writeln(s); } Yes, D's "pure" could help here, only it's misnamed (even if in this particular case that term would be fitting). artur
Jun 18 2012
prev sibling next sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 6/18/12 1:35 AM, Matthias Walter wrote:
 On 06/18/2012 08:19 AM, Mehrdad wrote:
 On Monday, 18 June 2012 at 06:14:22 UTC, Matthias Walter wrote:
 Its not, that a const method cannot modify an object, it just
 ensures that the const method cannot modify the object *by using
 the this-pointer*.

I see... So that means you /can't/ tell something just by looking at a part of the code, right? (Just mentioning this since this idea seemed to be emphasized a lot by D.)

Yes, you are right with that.

Actually things are a fair amount subtler. On the face of it, immutable does fulfill the OP's expectation. But even for const code, there's still a lot of guarantees that can be inferred depending on the types involved. Andrei
Jun 18 2012
prev sibling parent reply travert phare.normalesup.org (Christophe Travert) writes:
Matthias Walter , dans le message (digitalmars.D:170036), a crit:
 On 06/18/2012 07:36 AM, Mehrdad wrote:
 Is it just me, or did I subvert the type system here?
 
 
 import std.stdio;
 
 struct Const
 {
     this(void delegate() increment)
     { this.increment = increment; }
     int a;
     void delegate() increment;
     void oops() const { this.increment(); }
 }
 
 void main()
 {
     Const c;
     c = Const({ c.a++; });
     writeln(c.a);
     c.oops();
     writeln(c.a);
 }
 

I don't think so. When calling oops you have two references to the object c: - The this-pointer of the object itself which is not allowed to change the object in the const-call. - The reference from within main which is allowed to change it and can be reached via the frame pointer of the delegate. I see this as perfectly valid code. Of course, opinions may differ here.

But here, the frame pointer of the delegate is part of the const structure. By transitivity, the frame pointer should be const, and therefore, calling the delegate (which modifies the frame pointer) should not be legal. To be callable from a const method (oops), the member delegate (increment) should be of type "void delegate() const". This type exists, but is not easy to get[1]. Because constness of frame pointers is not implemented as it should be[2], there is a hole in the const system. [1] AFAIK, it can only be got by making an explicit delegate like &struct.const_method [2] Just like pure and no_throw attributes are complicated to work with delegates, the const attribute would be a mess to use at the moment. -- Christophe
Jun 18 2012
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 06/18/2012 05:14 PM, Christophe Travert wrote:
 Matthias Walter , dans le message (digitalmars.D:170036), a crit :
 On 06/18/2012 07:36 AM, Mehrdad wrote:
 Is it just me, or did I subvert the type system here?


 import std.stdio;

 struct Const
 {
      this(void delegate() increment)
      { this.increment = increment; }
      int a;
      void delegate() increment;
      void oops() const { this.increment(); }
 }

 void main()
 {
      Const c;
      c = Const({ c.a++; });
      writeln(c.a);
      c.oops();
      writeln(c.a);
 }

I don't think so. When calling oops you have two references to the object c: - The this-pointer of the object itself which is not allowed to change the object in the const-call. - The reference from within main which is allowed to change it and can be reached via the frame pointer of the delegate. I see this as perfectly valid code. Of course, opinions may differ here.

But here, the frame pointer of the delegate is part of the const structure. By transitivity, the frame pointer should be const, ...

'By transitivity' is not a sufficient reason. What you really mean is 'For the guarantee that a const pure method does not change its mutable parameters'.
Jun 18 2012
parent reply deadalnix <deadalnix gmail.com> writes:
Le 18/06/2012 17:29, Timon Gehr a crit :
 On 06/18/2012 05:14 PM, Christophe Travert wrote:
 Matthias Walter , dans le message (digitalmars.D:170036), a crit :
 On 06/18/2012 07:36 AM, Mehrdad wrote:
 Is it just me, or did I subvert the type system here?


 import std.stdio;

 struct Const
 {
 this(void delegate() increment)
 { this.increment = increment; }
 int a;
 void delegate() increment;
 void oops() const { this.increment(); }
 }

 void main()
 {
 Const c;
 c = Const({ c.a++; });
 writeln(c.a);
 c.oops();
 writeln(c.a);
 }

I don't think so. When calling oops you have two references to the object c: - The this-pointer of the object itself which is not allowed to change the object in the const-call. - The reference from within main which is allowed to change it and can be reached via the frame pointer of the delegate. I see this as perfectly valid code. Of course, opinions may differ here.

But here, the frame pointer of the delegate is part of the const structure. By transitivity, the frame pointer should be const, ...

'By transitivity' is not a sufficient reason. What you really mean is 'For the guarantee that a const pure method does not change its mutable parameters'.

Transitivity by itself is required to solve a wide range of problem. The most obvious one is controlling what is shared and what isn't using the type system.
Jun 18 2012
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 06/18/2012 11:04 PM, deadalnix wrote:
 Le 18/06/2012 17:29, Timon Gehr a crit :
 On 06/18/2012 05:14 PM, Christophe Travert wrote:
 Matthias Walter , dans le message (digitalmars.D:170036), a crit :
 On 06/18/2012 07:36 AM, Mehrdad wrote:
 Is it just me, or did I subvert the type system here?


 import std.stdio;

 struct Const
 {
     this(void delegate() increment)
     { this.increment = increment; }
     int a;
     void delegate() increment;
     void oops() const { this.increment(); }
 }

 void main()
 {
     Const c;
     c = Const({ c.a++; });
     writeln(c.a);
     c.oops();
     writeln(c.a);
 }

I don't think so. When calling oops you have two references to the object c: - The this-pointer of the object itself which is not allowed to change the object in the const-call. - The reference from within main which is allowed to change it and can be reached via the frame pointer of the delegate. I see this as perfectly valid code. Of course, opinions may differ here.

But here, the frame pointer of the delegate is part of the const structure. By transitivity, the frame pointer should be const, ...

'By transitivity' is not a sufficient reason. What you really mean is 'For the guarantee that a const pure method does not change its mutable parameters'.

Transitivity by itself is required to solve a wide range of problem. The most obvious one is controlling what is shared and what isn't using the type system.

That is completely unrelated. It is impossible to justify transitivity of const for delegate context pointers using this argument. It is far too general and the justification for the general concept comes from a specific example that is different from the one at hand. The question is, what the meaning of 'const' references should be: 1. data cannot be changed transitively through the reference 2. the reference can reference both 'const' and 'immutable' data and 'immutable' data can transitively not be changed through the reference. 1. requires transitive const for delegate context pointers, 2. does not.
Jun 19 2012
next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 06/19/2012 05:49 PM, Timon Gehr wrote:
 The question is, what the meaning of 'const' references should be:

 1. data cannot be changed transitively through the reference

 2. the reference can reference both 'const' and 'immutable' data and
 'immutable' data can transitively not be changed through the
 reference.


 1. requires transitive const for delegate context pointers, 2. does not.

(Note: I completely agree that 'immutable' and 'shared' need to be transitive across delegate context pointers.)
Jun 19 2012
prev sibling next sibling parent reply travert phare.normalesup.org (Christophe Travert) writes:
Timon Gehr , dans le message (digitalmars.D:170178), a crit:
 That is completely unrelated.
 It is impossible to justify transitivity of const for delegate context
 pointers using this argument. It is far too general and the
 justification for the general concept comes from a specific example
 that is different from the one at hand.
 
 The question is, what the meaning of 'const' references should be:
 
 1. data cannot be changed transitively through the reference
 
 2. the reference can reference both 'const' and 'immutable' data and
     'immutable' data can transitively not be changed through the
     reference.
 
 
 1. requires transitive const for delegate context pointers, 2. does not.

A const reference can contain I don't understand the difference. struct Delegate(C, F, Args) { C* ptr; // points to some structure containing all referenced data R function(C*, Args) fun; R opCall(Args) { return ptr.fun(Args); } } The signature of opCall determines the type of the delegate. In reality, the delegate is opaque, and C is not typed. ptr is a pointer to void*, and fun knows how to use that pointer. But that does not prevent the pointer to be const or immutable. Note that calling opCall is not possible if the Delegate is const, or part of a const structure, because opCall does not have the const attribute. But the signature of opCall could have any kind of attributes: const, immutable, pure, nothrow, inout..., which can be reflected by the delegates type. A delegate of type "R delegate(Args) const" would be like this: struct DelegateConst(C, R, Args...) { const C* ptr; R function(const C*, Args) fun; R opCall(Args) const { return ptr.fun(Args); } } Now it is possible to call opCall if the DelegateConst is const. However, it is possible to build this delegate only if fun is const with regard to its context argument. The same holds if you replace const by immutable. Now, the context pointer can point to all type of data. C is a like a structure, and can contain any kind of data (mutable, const, immutable, shared...). However transitivity rules must be preserved. If the data is immutable, the delegate context pointer must be. If the data is a mix of mutable, const, and immutable data, there is no problem, has long has the function mutates only the mutable data (but then, the delegate's frame pointer type must be mutable, and the delegate is not callable if it is const). However, it must respect transitivity: if the delegate is immutable, all data contained in the context must be immutable. If the context pointer is const, the data can be mutable, const, or immutable. And where does all this comes from ? delegates are primarily methods applied to a struct or class instance. class S { data d; void method(arg); } S s = new S; void delegate(arg) dg = &s.method; The delegate is constructed directly from the object's method. (1) That is why it must have the same signature has objects method. If you want to fully represent methods, delegates must have all methods attributes: pure, nothrow, (which is a problem to introduce in toString methods, for example), etc... but also const, immutable, and maybe one day inout. Currently, delegates does not support all this. It makes life easier, because we do not have a zillion types of delegates, and it gives a little bit of air on const virality until we have proper systems to simplify all this. But this is a gap in the langage. It is up to the programmer to respect const transitivity, and not exploit this gap and break the langage. (1) In my first example, a langage delegate can be obtained from my artificial Delegate template by taking the adresse of opApply: Delegate!(C, R, Args) s; R delegate(Args) dg = &s.opApply (2) Note that it is however possible to obtain a 'C delegate(Args) const' but taking the adress of a const method. class S { data d; void method(Arg) const; } S s = new S; auto dg = &s.method; dg is infered as 'void delegate(Arg) const' by the compiler -- Christophe
Jun 19 2012
next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 06/19/2012 07:18 PM, Christophe Travert wrote:
 Timon Gehr , dans le message (digitalmars.D:170178), a crit :
 That is completely unrelated.
 It is impossible to justify transitivity of const for delegate context
 pointers using this argument. It is far too general and the
 justification for the general concept comes from a specific example
 that is different from the one at hand.

 The question is, what the meaning of 'const' references should be:

 1. data cannot be changed transitively through the reference

 2. the reference can reference both 'const' and 'immutable' data and
      'immutable' data can transitively not be changed through the
      reference.


 1. requires transitive const for delegate context pointers, 2. does not.

A const reference can contain I don't understand the difference. ...

In 2., mutable data referred to by a const reference might be changed through it.
Jun 19 2012
parent reply travert phare.normalesup.org (Christophe Travert) writes:
Timon Gehr , dans le message (digitalmars.D:170185), a crit:
 On 06/19/2012 07:18 PM, Christophe Travert wrote:
 In 2., mutable data referred to by a const reference might be changed 
 through it.

Then it is not a const reference, it is a normal reference. Qualifying the reference as 'const' does not by you anything at all. Of course it should not change immutable data, no delegate should do that ! You might state that frame pointer are opaque, have no const qualifier, and that any delegates is callable even if they are in a const structure. But saying a reference (frame pointer) can be const AND modify data is a bit weird. -- Christophe
Jun 19 2012
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 06/19/2012 07:47 PM, Christophe Travert wrote:
 Timon Gehr , dans le message (digitalmars.D:170185), a crit :
 On 06/19/2012 07:18 PM, Christophe Travert wrote:
 In 2., mutable data referred to by a const reference might be changed
 through it.

Then it is not a const reference, it is a normal reference. Qualifying the reference as 'const' does not by you anything at all. Of course it should not change immutable data, no delegate should do that ! You might state that frame pointer are opaque, have no const qualifier, and that any delegates is callable even if they are in a const structure. But saying a reference (frame pointer) can be const AND modify data is a bit weird.

I am not saying that. I am saying that a 'const' delegate can contain a non-'const' frame pointer without breaking the 'immutable' type qualifier, because the delegate is type checked modularly at its creation site. It is a const reference to a structure that contains such a delegate that will fail to deliver any guarantees in case it refers to data that is not 'immutable'. Opaque delegate context pointers effectively break the transitivity of 'const', but not the transitivity of 'immutable'.
Jun 19 2012
prev sibling parent reply travert phare.normalesup.org (Christophe Travert) writes:
Christophe Travert, dans le message (digitalmars.D:170182), a crit:
 Timon Gehr , dans le message (digitalmars.D:170178), a crit:
 That is completely unrelated.
 It is impossible to justify transitivity of const for delegate context
 pointers using this argument. It is far too general and the
 justification for the general concept comes from a specific example
 that is different from the one at hand.
 
 The question is, what the meaning of 'const' references should be:
 
 1. data cannot be changed transitively through the reference
 
 2. the reference can reference both 'const' and 'immutable' data and
     'immutable' data can transitively not be changed through the
     reference.
 
 
 1. requires transitive const for delegate context pointers, 2. does not.

A const reference can contain I don't understand the difference.

Apologies, I forgot to complete my post: A const reference can contain both mutable and immutable data, as long as it does not allow to mutate it. I don't understand point 2: if the reference contain const reference, it should not modify the const reference. Of course, immutable data should not be changed in any case. This is how I understand delegates should work: [the rest of the post is unchanged]
 
 struct Delegate(C, F, Args)
 {
     C* ptr; // points to some structure containing all referenced data
     R function(C*, Args) fun;
     R opCall(Args) { return ptr.fun(Args); }
 }
 
 The signature of opCall determines the type of the delegate. In reality, 
 the delegate is opaque, and C is not typed. ptr is a pointer to void*, 
 and fun knows how to use that pointer. But that does not prevent the 
 pointer to be const or immutable.
 
 Note that calling opCall is not possible if the Delegate is const, or 
 part of a const structure, because opCall does not have the const 
 attribute.
 
 But the signature of opCall could have any kind of attributes: const, 
 immutable, pure, nothrow, inout..., which can be reflected by the 
 delegates type.
 
 A delegate of type "R delegate(Args) const" would be like this:
 
 struct DelegateConst(C, R, Args...)
 {
   const C* ptr;
   R function(const C*, Args) fun;
   R opCall(Args) const { return ptr.fun(Args); }
 }
 
 Now it is possible to call opCall if the DelegateConst is const. 
 However, it is possible to build this delegate only if fun is const with 
 regard to its context argument.
 
 The same holds if you replace const by immutable.
 
 Now, the context pointer can point to all type of data. C is a like a 
 structure, and can contain any kind of data (mutable, const, immutable, 
 shared...). However transitivity rules must be preserved. If the data is 
 immutable, the delegate context pointer must be. If the data is a mix of 
 mutable, const, and immutable data, there is no problem, has long has 
 the function mutates only the mutable data (but then, the delegate's 
 frame pointer type must be mutable, and the delegate is not callable if 
 it is const).
 
 However, it must respect transitivity: if the delegate is immutable, all 
 data contained in the context must be immutable. If the context pointer 
 is const, the data can be mutable, const, or immutable.
 
 
 And where does all this comes from ?
 delegates are primarily methods applied to a struct or class instance.
 
 class S
 {
   data d;
   void method(arg);
 }
 
 S s = new S;
 void delegate(arg) dg = &s.method;
 
 The delegate is constructed directly from the object's method. (1)
 
 That is why it must have the same signature has objects method. If you 
 want to fully represent methods, delegates must have all methods 
 attributes: pure, nothrow, (which is a problem to introduce in 
 toString methods, for example), etc... but also const, immutable, and 
 maybe one day inout.
 
 Currently, delegates does not support all this. It makes life easier, 
 because we do not have a zillion types of delegates, and it gives a 
 little bit of air on const virality until we have proper systems to 
 simplify all this. But this is a gap in the langage. It is up to the 
 programmer to respect const transitivity, and not exploit this gap and 
 break the langage.
 
 (1) In my first example, a langage delegate can be obtained from my 
 artificial Delegate template by taking the adresse of opApply:
 
 Delegate!(C, R, Args) s;
 R delegate(Args) dg = &s.opApply
 
 (2) Note that it is however possible to obtain a 'C delegate(Args) 
 const' but taking the adress of a const method.
 
 class S
 {
   data d;
   void method(Arg) const;
 }
 
 S s = new S;
 auto dg = &s.method; 
 
 dg is infered as 'void delegate(Arg) const' by the compiler
 
 -- 
 Christophe

Jun 19 2012
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 06/19/2012 07:40 PM, Christophe Travert wrote:
 Christophe Travert, dans le message (digitalmars.D:170182), a crit :
 Timon Gehr , dans le message (digitalmars.D:170178), a crit :
 That is completely unrelated.
 It is impossible to justify transitivity of const for delegate context
 pointers using this argument. It is far too general and the
 justification for the general concept comes from a specific example
 that is different from the one at hand.

 The question is, what the meaning of 'const' references should be:

 1. data cannot be changed transitively through the reference

 2. the reference can reference both 'const' and 'immutable' data and
      'immutable' data can transitively not be changed through the
      reference.


 1. requires transitive const for delegate context pointers, 2. does not.

A const reference can contain I don't understand the difference.

Apologies, I forgot to complete my post: A const reference can contain both mutable and immutable data, as long as it does not allow to mutate it.

Exactly, absolutely no mutation ==> can refer to mutable or immutable data but can refer to mutable or immutable data =/=> absolutely no mutation The question is whether 'const' is there just to tie together mutable and immutable, or if it is a distinct entity.
Jun 19 2012
prev sibling parent reply deadalnix <deadalnix gmail.com> writes:
Le 19/06/2012 17:49, Timon Gehr a crit :
 On 06/18/2012 11:04 PM, deadalnix wrote:
 Le 18/06/2012 17:29, Timon Gehr a crit :
 On 06/18/2012 05:14 PM, Christophe Travert wrote:
 Matthias Walter , dans le message (digitalmars.D:170036), a crit :
 On 06/18/2012 07:36 AM, Mehrdad wrote:
 Is it just me, or did I subvert the type system here?


 import std.stdio;

 struct Const
 {
 this(void delegate() increment)
 { this.increment = increment; }
 int a;
 void delegate() increment;
 void oops() const { this.increment(); }
 }

 void main()
 {
 Const c;
 c = Const({ c.a++; });
 writeln(c.a);
 c.oops();
 writeln(c.a);
 }

I don't think so. When calling oops you have two references to the object c: - The this-pointer of the object itself which is not allowed to change the object in the const-call. - The reference from within main which is allowed to change it and can be reached via the frame pointer of the delegate. I see this as perfectly valid code. Of course, opinions may differ here.

But here, the frame pointer of the delegate is part of the const structure. By transitivity, the frame pointer should be const, ...

'By transitivity' is not a sufficient reason. What you really mean is 'For the guarantee that a const pure method does not change its mutable parameters'.

Transitivity by itself is required to solve a wide range of problem. The most obvious one is controlling what is shared and what isn't using the type system.

That is completely unrelated. It is impossible to justify transitivity of const for delegate context pointers using this argument. It is far too general and the justification for the general concept comes from a specific example that is different from the one at hand. The question is, what the meaning of 'const' references should be: 1. data cannot be changed transitively through the reference 2. the reference can reference both 'const' and 'immutable' data and 'immutable' data can transitively not be changed through the reference. 1. requires transitive const for delegate context pointers, 2. does not.

No, 2. require 1., even if the initialization is broken. class Foo { void delegate() dg; this(immutable void delegate() dg) immutable { thid.dg = dg; } } Now, as delegate doesn't carry the constness of its context, an immutable instance of Foo can refers to something that isn't immutable.
Jun 20 2012
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 06/20/2012 09:16 AM, deadalnix wrote:
 Le 19/06/2012 17:49, Timon Gehr a crit :
 The question is, what the meaning of 'const' references should be:

 1. data cannot be changed transitively through the reference

 2. the reference can reference both 'const' and 'immutable' data and
 'immutable' data can transitively not be changed through the
 reference.


 1. requires transitive const for delegate context pointers, 2. does not.

No, 2. require 1., even if the initialization is broken. class Foo { void delegate() dg; this(immutable void delegate() dg) immutable { thid.dg = dg; } } Now, as delegate doesn't carry the constness of its context, an immutable instance of Foo can refers to something that isn't immutable.

Clarification: 'const' means 'const'. No other qualifiers. There is no 'const' in that example code. 'immutable' obviously needs to be transitive regardless of the particular interpretation of 'const'.
Jun 20 2012
parent reply travert phare.normalesup.org (Christophe Travert) writes:
Timon Gehr , dans le message (digitalmars.D:170288), a crit:
 On 06/20/2012 09:16 AM, deadalnix wrote:
 Le 19/06/2012 17:49, Timon Gehr a crit :
 The question is, what the meaning of 'const' references should be:

 1. data cannot be changed transitively through the reference

 2. the reference can reference both 'const' and 'immutable' data and
 'immutable' data can transitively not be changed through the
 reference.


 1. requires transitive const for delegate context pointers, 2. does not.

No, 2. require 1., even if the initialization is broken. class Foo { void delegate() dg; this(immutable void delegate() dg) immutable { thid.dg = dg; } } Now, as delegate doesn't carry the constness of its context, an immutable instance of Foo can refers to something that isn't immutable.

Clarification: 'const' means 'const'. No other qualifiers. There is no 'const' in that example code. 'immutable' obviously needs to be transitive regardless of the particular interpretation of 'const'.

you apply different rules to const and to immutable, you are breaking the consistency of the langage.
Jun 20 2012
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 06/20/2012 01:36 PM, Christophe Travert wrote:
 Timon Gehr , dans le message (digitalmars.D:170288), a crit :
 On 06/20/2012 09:16 AM, deadalnix wrote:
 Le 19/06/2012 17:49, Timon Gehr a crit :
 The question is, what the meaning of 'const' references should be:

 1. data cannot be changed transitively through the reference

 2. the reference can reference both 'const' and 'immutable' data and
 'immutable' data can transitively not be changed through the
 reference.


 1. requires transitive const for delegate context pointers, 2. does not.

No, 2. require 1., even if the initialization is broken. class Foo { void delegate() dg; this(immutable void delegate() dg) immutable { thid.dg = dg; } } Now, as delegate doesn't carry the constness of its context, an immutable instance of Foo can refers to something that isn't immutable.

Clarification: 'const' means 'const'. No other qualifiers. There is no 'const' in that example code. 'immutable' obviously needs to be transitive regardless of the particular interpretation of 'const'.


Or maybe mutable. Therefore, interpretation '2.'
 Thus const needs to be transitive too.

Wrong. This is the (A==>B) ==> (B==>A) fallacy, where A: 'const' is transitive B: 'const' references cannot modify 'immutable' data The conclusion regarding transitivity, given interpretation 2., is that 'const' needs to be transitive _if_ it is actually 'immutable'.
 If you apply different rules to const and to immutable, you are breaking
 the consistency of the langage.

Certainly not. This is like saying that applying different rules to 'const' and mutable is breaking the consistency of the language. mutable is not transitive.
Jun 20 2012
next sibling parent reply deadalnix <deadalnix gmail.com> writes:
Le 20/06/2012 13:58, Timon Gehr a crit :
 Clarification: 'const' means 'const'. No other qualifiers.

 There is no 'const' in that example code. 'immutable' obviously needs to
 be transitive regardless of the particular interpretation of 'const'.


Or maybe mutable. Therefore, interpretation '2.'
 Thus const needs to be transitive too.

Wrong. This is the (A==>B) ==> (B==>A) fallacy, where A: 'const' is transitive B: 'const' references cannot modify 'immutable' data

I understand the difference. It can change the legality of some calls, that is true (and probably it is better). But it doesn't change the need for a frame pointer's qualifier (mandatory to ensure immutability transitivity). To benefit of the extra freedom granted by B, what could be the casts safely allowed on the delegate ? I understand your point, but fail to find any way to make it work in practice.
Jun 20 2012
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 06/20/2012 02:38 PM, deadalnix wrote:
 Le 20/06/2012 13:58, Timon Gehr a crit :
 Clarification: 'const' means 'const'. No other qualifiers.

 There is no 'const' in that example code. 'immutable' obviously
 needs to
 be transitive regardless of the particular interpretation of 'const'.


Or maybe mutable. Therefore, interpretation '2.'
 Thus const needs to be transitive too.

Wrong. This is the (A==>B) ==> (B==>A) fallacy, where A: 'const' is transitive B: 'const' references cannot modify 'immutable' data

I understand the difference. It can change the legality of some calls, that is true (and probably it is better).

None is strictly better than the other. Interpretation 2. gains some flexibility in respect to interpretation 1., but it loses some 'const pure' guarantees. It is a matter of how these two orthogonal concerns are weighted against each other. (Ideally, there would be one distinct qualifier for each of the interpretations. :o) )
 But it doesn't change the need
 for a frame pointer's qualifier (mandatory to ensure immutability
 transitivity).

 To benefit of the extra freedom granted by B, what could be the casts
 safely allowed on the delegate ? I understand your point, but fail to
 find any way to make it work in practice.

It works if implicit conversions from 'R delegate(A)' to 'R delegate(A) const' are allowed, as well as looking up an 'R delegate(A) in a const receiver object.
Jun 20 2012
parent reply deadalnix <deadalnix gmail.com> writes:
Le 20/06/2012 16:21, Timon Gehr a crit :
 On 06/20/2012 02:38 PM, deadalnix wrote:
 Le 20/06/2012 13:58, Timon Gehr a crit :
 Clarification: 'const' means 'const'. No other qualifiers.

 There is no 'const' in that example code. 'immutable' obviously
 needs to
 be transitive regardless of the particular interpretation of 'const'.


Or maybe mutable. Therefore, interpretation '2.'
 Thus const needs to be transitive too.

Wrong. This is the (A==>B) ==> (B==>A) fallacy, where A: 'const' is transitive B: 'const' references cannot modify 'immutable' data

I understand the difference. It can change the legality of some calls, that is true (and probably it is better).

None is strictly better than the other. Interpretation 2. gains some flexibility in respect to interpretation 1., but it loses some 'const pure' guarantees. It is a matter of how these two orthogonal concerns are weighted against each other. (Ideally, there would be one distinct qualifier for each of the interpretations. :o) )
 But it doesn't change the need
 for a frame pointer's qualifier (mandatory to ensure immutability
 transitivity).

 To benefit of the extra freedom granted by B, what could be the casts
 safely allowed on the delegate ? I understand your point, but fail to
 find any way to make it work in practice.

It works if implicit conversions from 'R delegate(A)' to 'R delegate(A) const' are allowed, as well as looking up an 'R delegate(A) in a const receiver object.

What about conversion from/to immutable ?
Jun 20 2012
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 06/20/2012 04:36 PM, deadalnix wrote:
 Le 20/06/2012 16:21, Timon Gehr a crit :
 On 06/20/2012 02:38 PM, deadalnix wrote:
 Le 20/06/2012 13:58, Timon Gehr a crit :
 Clarification: 'const' means 'const'. No other qualifiers.

 There is no 'const' in that example code. 'immutable' obviously
 needs to
 be transitive regardless of the particular interpretation of 'const'.


Or maybe mutable. Therefore, interpretation '2.'
 Thus const needs to be transitive too.

Wrong. This is the (A==>B) ==> (B==>A) fallacy, where A: 'const' is transitive B: 'const' references cannot modify 'immutable' data

I understand the difference. It can change the legality of some calls, that is true (and probably it is better).

None is strictly better than the other. Interpretation 2. gains some flexibility in respect to interpretation 1., but it loses some 'const pure' guarantees. It is a matter of how these two orthogonal concerns are weighted against each other. (Ideally, there would be one distinct qualifier for each of the interpretations. :o) )
 But it doesn't change the need
 for a frame pointer's qualifier (mandatory to ensure immutability
 transitivity).

 To benefit of the extra freedom granted by B, what could be the casts
 safely allowed on the delegate ? I understand your point, but fail to
 find any way to make it work in practice.

It works if implicit conversions from 'R delegate(A)' to 'R delegate(A) const' are allowed, as well as looking up an 'R delegate(A) in a const receiver object.

What about conversion from/to immutable ?

Simple application of transitivity. This part is the same for 1./2. 'R delegate(A)' and 'R delegate(A) const' are not convertible to 'R delegate(A) immutable', but 'R delegate(A) immutable' is convertible to 'R delegate(A)' and 'R delegate(A) const'. 'R delegate(A)' and 'R delegate(A) const' decay to 'R delegate(A) immutable' when stored in an immutable object. Regarding 'shared': delegates that only access shared data can be marked as having a 'shared' context pointer. 'R delegate(A)shared' can convert to 'R delegate(A)', but not vice versa. 'R delegate(A)shared' can convert to 'shared(R delegate(A))', and vice versa. In essence, for delegates, the operation of qualifying the 'tail' of the delegate will be qualifying the context pointer. eg: static assert(is( typeof(cast()shared(R delegate(A)).init) == R delegate(A)shared ));
Jun 20 2012
parent reply deadalnix <deadalnix gmail.com> writes:
Le 20/06/2012 16:55, Timon Gehr a crit :
 It works if implicit conversions from 'R delegate(A)' to
 'R delegate(A) const' are allowed, as well as looking up an
 'R delegate(A) in a const receiver object.

What about conversion from/to immutable ?

Simple application of transitivity. This part is the same for 1./2. 'R delegate(A)' and 'R delegate(A) const' are not convertible to 'R delegate(A) immutable', but 'R delegate(A) immutable' is convertible to 'R delegate(A)' and 'R delegate(A) const'. 'R delegate(A)' and 'R delegate(A) const' decay to 'R delegate(A) immutable' when stored in an immutable object.

I've gone through a similar reasoning. It does not work. Here are implicit casts summed up : mutable -> const immutable -> const immutable -> mutable which implicitely imply immutable -> const And then you add when storing in immutable object. mutable -> immutable const -> immutable It means that you can store a delegate that have a mutable frame pointer into an immutable object, which break transitivity.
Jun 20 2012
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 06/20/2012 05:17 PM, deadalnix wrote:
 Le 20/06/2012 16:55, Timon Gehr a crit :
 It works if implicit conversions from 'R delegate(A)' to
 'R delegate(A) const' are allowed, as well as looking up an
 'R delegate(A) in a const receiver object.

What about conversion from/to immutable ?

Simple application of transitivity. This part is the same for 1./2. 'R delegate(A)' and 'R delegate(A) const' are not convertible to 'R delegate(A) immutable', but 'R delegate(A) immutable' is convertible to 'R delegate(A)' and 'R delegate(A) const'. 'R delegate(A)' and 'R delegate(A) const' decay to 'R delegate(A) immutable' when stored in an immutable object.

I've gone through a similar reasoning. It does not work.

Yes it does.
 Here are implicit casts summed up :

 mutable -> const
 immutable -> const
 immutable -> mutable

 which implicitely imply immutable -> const

 And then you add when storing in immutable object.

 mutable -> immutable
 const -> immutable

These are not valid implicit conversions. What I meant was: struct S{ R delegate(A) mutable; R delegate(B)const const_; } static assert(is(typeof(immutable(S).mutable)==R delegate(A) immutable)); static assert(is(typeof(immutable(S).const_)==R delegate(A) immutable));
 It means that you can store a delegate that have a mutable frame pointer
 into an immutable object, which break transitivity.

No.
Jun 20 2012
next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 06/20/2012 05:24 PM, Timon Gehr wrote:
 static assert(is(typeof(immutable(S).mutable)==R delegate(A) immutable));
 static assert(is(typeof(immutable(S).const_)==R delegate(A) immutable));

Actually, static assert(is(typeof(cast()immutable(S).mutable)==R delegate(A) immutable)); static assert(is(typeof(cast()immutable(S).const_)==R delegate(A) immutable));
Jun 20 2012
prev sibling parent reply deadalnix <deadalnix gmail.com> writes:
Le 20/06/2012 17:24, Timon Gehr a crit :
 On 06/20/2012 05:17 PM, deadalnix wrote:
 Le 20/06/2012 16:55, Timon Gehr a crit :
 It works if implicit conversions from 'R delegate(A)' to
 'R delegate(A) const' are allowed, as well as looking up an
 'R delegate(A) in a const receiver object.

What about conversion from/to immutable ?

Simple application of transitivity. This part is the same for 1./2. 'R delegate(A)' and 'R delegate(A) const' are not convertible to 'R delegate(A) immutable', but 'R delegate(A) immutable' is convertible to 'R delegate(A)' and 'R delegate(A) const'. 'R delegate(A)' and 'R delegate(A) const' decay to 'R delegate(A) immutable' when stored in an immutable object.

I've gone through a similar reasoning. It does not work.

Yes it does.
 Here are implicit casts summed up :

 mutable -> const
 immutable -> const
 immutable -> mutable

 which implicitely imply immutable -> const

 And then you add when storing in immutable object.

 mutable -> immutable
 const -> immutable

These are not valid implicit conversions. What I meant was: struct S{ R delegate(A) mutable; R delegate(B)const const_; } static assert(is(typeof(immutable(S).mutable)==R delegate(A) immutable)); static assert(is(typeof(immutable(S).const_)==R delegate(A) immutable));
 It means that you can store a delegate that have a mutable frame pointer
 into an immutable object, which break transitivity.

No.

OK I understand. This is consistent for the frame pointer type qualifier. But not for the function pointer. Remember that the function pointer accept the frame pointer as argument. This set of rule prevent safe closure rebinding.
Jun 20 2012
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 06/20/2012 05:33 PM, deadalnix wrote:
 Le 20/06/2012 17:24, Timon Gehr a crit :
 On 06/20/2012 05:17 PM, deadalnix wrote:
 Le 20/06/2012 16:55, Timon Gehr a crit :
 It works if implicit conversions from 'R delegate(A)' to
 'R delegate(A) const' are allowed, as well as looking up an
 'R delegate(A) in a const receiver object.

What about conversion from/to immutable ?

Simple application of transitivity. This part is the same for 1./2. 'R delegate(A)' and 'R delegate(A) const' are not convertible to 'R delegate(A) immutable', but 'R delegate(A) immutable' is convertible to 'R delegate(A)' and 'R delegate(A) const'. 'R delegate(A)' and 'R delegate(A) const' decay to 'R delegate(A) immutable' when stored in an immutable object.

I've gone through a similar reasoning. It does not work.

Yes it does.
 Here are implicit casts summed up :

 mutable -> const
 immutable -> const
 immutable -> mutable

 which implicitely imply immutable -> const

 And then you add when storing in immutable object.

 mutable -> immutable
 const -> immutable

These are not valid implicit conversions. What I meant was: struct S{ R delegate(A) mutable; R delegate(B)const const_; } static assert(is(typeof(immutable(S).mutable)==R delegate(A) immutable)); static assert(is(typeof(immutable(S).const_)==R delegate(A) immutable));
 It means that you can store a delegate that have a mutable frame pointer
 into an immutable object, which break transitivity.

No.

OK I understand. This is consistent for the frame pointer type qualifier. But not for the function pointer. Remember that the function pointer accept the frame pointer as argument. This set of rule prevent safe closure rebinding.

There is no such thing as safe closure rebinding.
Jun 20 2012
parent deadalnix <deadalnix gmail.com> writes:
Le 20/06/2012 20:08, Timon Gehr a crit :
 This set of rule prevent safe closure rebinding.

There is no such thing as safe closure rebinding.

In D and ATM no. But this exists, and should be considered, as what you proposed totally exclude from being added later on.
Jun 21 2012
prev sibling parent reply travert phare.normalesup.org (Christophe Travert) writes:
Timon Gehr , dans le message (digitalmars.D:170296), a crit:
 On 06/20/2012 01:36 PM, Christophe Travert wrote:
 Timon Gehr , dans le message (digitalmars.D:170288), a crit :
 On 06/20/2012 09:16 AM, deadalnix wrote:
 Le 19/06/2012 17:49, Timon Gehr a crit :
 The question is, what the meaning of 'const' references should be:

 1. data cannot be changed transitively through the reference

 2. the reference can reference both 'const' and 'immutable' data and
 'immutable' data can transitively not be changed through the
 reference.


 1. requires transitive const for delegate context pointers, 2. does not.

No, 2. require 1., even if the initialization is broken. class Foo { void delegate() dg; this(immutable void delegate() dg) immutable { thid.dg = dg; } } Now, as delegate doesn't carry the constness of its context, an immutable instance of Foo can refers to something that isn't immutable.

Clarification: 'const' means 'const'. No other qualifiers. There is no 'const' in that example code. 'immutable' obviously needs to be transitive regardless of the particular interpretation of 'const'.


Or maybe mutable. Therefore, interpretation '2.'
 Thus const needs to be transitive too.

Wrong. This is the (A==>B) ==> (B==>A) fallacy, where A: 'const' is transitive B: 'const' references cannot modify 'immutable' data The conclusion regarding transitivity, given interpretation 2., is that 'const' needs to be transitive _if_ it is actually 'immutable'.

OK, I understand what you mean. I think this behavior is dangerous. You have to add rules to make sure B is not violated. I am not sure this is worth it.
 If you apply different rules to const and to immutable, you are breaking
 the consistency of the langage.

Certainly not. This is like saying that applying different rules to 'const' and mutable is breaking the consistency of the language. mutable is not transitive.

OK, that was not the right argument. Const is transitive according to the spec. Making it not transitive would break the mutable < const < immutable design. You're trying to make an exception for data hidden behind a delegate, which is a dangerous thing. -- Christophe
Jun 20 2012
next sibling parent deadalnix <deadalnix gmail.com> writes:
Le 20/06/2012 15:13, Christophe Travert a crit :
 OK, that was not the right argument. Const is transitive according to
 the spec. Making it not transitive would break the mutable<  const<
 immutable design. You're trying to make an exception for data hidden
 behind a delegate, which is a dangerous thing.

Nothing is const by itself. Things are mutable or immutable. const means mutable or immutable. Things are const only in the eyes of code manipulating the mutable or immutable data. Immutable require to be transitive. Const need to respect immutability transitivity. This means const transitivity most of the time, but in fact, the real constraint is immutability transitivity.
Jun 20 2012
prev sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 06/20/2012 03:13 PM, Christophe Travert wrote:
 Timon Gehr , dans le message (digitalmars.D:170296), a crit :
 On 06/20/2012 01:36 PM, Christophe Travert wrote:
 Timon Gehr , dans le message (digitalmars.D:170288), a crit :
 On 06/20/2012 09:16 AM, deadalnix wrote:
 Le 19/06/2012 17:49, Timon Gehr a crit :
 The question is, what the meaning of 'const' references should be:

 1. data cannot be changed transitively through the reference

 2. the reference can reference both 'const' and 'immutable' data and
 'immutable' data can transitively not be changed through the
 reference.


 1. requires transitive const for delegate context pointers, 2. does not.

No, 2. require 1., even if the initialization is broken. class Foo { void delegate() dg; this(immutable void delegate() dg) immutable { thid.dg = dg; } } Now, as delegate doesn't carry the constness of its context, an immutable instance of Foo can refers to something that isn't immutable.

Clarification: 'const' means 'const'. No other qualifiers. There is no 'const' in that example code. 'immutable' obviously needs to be transitive regardless of the particular interpretation of 'const'.


Or maybe mutable. Therefore, interpretation '2.'
 Thus const needs to be transitive too.

Wrong. This is the (A==>B) ==> (B==>A) fallacy, where A: 'const' is transitive B: 'const' references cannot modify 'immutable' data The conclusion regarding transitivity, given interpretation 2., is that 'const' needs to be transitive _if_ it is actually 'immutable'.

OK, I understand what you mean. I think this behavior is dangerous. You have to add rules to make sure B is not violated. I am not sure this is worth it.

No additional rules necessary, only loosening of existing rules. (in the obvious design, not in how the compiler implements it.)
 If you apply different rules to const and to immutable, you are breaking
 the consistency of the langage.

Certainly not. This is like saying that applying different rules to 'const' and mutable is breaking the consistency of the language. mutable is not transitive.

OK, that was not the right argument. Const is transitive according to the spec.

IIRC the spec is based on the claim that this is necessary in order to maintain immutability guarantees, so this is irrelevant for discussions relating to the alternative interpretation.
 Making it not transitive would break the mutable<  const< immutable design.

What is this '<' relation ?
 You're trying to make an exception for data hidden
 behind a delegate, which is a dangerous thing.

I understand the implications. There is nothing dangerous about it.
Jun 20 2012
parent reply travert phare.normalesup.org (Christophe Travert) writes:
Timon Gehr , dans le message (digitalmars.D:170313), a crit:
 OK, I understand what you mean. I think this behavior is dangerous. You
 have to add rules to make sure B is not violated. I am not sure this is
 worth it.

No additional rules necessary, only loosening of existing rules. (in the obvious design, not in how the compiler implements it.)
 If you apply different rules to const and to immutable, you are breaking
 the consistency of the langage.

Certainly not. This is like saying that applying different rules to 'const' and mutable is breaking the consistency of the language. mutable is not transitive.

OK, that was not the right argument. Const is transitive according to the spec.

IIRC the spec is based on the claim that this is necessary in order to maintain immutability guarantees, so this is irrelevant for discussions relating to the alternative interpretation.

own reasons. In reality, it is not purely necessary to maintain immutability, as you say, but it is necessary to maintain immutability AND the benefit of purity, AND the integrity of the whole spec, which is based on the assumption that const is transitive.
 Making it not transitive would break the mutable<  const< immutable design.

What is this '<' relation ?

I meant that const is somewhere between mutable and immutable. Also, mutable variable can contains const and immutable data, const variable can contain immutable data, but no mutable data (meaning: mutable through this reference). Subparts of a reference can increase the protection level of the data, but not decrease it.
 You're trying to make an exception for data hidden
 behind a delegate, which is a dangerous thing.
 


Making exception in the langage is dangerous because you can have surprising results when interacting with other exception. Give some spec explaining how your opaque delegate works, and someone will find a hole one day. I can't think of a way of implementing opaque delegates without a hole inside, or without loosing most of the point of immutability and purity. If you do, please share.
Jun 20 2012
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 06/20/2012 04:56 PM, Christophe Travert wrote:
 Timon Gehr , dans le message (digitalmars.D:170313), a crit :
 OK, I understand what you mean. I think this behavior is dangerous. You
 have to add rules to make sure B is not violated. I am not sure this is
 worth it.

No additional rules necessary, only loosening of existing rules. (in the obvious design, not in how the compiler implements it.)
 If you apply different rules to const and to immutable, you are breaking
 the consistency of the langage.

Certainly not. This is like saying that applying different rules to 'const' and mutable is breaking the consistency of the language. mutable is not transitive.

OK, that was not the right argument. Const is transitive according to the spec.

IIRC the spec is based on the claim that this is necessary in order to maintain immutability guarantees, so this is irrelevant for discussions relating to the alternative interpretation.

own reasons. In reality, it is not purely necessary to maintain immutability, as you say, but it is necessary to maintain immutability

if(true && ... )
 AND the benefit of purity,

As I said, 'const pure' with mutable/const arguments loses guarantees. purity does not lose its benefits.
 AND the integrity of the whole spec, which is
 based on the assumption that const is transitive.

The integrity of the spec cannot be maintained because it does not have one.
 Making it not transitive would break the mutable<   const<  immutable design.

What is this '<' relation ?

I meant that const is somewhere between mutable and immutable. Also, mutable variable can contains const and immutable data, const variable can contain immutable data, but no mutable data (meaning: mutable through this reference). Subparts of a reference can increase the protection level of the data, but not decrease it.

If 'const' is about 'protection' then you are straight into interpretation 1. This is not what I was discussing.
 You're trying to make an exception for data hidden
 behind a delegate, which is a dangerous thing.


Making exception in the langage is dangerous because you can have surprising results when interacting with other exception. Give some spec explaining how your opaque delegate works,

It is not an exception. What do you think is the exception?
 and someone will find a hole one day.

Before the language adapts the change, its correctness could be formally proven.
 I can't think of a way of implementing opaque delegates without a hole
 inside, or without loosing most of the point of immutability and purity.
 If you do, please share.

Delegates cannot be opaque as much as any other data structure. But they have the property that their data is only accessible via a pointer to a fully type checked function. This facilitates reasoning about what is and what is not valid for them.
Jun 20 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Monday, June 18, 2012 07:36:26 Mehrdad wrote:
 Is it just me, or did I subvert the type system here?
 
 
 import std.stdio;
 
 struct Const
 {
 	this(void delegate() increment)
 	{ this.increment = increment; }
 	int a;
 	void delegate() increment;
 	void oops() const { this.increment(); }
 }
 
 void main()
 {
 	Const c;
 	c = Const({ c.a++; });
 	writeln(c.a);
 	c.oops();
 	writeln(c.a);
 }

No, actually I don't think that you did. The delegate isn't accessing the this pointer - which is where const would kick in. It's accessing c through a non- const reference to the data that was put in its body inside of main. - Jonathan M Davis
Jun 17 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 18 June 2012 at 06:00:11 UTC, Matthias Walter wrote:
 On 06/18/2012 07:36 AM, Mehrdad wrote:
 Is it just me, or did I subvert the type system here?
 
 
 import std.stdio;
 
 struct Const
 {
     this(void delegate() increment)
     { this.increment = increment; }
     int a;
     void delegate() increment;
     void oops() const { this.increment(); }
 }
 
 void main()
 {
     Const c;
     c = Const({ c.a++; });
     writeln(c.a);
     c.oops();
     writeln(c.a);
 }
 

I don't think so. When calling oops you have two references to the object c: - The this-pointer of the object itself which is not allowed to change the object in the const-call. - The reference from within main which is allowed to change it and can be reached via the frame pointer of the delegate. I see this as perfectly valid code. Of course, opinions may differ here. Matthias

My trouble isn't with the delegate, it's with the const method. It's with the idea that "you can tell something about the code just by looking at it". The way I understood it, you can assume that a `const` method cannot modify an object, but... that doesn't seem to be the case here. What am I misunderstanding?
Jun 17 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 18 June 2012 at 06:00:11 UTC, Matthias Walter wrote:
 On 06/18/2012 07:36 AM, Mehrdad wrote:
 Is it just me, or did I subvert the type system here?
 
 
 import std.stdio;
 
 struct Const
 {
     this(void delegate() increment)
     { this.increment = increment; }
     int a;
     void delegate() increment;
     void oops() const { this.increment(); }
 }
 
 void main()
 {
     Const c;
     c = Const({ c.a++; });
     writeln(c.a);
     c.oops();
     writeln(c.a);
 }
 

I don't think so. When calling oops you have two references to the object c: - The this-pointer of the object itself which is not allowed to change the object in the const-call. - The reference from within main which is allowed to change it and can be reached via the frame pointer of the delegate. I see this as perfectly valid code. Of course, opinions may differ here. Matthias

My trouble isn't with the delegate, it's with the const method. It's with the idea that "you can tell something about the code just by looking at it". The way I understood it, you can assume that a `const` method cannot modify an object, but... that doesn't seem to be the case here. What am I misunderstanding?
Jun 17 2012
prev sibling next sibling parent reply Matthias Walter <xammy xammy.homelinux.net> writes:
On 06/18/2012 08:04 AM, Mehrdad wrote:
 On Monday, 18 June 2012 at 06:00:11 UTC, Matthias Walter wrote:
 On 06/18/2012 07:36 AM, Mehrdad wrote:
 Is it just me, or did I subvert the type system here?


 import std.stdio;

 struct Const
 {
     this(void delegate() increment)
     { this.increment = increment; }
     int a;
     void delegate() increment;
     void oops() const { this.increment(); }
 }

 void main()
 {
     Const c;
     c = Const({ c.a++; });
     writeln(c.a);
     c.oops();
     writeln(c.a);
 }

I don't think so. When calling oops you have two references to the object c: - The this-pointer of the object itself which is not allowed to change the object in the const-call. - The reference from within main which is allowed to change it and can be reached via the frame pointer of the delegate. I see this as perfectly valid code. Of course, opinions may differ here. Matthias

My trouble isn't with the delegate, it's with the const method. It's with the idea that "you can tell something about the code just by looking at it". The way I understood it, you can assume that a `const` method cannot modify an object, but... that doesn't seem to be the case here. What am I misunderstanding?

Its not, that a const method cannot modify an object, it just ensures that the const method cannot modify the object *by using the this-pointer*. Other things cannot be ensured with const: For example, a const method could wake up a 2nd thread and proceed with a longer computation. Then this 2nd thread may have a reference to the object and may change it while your computation is going on.
Jun 17 2012
parent deadalnix <deadalnix gmail.com> writes:
Le 18/06/2012 09:58, Jonathan M Davis a écrit :
 On Monday, June 18, 2012 08:19:55 Mehrdad wrote:
 On Monday, 18 June 2012 at 06:14:22 UTC, Matthias Walter wrote:
 Its not, that a const method cannot modify an object, it just
 ensures that the const method cannot modify the object *by
 using the this-pointer*.

I see... So that means you /can't/ tell something just by looking at a part of the code, right? (Just mentioning this since this idea seemed to be emphasized a lot by D.)

You can if the function is pure as well, because then that delegate would not be legal. For instance, this code import std.stdio; struct Const { this(void delegate() pure increment) { this.increment = increment; } int a; void delegate() pure increment; void oops() const pure { this.increment(); } } void main() { Const c; c = Const({ c.a++; }); writeln(c.a); c.oops(); writeln(c.a); } fails to compile, giving this error: q.d(15): Error: constructor q.Const.this (void delegate() pure increment) is not callable using argument types (void delegate() nothrow safe) All const guarantees is that the object isn't altered through the const reference/pointer (which in the case of a const function is this). That's powerful, but it needs pure as well to really be able to just glance at it and know that it's not altering your object. If you want an extreme exampl. you could create an object whose entire state was held in a global variable, then the fact that the this pointer was const wouldn't mean much. But if the member functions were pure, then you couldn't access that global variable, and so that externalization of the state wouldn't work, and you'd be guaranteed that the const function didn't alter your object (as long as none of the arguments to that const function held a reference or pointer to that object anyway (though that's a fairly abnormal thing to do) - only strong purity absolutely guarantees that your object isn't being altered). - Jonathan M Davis

It would be true if the delegate wasn't stored in c. For instance, it is true if the delegate is passed as oops argument. But in our case, the type system is broken. See explanations in other posts I've made in this thread.
Jun 18 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 18 June 2012 at 06:14:22 UTC, Matthias Walter wrote:
 Its not, that a const method cannot modify an object, it just 
 ensures that the const method cannot modify the object *by 
 using the this-pointer*.

I see... So that means you /can't/ tell something just by looking at a part of the code, right? (Just mentioning this since this idea seemed to be emphasized a lot by D.)
Jun 17 2012
prev sibling next sibling parent Matthias Walter <xammy xammy.homelinux.net> writes:
On 06/18/2012 08:19 AM, Mehrdad wrote:
 On Monday, 18 June 2012 at 06:14:22 UTC, Matthias Walter wrote:
 Its not, that a const method cannot modify an object, it just
 ensures that the const method cannot modify the object *by using
 the this-pointer*.

I see... So that means you /can't/ tell something just by looking at a part of the code, right? (Just mentioning this since this idea seemed to be emphasized a lot by D.)

Yes, you are right with that.
Jun 17 2012
prev sibling next sibling parent reply "Mehrdad" <wfunction hotmail.com> writes:
Okay, how about this? http://ideone.com/VMlzS

Does this break const?


import std.stdio;
class S
{
         this(int a)
         {
                 this.a = a;
                 this.increment = { this.a++; };
         }
         int a;
         void delegate() increment;
         void oops() const { this.increment(); }
}
void main()
{
         auto c = new const(S)(0);
         writeln(c.a);
         c.oops();
         writeln(c.a);
}
Jun 18 2012
next sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 6/18/12 2:14 AM, Mehrdad wrote:
 Okay, how about this? http://ideone.com/VMlzS

 Does this break const?


 import std.stdio;
 class S
 {
 this(int a)
 {
 this.a = a;
 this.increment = { this.a++; };
 }
 int a;
 void delegate() increment;
 void oops() const { this.increment(); }
 }
 void main()
 {
 auto c = new const(S)(0);
 writeln(c.a);
 c.oops();
 writeln(c.a);
 }

Yes. Currently the constructor is not typechecked properly. Kenji has done some work on that and is still blocked by me and Walter with some questions. Andrei
Jun 18 2012
prev sibling next sibling parent reply deadalnix <deadalnix gmail.com> writes:
Le 18/06/2012 09:14, Mehrdad a écrit :
 Okay, how about this? http://ideone.com/VMlzS

 Does this break const?


 import std.stdio;
 class S
 {
 this(int a)
 {
 this.a = a;
 this.increment = { this.a++; };
 }
 int a;
 void delegate() increment;
 void oops() const { this.increment(); }
 }
 void main()
 {
 auto c = new const(S)(0);
 writeln(c.a);
 c.oops();
 writeln(c.a);
 }

Depending on how it is specified, I think you should either : - get an error when constructing c, because S isn't « constable ». (delegate type cannot be consted, but can be unconsted safely). - get an error when you try to call increment in oops, because the delegate type can't ensure the constness of the operation.
Jun 18 2012
next sibling parent reply deadalnix <deadalnix gmail.com> writes:
Le 19/06/2012 00:16, Artur Skawina a écrit :
 On 06/18/12 23:08, deadalnix wrote:
 Le 18/06/2012 09:14, Mehrdad a écrit :
 Okay, how about this? http://ideone.com/VMlzS

 Does this break const?


 import std.stdio;
 class S
 {
 this(int a)
 {
 this.a = a;
 this.increment = { this.a++; };
 }
 int a;
 void delegate() increment;
 void oops() const { this.increment(); }
 }
 void main()
 {
 auto c = new const(S)(0);
 writeln(c.a);
 c.oops();
 writeln(c.a);
 }

Depending on how it is specified, I think you should either : - get an error when constructing c, because S isn't « constable ». (delegate type cannot be consted, but can be unconsted safely). - get an error when you try to call increment in oops, because the delegate type can't ensure the constness of the operation.

I'm afraid that: - Banning implicit mutable->const conversions would do far more harm, so this would not be a good solution to the problem. - Requiring that the type of the delegate "ensure the constness" would be far too restrictive. (the question would be: "Is any data reachable via 'this' also reachable through the delegates context pointer?" and every such delegate would of course need to be "pure" [1]. [This isn't *just* about the above example, you get the same problems when a const object isn't created but received from somewhere.] artur [1] which is a misnomer.

Due to D concept of weak purity, this doesn't ensure the required what we need here. It is possible to get the error when trying to call the delegate instead of preventing to make it const, as I said in the post you quote. It is probably a better solution.
Jun 19 2012
next sibling parent reply deadalnix <deadalnix gmail.com> writes:
Le 19/06/2012 14:30, Artur Skawina a écrit :
 Due to D concept of weak purity, this doesn't ensure the required what we need
here.

Actually, it does - if it can be proved that the delegate can't alter the object via the context pointer (eg because whatever it points to is not mutable) then even D's "pure" is enough. Because the delegate would need to be passed a mutable ref to be able to alter the object, which then could hardly be constructed as "breaking" constness. But such a limit (const/immutable context) would be a problem for the cases where the delegate needs to alter some state (like export the result of some operation), but does _not_ modify the object it's embedded in. Note that the current object may very well be reachable (and mutable) from the delegate - but at some point you have to trust the programmer. Sure, fixing this hole would be great, but /how/ - w/o incurring unacceptable collateral damage?

This isn't a problem as long as the delegate isn't a member of the object. If it is, transitivity is broken, which is something you don't want. Relying on the trust on the programmer is a dumb idea. Human do mistake, way more than computers. The basic behavior MUST be a safe one. Transitivity has been introduced for good reason and language already provide a way to break it via cast. So it is unacceptable to rely on programmer on that point.
 It is possible to get the error when trying to call the delegate instead of
preventing to make it const, as I said in the post you quote. It is probably a
better solution.

Any delegate?

No, any delegate that have type that isn't covariant with the expected delegate type.
Jun 19 2012
next sibling parent deadalnix <deadalnix gmail.com> writes:
Le 19/06/2012 16:16, Artur Skawina a écrit :
 On 06/19/12 15:29, deadalnix wrote:
 Le 19/06/2012 14:30, Artur Skawina a écrit :
 Due to D concept of weak purity, this doesn't ensure the required what we need
here.

Actually, it does - if it can be proved that the delegate can't alter the object via the context pointer (eg because whatever it points to is not mutable) then even D's "pure" is enough. Because the delegate would need to be passed a mutable ref to be able to alter the object, which then could hardly be constructed as "breaking" constness. But such a limit (const/immutable context) would be a problem for the cases where the delegate needs to alter some state (like export the result of some operation), but does _not_ modify the object it's embedded in. Note that the current object may very well be reachable (and mutable) from the delegate - but at some point you have to trust the programmer. Sure, fixing this hole would be great, but /how/ - w/o incurring unacceptable collateral damage?

This isn't a problem as long as the delegate isn't a member of the object. If it is, transitivity is broken, which is something you don't want. Relying on the trust on the programmer is a dumb idea. Human do mistake, way more than computers. The basic behavior MUST be a safe one. Transitivity has been introduced for good reason and language already provide a way to break it via cast. So it is unacceptable to rely on programmer on that point.
 It is possible to get the error when trying to call the delegate instead of
preventing to make it const, as I said in the post you quote. It is probably a
better solution.

Any delegate?

No, any delegate that have type that isn't covariant with the expected delegate type.

struct S { int i; this(int i) { this.i = i; } T* p; void f(int i) { this.i = i; /*p.i++;*/ } } struct T { int i; this(int i) { this.i = i; } void delegate(int i) f; } void main() { auto t = new T(42); auto s = new S(17); s.p = t; t.f =&s.f; f(t); } void f(const (T)* t) { t.f(t.i*2); } You're proposing to make the last 'f' function illegal, just because the commented out part could happen. I'm saying that this is unlikely to happen *by accident*, and of course would still be possible by casting away the constness.

I think you are overcomplicating things. Yes, the call would be illegal.
 But banning "unsafe" delegates would result in casts *when using "safe" ones*
 - which is not a real improvement because this would make the "bad" casts much
 harder to spot.

I think you don't understand the benefice of transitive type qualifier.
Jun 19 2012
prev sibling next sibling parent reply travert phare.normalesup.org (Christophe Travert) writes:
Artur Skawina , dans le message (digitalmars.D:170175), a crit:
 On 06/19/12 15:29, deadalnix wrote:
 Le 19/06/2012 14:30, Artur Skawina a crit :
 Due to D concept of weak purity, this doesn't ensure the required what we need
here.

Actually, it does - if it can be proved that the delegate can't alter the object via the context pointer (eg because whatever it points to is not mutable) then even D's "pure" is enough. Because the delegate would need to be passed a mutable ref to be able to alter the object, which then could hardly be constructed as "breaking" constness. But such a limit (const/immutable context) would be a problem for the cases where the delegate needs to alter some state (like export the result of some operation), but does _not_ modify the object it's embedded in. Note that the current object may very well be reachable (and mutable) from the delegate - but at some point you have to trust the programmer. Sure, fixing this hole would be great, but /how/ - w/o incurring unacceptable collateral damage?

This isn't a problem as long as the delegate isn't a member of the object. If it is, transitivity is broken, which is something you don't want. Relying on the trust on the programmer is a dumb idea. Human do mistake, way more than computers. The basic behavior MUST be a safe one. Transitivity has been introduced for good reason and language already provide a way to break it via cast. So it is unacceptable to rely on programmer on that point.
 It is possible to get the error when trying to call the delegate instead of
preventing to make it const, as I said in the post you quote. It is probably a
better solution.

Any delegate?

No, any delegate that have type that isn't covariant with the expected delegate type.

struct S { int i; this(int i) { this.i = i; } T* p; void f(int i) { this.i = i; /*p.i++;*/ } } struct T { int i; this(int i) { this.i = i; } void delegate(int i) f; } void main() { auto t = new T(42); auto s = new S(17); s.p = t; t.f = &s.f; f(t); } void f(const (T)* t) { t.f(t.i*2); } You're proposing to make the last 'f' function illegal, just because the commented out part could happen. I'm saying that this is unlikely to happen *by accident*, and of course would still be possible by casting away the constness. But banning "unsafe" delegates would result in casts *when using "safe" ones* - which is not a real improvement because this would make the "bad" casts much harder to spot.

The proper way to do this is not cast, it is to give the proper constness for all types and methods : struct S { int i; this(int i) { this.i = i; } T* p; void f(int i) const { this.i = i; /*p.i++;*/ } // the commented part is illegal because S.f is const } struct T { int i; this(int i) { this.i = i; } void delegate(int i) const f; // T.f is const to be callable from .f } void main() { auto t = new T(42); auto s = new S(17); s.p = t; t.f = &s.f; // legal when T.f is const because S.f is also const f(t); } void f(const (T)* t) { t.f(t.i*2); // legal because T.f is const } -- Christophe
Jun 19 2012
next sibling parent reply deadalnix <deadalnix gmail.com> writes:
Le 19/06/2012 20:11, Artur Skawina a écrit :
 On 06/19/12 19:30, Christophe Travert wrote:
 Artur Skawina , dans le message (digitalmars.D:170175), a écrit :
 On 06/19/12 15:29, deadalnix wrote:
 Le 19/06/2012 14:30, Artur Skawina a écrit :
 Due to D concept of weak purity, this doesn't ensure the required what we need
here.

Actually, it does - if it can be proved that the delegate can't alter the object via the context pointer (eg because whatever it points to is not mutable) then even D's "pure" is enough. Because the delegate would need to be passed a mutable ref to be able to alter the object, which then could hardly be constructed as "breaking" constness. But such a limit (const/immutable context) would be a problem for the cases where the delegate needs to alter some state (like export the result of some operation), but does _not_ modify the object it's embedded in. Note that the current object may very well be reachable (and mutable) from the delegate - but at some point you have to trust the programmer. Sure, fixing this hole would be great, but /how/ - w/o incurring unacceptable collateral damage?

This isn't a problem as long as the delegate isn't a member of the object. If it is, transitivity is broken, which is something you don't want. Relying on the trust on the programmer is a dumb idea. Human do mistake, way more than computers. The basic behavior MUST be a safe one. Transitivity has been introduced for good reason and language already provide a way to break it via cast. So it is unacceptable to rely on programmer on that point.
 It is possible to get the error when trying to call the delegate instead of
preventing to make it const, as I said in the post you quote. It is probably a
better solution.

Any delegate?

No, any delegate that have type that isn't covariant with the expected delegate type.

struct S { int i; this(int i) { this.i = i; } T* p; void f(int i) { this.i = i; /*p.i++;*/ } } struct T { int i; this(int i) { this.i = i; } void delegate(int i) f; } void main() { auto t = new T(42); auto s = new S(17); s.p = t; t.f =&s.f; f(t); } void f(const (T)* t) { t.f(t.i*2); } You're proposing to make the last 'f' function illegal, just because the commented out part could happen. I'm saying that this is unlikely to happen *by accident*, and of course would still be possible by casting away the constness. But banning "unsafe" delegates would result in casts *when using "safe" ones* - which is not a real improvement because this would make the "bad" casts much harder to spot.

The proper way to do this is not cast, it is to give the proper constness for all types and methods : struct S { int i; this(int i) { this.i = i; } T* p; void f(int i) const { this.i = i; /*p.i++;*/ } // the commented part is illegal because S.f is const

Not just the commented part.
     }
     struct T {
        int i; this(int i) { this.i = i; }
        void delegate(int i) const f;
    // T.f is const to be callable from .f
     }

     void main() {
        auto t = new T(42);
        auto s = new S(17);
        s.p = t;
        t.f =&s.f; // legal when T.f is const because S.f is also const

Only a const T.f would be pointless. Like I've already said twice in this thread - it *can* be done (the function has to be "pure" too for it to work), but certain delegate uses, which are OK now, would be forbidden.

Once again, this is inconsistent with how purity is defined elsewhere.
 I'm all for fixing this hole - it's just that the proposed "fix" would
 have consequences, which can't simply be ignored.

They are not ignored, but it seems you don't clearly understand the implications and the big picture.
Jun 20 2012
parent reply travert phare.normalesup.org (Christophe Travert) writes:
deadalnix , dans le message (digitalmars.D:170262), a crit:
 Once again, this is inconsistent with how purity is defined elsewhere.
 
 I'm all for fixing this hole - it's just that the proposed "fix" would
 have consequences, which can't simply be ignored.

They are not ignored, but it seems you don't clearly understand the implications and the big picture.

Let me stress that argument: It has been said in several places in the newsgroup that D had many features that work right, but now problems emerge when feature interacts with each other. That is why consistency in the langage design is *very* important. Implementing delegates properly with constness of the context pointer, and a consistent pure attribute is a way that garanties that delegates interacts finely with all other feature (constness, purity, etc.): delegates are implementable has classique structures with an opApply like I showed in a post in this thread. There is no black magic (1), an thus delegates are guaranted not add any hole in the langage: delegates are only a very convenient way to do what you can do with a context pointer and a function pointer. (1) there is still a little bit of magic: closure makes automatic heap allocation of normally stack variables. Since you could do that explicitely, this is no big problem. The only way to make this magic disappear is to make heap allocation of normally stack variables legal in other cases, which would cover another big hole in the langage but this is too big a change in the langage to be accepted for the moment. -- Christophe
Jun 20 2012
parent reply deadalnix <deadalnix gmail.com> writes:
Le 20/06/2012 10:18, Christophe Travert a crit :
 deadalnix , dans le message (digitalmars.D:170262), a crit :
 Once again, this is inconsistent with how purity is defined elsewhere.

 I'm all for fixing this hole - it's just that the proposed "fix" would
 have consequences, which can't simply be ignored.

They are not ignored, but it seems you don't clearly understand the implications and the big picture.

Let me stress that argument: It has been said in several places in the newsgroup that D had many features that work right, but now problems emerge when feature interacts with each other. That is why consistency in the langage design is *very* important. Implementing delegates properly with constness of the context pointer, and a consistent pure attribute is a way that garanties that delegates interacts finely with all other feature (constness, purity, etc.): delegates are implementable has classique structures with an opApply like I showed in a post in this thread. There is no black magic (1), an thus delegates are guaranted not add any hole in the langage: delegates are only a very convenient way to do what you can do with a context pointer and a function pointer. (1) there is still a little bit of magic: closure makes automatic heap allocation of normally stack variables. Since you could do that explicitely, this is no big problem. The only way to make this magic disappear is to make heap allocation of normally stack variables legal in other cases, which would cover another big hole in the langage but this is too big a change in the langage to be accepted for the moment.

What is the conclusion here ?
Jun 20 2012
parent reply travert phare.normalesup.org (Christophe Travert) writes:
deadalnix , dans le message (digitalmars.D:170272), a crit:
 Le 20/06/2012 10:18, Christophe Travert a crit :
 What is the conclusion here ?

I think it is the same as yours. My conclusion is that delegate's signature should be the same has methods's signature, and have the same meaning. That makes the langage consistent, and makes sure not to bring any new hole in the langage. In detail, they should be able to have a const or immutable attribute for their frame pointer, just like methods can be const or immutable with regard to the this argument, and a pure attribute that has the same meaning as methods pure attribute: no global variable access (and no impure function calls), but possibility to access the frame pointer. -- Christophe
Jun 20 2012
parent reply deadalnix <deadalnix gmail.com> writes:
Le 20/06/2012 11:28, Christophe Travert a crit :
 deadalnix , dans le message (digitalmars.D:170272), a crit :
 Le 20/06/2012 10:18, Christophe Travert a crit :
 What is the conclusion here ?

I think it is the same as yours. My conclusion is that delegate's signature should be the same has methods's signature, and have the same meaning. That makes the langage consistent, and makes sure not to bring any new hole in the langage. In detail, they should be able to have a const or immutable attribute for their frame pointer, just like methods can be const or immutable with regard to the this argument, and a pure attribute that has the same meaning as methods pure attribute: no global variable access (and no impure function calls), but possibility to access the frame pointer.

This is definitively my point too.
Jun 20 2012
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 06/20/2012 02:32 PM, deadalnix wrote:
 Le 20/06/2012 11:28, Christophe Travert a crit :
 deadalnix , dans le message (digitalmars.D:170272), a crit :
 Le 20/06/2012 10:18, Christophe Travert a crit :
 What is the conclusion here ?

I think it is the same as yours. My conclusion is that delegate's signature should be the same has methods's signature, and have the same meaning. That makes the langage consistent, and makes sure not to bring any new hole in the langage. In detail, they should be able to have a const or immutable attribute for their frame pointer, just like methods can be const or immutable with regard to the this argument, and a pure attribute that has the same meaning as methods pure attribute: no global variable access (and no impure function calls), but possibility to access the frame pointer.

This is definitively my point too.

+1.
Jun 20 2012
prev sibling next sibling parent reply travert phare.normalesup.org (Christophe Travert) writes:
Artur Skawina , dans le message (digitalmars.D:170191), a crit:
 The proper way to do this is not cast, it is to give the proper 
 constness for all types and methods :
 
    struct S {
       int i; this(int i) { this.i = i; }
       T* p; 
       void f(int i) const { this.i = i; /*p.i++;*/ }
 // the commented part is illegal because S.f is const


I missed that. Then I disagree with your use of delegates. The solution here is to make the free f function to take a non const T*, since the function tries to modify variables from that T instance (via a delegate). I don't mind if the delegate modify an S or a T instance: it should not modify anything. You use a delegate to store an S structure that you can modifiy in a const T. This is only one step to consider the following acceptable: struct T { ref int delegate() _i; this(int i_) { _i = () => i_ } property ref int i() const { return _i(); } } void foo(const T*) { T.i = 10; } Would you say that this is not a problem, since you do not modify a T instance with that delegate? I prefer to legalise: struct T { mutable int i; // makes modifying i legal, but prevent instanciating // an immutable T and several optimizations }
 Only a const T.f would be pointless.

 Like I've already said twice in this thread - it *can* be done (the
 function has to be "pure" too for it to work), but certain delegate
 uses, which are OK now, would be forbidden.

 I'm all for fixing this hole - it's just that the proposed "fix" would
 have consequences, which can't simply be ignored.
 
 Language design is not a game of whack-a-mole, where you ban random
 features left and right, because you think you've noticed a problem,
 without properly identifying it nor spending even a single second
 figuring out the implications.

I completely agree with that. And I'm fine with the current situation: you trust the programmer not to use delegates to break the const system until we have a proper system to deal with this (inference of constness of delegates, for a start). But encouraging the use of delegates to modify variables from a const instance seems wrong to me. -- Christophe
Jun 20 2012
next sibling parent reply travert phare.normalesup.org (Christophe Travert) writes:
Artur Skawina , dans le message (digitalmars.D:170305), a crit:
 On 06/20/12 09:31, Christophe Travert wrote:
 Artur Skawina , dans le message (digitalmars.D:170191), a crit :
 The proper way to do this is not cast, it is to give the proper 
 constness for all types and methods :

    struct S {
       int i; this(int i) { this.i = i; }
       T* p; 
       void f(int i) const { this.i = i; /*p.i++;*/ }
 // the commented part is illegal because S.f is const


I missed that. Then I disagree with your use of delegates. The solution here is to make the free f function to take a non const T*, since the function tries to modify variables from that T instance (via a delegate).

The issue is the delegate definition - what exactly is a delegate? My first message in this thread started with this sentence: "It's fine, if you view a delegate as opaque". Because if you do, then accesses via a delegates context pointer are no different from using an external reference. That message also contained a program to show how trivial bypassing const is, at least for impure functions. So banning "unsafe" delegates wouldn't really change the situation.

Having opaque delegate and no holes in the langage is a possibility, but not without changing the langage spec. With opaque delegates, you can't have a pure delegate, only pure function pointers, because pure means you do not access external data, and accessing data pointed by a the opaque frame pointer of the delegate would be accessing external data. You may then redefine pure for delegate as delegates having only immutable external data (const is not enough). I'm definitely not in favor of that solution.
Jun 20 2012
next sibling parent deadalnix <deadalnix gmail.com> writes:
Le 20/06/2012 15:58, Christophe Travert a crit :
 Artur Skawina , dans le message (digitalmars.D:170305), a crit :
 On 06/20/12 09:31, Christophe Travert wrote:
 Artur Skawina , dans le message (digitalmars.D:170191), a crit :
 The proper way to do this is not cast, it is to give the proper
 constness for all types and methods :

     struct S {
        int i; this(int i) { this.i = i; }
        T* p;
        void f(int i) const { this.i = i; /*p.i++;*/ }
 // the commented part is illegal because S.f is const


I missed that. Then I disagree with your use of delegates. The solution here is to make the free f function to take a non const T*, since the function tries to modify variables from that T instance (via a delegate).

The issue is the delegate definition - what exactly is a delegate? My first message in this thread started with this sentence: "It's fine, if you view a delegate as opaque". Because if you do, then accesses via a delegates context pointer are no different from using an external reference. That message also contained a program to show how trivial bypassing const is, at least for impure functions. So banning "unsafe" delegates wouldn't really change the situation.

Having opaque delegate and no holes in the langage is a possibility, but not without changing the langage spec. With opaque delegates, you can't have a pure delegate, only pure function pointers, because pure means you do not access external data, and accessing data pointed by a the opaque frame pointer of the delegate would be accessing external data. You may then redefine pure for delegate as delegates having only immutable external data (const is not enough). I'm definitely not in favor of that solution.

Non, opaque delegate, whatever it means, means breaking immutability and shared transitivity. Which is a 100% NO. Introducing implicit sharing would be a major step backward in D design.
Jun 20 2012
prev sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 06/20/2012 08:02 PM, Artur Skawina wrote:
 On 06/20/12 15:58, Christophe Travert wrote:
 Artur Skawina , dans le message (digitalmars.D:170305), a écrit :
 On 06/20/12 09:31, Christophe Travert wrote:
 Artur Skawina , dans le message (digitalmars.D:170191), a écrit :
 The proper way to do this is not cast, it is to give the proper
 constness for all types and methods :

     struct S {
        int i; this(int i) { this.i = i; }
        T* p;
        void f(int i) const { this.i = i; /*p.i++;*/ }
 // the commented part is illegal because S.f is const


I missed that. Then I disagree with your use of delegates. The solution here is to make the free f function to take a non const T*, since the function tries to modify variables from that T instance (via a delegate).

The issue is the delegate definition - what exactly is a delegate? My first message in this thread started with this sentence: "It's fine, if you view a delegate as opaque". Because if you do, then accesses via a delegates context pointer are no different from using an external reference. That message also contained a program to show how trivial bypassing const is, at least for impure functions. So banning "unsafe" delegates wouldn't really change the situation.

Having opaque delegate and no holes in the langage is a possibility, but not without changing the langage spec. With opaque delegates, you can't have a pure delegate, only pure function pointers, because pure means you do not access external data, and accessing data pointed by a the opaque frame pointer of the delegate would be accessing external data. You may then redefine pure for delegate as delegates having only immutable external data (const is not enough). I'm definitely not in favor of that solution.

I agree with all of the above, maybe except the last sentence. The choice is between a) status quo, where the delegates are treated similarly to impure free functions, and b) handled "correctly". With scheme "a" I would expect to encounter genuine bugs extremely rarely, if ever; while if "b" would be enforced, the bugs directly or indirectly caused by working around the limitations would be plenty... Having a "cleaner" design, but one that results in more bugs is not really an improvement. Maybe there is a middle ground.

The type system must be sound. Some type system constraints can be loosened, however.
 Anyway, that problem is not likely to get fixed quickly. In the mean time,
 there are serious compiler bugs related to the thread subject, which likely
 could. For example:

     import std.stdio;

     struct S {
        int i; this(int i) { this.i = i; }
        auto mutable() pure { return&i; }
        void iAintAfraidOfNoConst() const pure { *(&mutable)()=-1813; }
     }

     void main() {
        auto s = new immutable(S)(42);
        writeln(*s);
        s.iAintAfraidOfNoConst();
        writeln(*s);
     }

 The '&const(this).mutable' operation is clearly bogus and should not be
 allowed. But the compiler happily creates a delegate from a method and
 an incompatible instance reference, which in this case results in 'const'
 being dropped.

 (I have no DMD

http://dlang.org/download.html
 - can't test if the problem is still there, hence can't
 file a bug report)

 artur

It is still there.
Jun 20 2012
prev sibling parent Artur Skawina <art.08.09 gmail.com> writes:
On 06/20/12 15:58, Christophe Travert wrote:
 Artur Skawina , dans le message (digitalmars.D:170305), a écrit :
 On 06/20/12 09:31, Christophe Travert wrote:
 Artur Skawina , dans le message (digitalmars.D:170191), a écrit :
 The proper way to do this is not cast, it is to give the proper 
 constness for all types and methods :

    struct S {
       int i; this(int i) { this.i = i; }
       T* p; 
       void f(int i) const { this.i = i; /*p.i++;*/ }
 // the commented part is illegal because S.f is const


I missed that. Then I disagree with your use of delegates. The solution here is to make the free f function to take a non const T*, since the function tries to modify variables from that T instance (via a delegate).

The issue is the delegate definition - what exactly is a delegate? My first message in this thread started with this sentence: "It's fine, if you view a delegate as opaque". Because if you do, then accesses via a delegates context pointer are no different from using an external reference. That message also contained a program to show how trivial bypassing const is, at least for impure functions. So banning "unsafe" delegates wouldn't really change the situation.

Having opaque delegate and no holes in the langage is a possibility, but not without changing the langage spec. With opaque delegates, you can't have a pure delegate, only pure function pointers, because pure means you do not access external data, and accessing data pointed by a the opaque frame pointer of the delegate would be accessing external data. You may then redefine pure for delegate as delegates having only immutable external data (const is not enough). I'm definitely not in favor of that solution.

I agree with all of the above, maybe except the last sentence. The choice is between a) status quo, where the delegates are treated similarly to impure free functions, and b) handled "correctly". With scheme "a" I would expect to encounter genuine bugs extremely rarely, if ever; while if "b" would be enforced, the bugs directly or indirectly caused by working around the limitations would be plenty... Having a "cleaner" design, but one that results in more bugs is not really an improvement. Maybe there is a middle ground. Anyway, that problem is not likely to get fixed quickly. In the mean time, there are serious compiler bugs related to the thread subject, which likely could. For example: import std.stdio; struct S { int i; this(int i) { this.i = i; } auto mutable() pure { return &i; } void iAintAfraidOfNoConst() const pure { *(&mutable)()=-1813; } } void main() { auto s = new immutable(S)(42); writeln(*s); s.iAintAfraidOfNoConst(); writeln(*s); } The '&const(this).mutable' operation is clearly bogus and should not be allowed. But the compiler happily creates a delegate from a method and an incompatible instance reference, which in this case results in 'const' being dropped. (I have no DMD - can't test if the problem is still there, hence can't file a bug report) artur
Jun 20 2012
prev sibling parent Artur Skawina <art.08.09 gmail.com> writes:
On 06/20/12 09:31, Christophe Travert wrote:
 Artur Skawina , dans le message (digitalmars.D:170191), a écrit :
 The proper way to do this is not cast, it is to give the proper 
 constness for all types and methods :

    struct S {
       int i; this(int i) { this.i = i; }
       T* p; 
       void f(int i) const { this.i = i; /*p.i++;*/ }
 // the commented part is illegal because S.f is const


I missed that. Then I disagree with your use of delegates. The solution here is to make the free f function to take a non const T*, since the function tries to modify variables from that T instance (via a delegate).

The issue is the delegate definition - what exactly is a delegate? My first message in this thread started with this sentence: "It's fine, if you view a delegate as opaque". Because if you do, then accesses via a delegates context pointer are no different from using an external reference. That message also contained a program to show how trivial bypassing const is, at least for impure functions. So banning "unsafe" delegates wouldn't really change the situation. I could, in theory, agree with "fixing" the delegates, they can be used to break the type system. (In more serious ways than discussed here; but I'm trying to avoid talking about it, as I'd expect the proposed short-term fixes, that would appear here, to be unacceptable). But let's assume that the delegates are correctly typed (both signature and ctx). Now you would need to have mutable overloads for many cases where 'const' and 'inout' methods work now. There are other ways to bypass const if somebody really wants to, so is the extra cost justified, and wouldn't it result in _more_ bugs due to the extra code required? The compiler would have to learn to handle cases like this: struct S { int i; this(int i) { this.i = i; } void delegate(int i) dg; void f() inout { dg(i); } } void main() { int j; auto s = new S(42); s.dg = (int i){ j += i; }; f(s); } void f(const(S)* s) { s.f(); } which would be illegal. Because it could *potentially* be abused. Except you can do that w/o ever using a delegate, so where's the gain? "Pure" (in d-speak) const methods would benefit - yes; but is that enough?
 I don't mind if the delegate modify an S or a T instance: it should not 
 modify anything. You use a delegate to store an S structure that you can 
 modifiy in a const T. This is only one step to consider the following 
 acceptable:
 
 struct T {
   ref int delegate() _i;
   this(int i_) { _i = () => i_ }
    property ref int i() const { return _i(); }
 }
 
 void foo(const T*)
 {
   T.i = 10;
 }
 
 Would you say that this is not a problem, since you do not modify a T 
 instance with that delegate?
 
 I prefer to legalise:
 struct T
 {
   mutable int i; // makes modifying i legal, but prevent instanciating 
                  // an immutable T and several optimizations
 }

struct T; void f(const(T)*); void g(T* t) { f(t); }
 Language design is not a game of whack-a-mole, where you ban random
 features left and right, because you think you've noticed a problem,
 without properly identifying it nor spending even a single second
 figuring out the implications.

I completely agree with that. And I'm fine with the current situation:

That was a generic comment, btw; not a direct response to this thread.
 you trust the programmer not to use delegates to break the const system 
 until we have a proper system to deal with this (inference of constness 
 of delegates, for a start). But encouraging the use of delegates to 
 modify variables from a const instance seems wrong to me.

I don't think anybody suggested it's OK; I'd consider code like in my example to be broken, because it's confusing and a const method may not expect the object to change. But 'const' only gives you a R/O view, it does *not* make the object immutable, unless you hold the only reference. Which you can not assume to be true in general, except when it's a new object, or otherwise tagged as 'unique'. artur
Jun 20 2012
prev sibling parent Artur Skawina <art.08.09 gmail.com> writes:
On 06/19/12 19:30, Christophe Travert wrote:
 Artur Skawina , dans le message (digitalmars.D:170175), a écrit :
 On 06/19/12 15:29, deadalnix wrote:
 Le 19/06/2012 14:30, Artur Skawina a écrit :
 Due to D concept of weak purity, this doesn't ensure the required what we need
here.

Actually, it does - if it can be proved that the delegate can't alter the object via the context pointer (eg because whatever it points to is not mutable) then even D's "pure" is enough. Because the delegate would need to be passed a mutable ref to be able to alter the object, which then could hardly be constructed as "breaking" constness. But such a limit (const/immutable context) would be a problem for the cases where the delegate needs to alter some state (like export the result of some operation), but does _not_ modify the object it's embedded in. Note that the current object may very well be reachable (and mutable) from the delegate - but at some point you have to trust the programmer. Sure, fixing this hole would be great, but /how/ - w/o incurring unacceptable collateral damage?

This isn't a problem as long as the delegate isn't a member of the object. If it is, transitivity is broken, which is something you don't want. Relying on the trust on the programmer is a dumb idea. Human do mistake, way more than computers. The basic behavior MUST be a safe one. Transitivity has been introduced for good reason and language already provide a way to break it via cast. So it is unacceptable to rely on programmer on that point.
 It is possible to get the error when trying to call the delegate instead of
preventing to make it const, as I said in the post you quote. It is probably a
better solution.

Any delegate?

No, any delegate that have type that isn't covariant with the expected delegate type.

struct S { int i; this(int i) { this.i = i; } T* p; void f(int i) { this.i = i; /*p.i++;*/ } } struct T { int i; this(int i) { this.i = i; } void delegate(int i) f; } void main() { auto t = new T(42); auto s = new S(17); s.p = t; t.f = &s.f; f(t); } void f(const (T)* t) { t.f(t.i*2); } You're proposing to make the last 'f' function illegal, just because the commented out part could happen. I'm saying that this is unlikely to happen *by accident*, and of course would still be possible by casting away the constness. But banning "unsafe" delegates would result in casts *when using "safe" ones* - which is not a real improvement because this would make the "bad" casts much harder to spot.

The proper way to do this is not cast, it is to give the proper constness for all types and methods : struct S { int i; this(int i) { this.i = i; } T* p; void f(int i) const { this.i = i; /*p.i++;*/ } // the commented part is illegal because S.f is const

Not just the commented part.
    }
    struct T {
       int i; this(int i) { this.i = i; }
       void delegate(int i) const f;
   // T.f is const to be callable from .f
    }
 
    void main() {
       auto t = new T(42);
       auto s = new S(17);
       s.p = t;
       t.f = &s.f; // legal when T.f is const because S.f is also const

Only a const T.f would be pointless. Like I've already said twice in this thread - it *can* be done (the function has to be "pure" too for it to work), but certain delegate uses, which are OK now, would be forbidden. I'm all for fixing this hole - it's just that the proposed "fix" would have consequences, which can't simply be ignored. Language design is not a game of whack-a-mole, where you ban random features left and right, because you think you've noticed a problem, without properly identifying it nor spending even a single second figuring out the implications. artur
Jun 19 2012
prev sibling parent Artur Skawina <art.08.09 gmail.com> writes:
On 06/19/12 15:29, deadalnix wrote:
 Le 19/06/2012 14:30, Artur Skawina a écrit :
 Due to D concept of weak purity, this doesn't ensure the required what we need
here.

Actually, it does - if it can be proved that the delegate can't alter the object via the context pointer (eg because whatever it points to is not mutable) then even D's "pure" is enough. Because the delegate would need to be passed a mutable ref to be able to alter the object, which then could hardly be constructed as "breaking" constness. But such a limit (const/immutable context) would be a problem for the cases where the delegate needs to alter some state (like export the result of some operation), but does _not_ modify the object it's embedded in. Note that the current object may very well be reachable (and mutable) from the delegate - but at some point you have to trust the programmer. Sure, fixing this hole would be great, but /how/ - w/o incurring unacceptable collateral damage?

This isn't a problem as long as the delegate isn't a member of the object. If it is, transitivity is broken, which is something you don't want. Relying on the trust on the programmer is a dumb idea. Human do mistake, way more than computers. The basic behavior MUST be a safe one. Transitivity has been introduced for good reason and language already provide a way to break it via cast. So it is unacceptable to rely on programmer on that point.
 It is possible to get the error when trying to call the delegate instead of
preventing to make it const, as I said in the post you quote. It is probably a
better solution.

Any delegate?

No, any delegate that have type that isn't covariant with the expected delegate type.

struct S { int i; this(int i) { this.i = i; } T* p; void f(int i) { this.i = i; /*p.i++;*/ } } struct T { int i; this(int i) { this.i = i; } void delegate(int i) f; } void main() { auto t = new T(42); auto s = new S(17); s.p = t; t.f = &s.f; f(t); } void f(const (T)* t) { t.f(t.i*2); } You're proposing to make the last 'f' function illegal, just because the commented out part could happen. I'm saying that this is unlikely to happen *by accident*, and of course would still be possible by casting away the constness. But banning "unsafe" delegates would result in casts *when using "safe" ones* - which is not a real improvement because this would make the "bad" casts much harder to spot. artur
Jun 19 2012
prev sibling parent Artur Skawina <art.08.09 gmail.com> writes:
On 06/19/12 09:59, deadalnix wrote:
 Le 19/06/2012 00:16, Artur Skawina a écrit :
 On 06/18/12 23:08, deadalnix wrote:
 Le 18/06/2012 09:14, Mehrdad a écrit :
 Okay, how about this? http://ideone.com/VMlzS

 Does this break const?


 import std.stdio;
 class S
 {
 this(int a)
 {
 this.a = a;
 this.increment = { this.a++; };
 }
 int a;
 void delegate() increment;
 void oops() const { this.increment(); }
 }
 void main()
 {
 auto c = new const(S)(0);
 writeln(c.a);
 c.oops();
 writeln(c.a);
 }

Depending on how it is specified, I think you should either : - get an error when constructing c, because S isn't « constable ». (delegate type cannot be consted, but can be unconsted safely). - get an error when you try to call increment in oops, because the delegate type can't ensure the constness of the operation.

I'm afraid that: - Banning implicit mutable->const conversions would do far more harm, so this would not be a good solution to the problem. - Requiring that the type of the delegate "ensure the constness" would be far too restrictive. (the question would be: "Is any data reachable via 'this' also reachable through the delegates context pointer?" and every such delegate would of course need to be "pure" [1]. [This isn't *just* about the above example, you get the same problems when a const object isn't created but received from somewhere.] artur [1] which is a misnomer.

Due to D concept of weak purity, this doesn't ensure the required what we need here.

Actually, it does - if it can be proved that the delegate can't alter the object via the context pointer (eg because whatever it points to is not mutable) then even D's "pure" is enough. Because the delegate would need to be passed a mutable ref to be able to alter the object, which then could hardly be constructed as "breaking" constness. But such a limit (const/immutable context) would be a problem for the cases where the delegate needs to alter some state (like export the result of some operation), but does _not_ modify the object it's embedded in. Note that the current object may very well be reachable (and mutable) from the delegate - but at some point you have to trust the programmer. Sure, fixing this hole would be great, but /how/ - w/o incurring unacceptable collateral damage?
 It is possible to get the error when trying to call the delegate instead of
preventing to make it const, as I said in the post you quote. It is probably a
better solution.

Any delegate? On 06/19/12 10:18, Don Clugston wrote:
 On 18/06/12 17:00, Artur Skawina wrote:
 D's "weak pure" can help; I just don't like the redefinition of the term
 "purity"; another name for "weak purity" would be better.

So would I. Can you think of one? It was the best name I could come up with, given that the 'pure' was the keyword. We want a word that means 'no hidden state'.

No, I can't think of an appropriate term either, from the current keyword list. Plus, inventing yet another one just leads to function qualifier hell. But, as it is, you're not able to mark a function as ("stronly") pure if it takes const args w/o resorting to pragmas. Immutable data is not always the solution. And having almost everything marked as "pure", when all it does is it prevents global/static accesses and does not influence codegen in any way, only creates confusion. artur
Jun 19 2012
prev sibling parent Artur Skawina <art.08.09 gmail.com> writes:
On 06/18/12 23:08, deadalnix wrote:
 Le 18/06/2012 09:14, Mehrdad a écrit :
 Okay, how about this? http://ideone.com/VMlzS

 Does this break const?


 import std.stdio;
 class S
 {
 this(int a)
 {
 this.a = a;
 this.increment = { this.a++; };
 }
 int a;
 void delegate() increment;
 void oops() const { this.increment(); }
 }
 void main()
 {
 auto c = new const(S)(0);
 writeln(c.a);
 c.oops();
 writeln(c.a);
 }

Depending on how it is specified, I think you should either : - get an error when constructing c, because S isn't « constable ». (delegate type cannot be consted, but can be unconsted safely). - get an error when you try to call increment in oops, because the delegate type can't ensure the constness of the operation.

I'm afraid that: - Banning implicit mutable->const conversions would do far more harm, so this would not be a good solution to the problem. - Requiring that the type of the delegate "ensure the constness" would be far too restrictive. (the question would be: "Is any data reachable via 'this' also reachable through the delegates context pointer?" and every such delegate would of course need to be "pure" [1]. [This isn't *just* about the above example, you get the same problems when a const object isn't created but received from somewhere.] artur [1] which is a misnomer.
Jun 18 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Monday, June 18, 2012 08:19:55 Mehrdad wrote:
 On Monday, 18 June 2012 at 06:14:22 UTC, Matthias Walter wrote:
 Its not, that a const method cannot modify an object, it just
 ensures that the const method cannot modify the object *by
 using the this-pointer*.

I see... So that means you /can't/ tell something just by looking at a part of the code, right? (Just mentioning this since this idea seemed to be emphasized a lot by D.)

You can if the function is pure as well, because then that delegate would not be legal. For instance, this code import std.stdio; struct Const { this(void delegate() pure increment) { this.increment = increment; } int a; void delegate() pure increment; void oops() const pure { this.increment(); } } void main() { Const c; c = Const({ c.a++; }); writeln(c.a); c.oops(); writeln(c.a); } fails to compile, giving this error: q.d(15): Error: constructor q.Const.this (void delegate() pure increment) is not callable using argument types (void delegate() nothrow safe) All const guarantees is that the object isn't altered through the const reference/pointer (which in the case of a const function is this). That's powerful, but it needs pure as well to really be able to just glance at it and know that it's not altering your object. If you want an extreme exampl. you could create an object whose entire state was held in a global variable, then the fact that the this pointer was const wouldn't mean much. But if the member functions were pure, then you couldn't access that global variable, and so that externalization of the state wouldn't work, and you'd be guaranteed that the const function didn't alter your object (as long as none of the arguments to that const function held a reference or pointer to that object anyway (though that's a fairly abnormal thing to do) - only strong purity absolutely guarantees that your object isn't being altered). - Jonathan M Davis
Jun 18 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 18 June 2012 at 07:58:50 UTC, Jonathan M Davis wrote:
 That's powerful, but it needs pure as well to really be able to 
 just glance at it and know that it's not altering your object.

I see, so I need pure for the first example; thanks. How about the second one though?
Jun 18 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 18 June 2012 at 07:58:50 UTC, Jonathan M Davis wrote:
 That's powerful, but it needs pure as well to really be able to 
 just glance at it and know that it's not altering your object.

I see, so I need pure for the first example; thanks. How about the second one though?
Jun 18 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Monday, June 18, 2012 09:14:59 Mehrdad wrote:
 Okay, how about this? http://ideone.com/VMlzS
 
 Does this break const?
 
 
 import std.stdio;
 class S
 {
          this(int a)
          {
                  this.a = a;
                  this.increment = { this.a++; };
          }
          int a;
          void delegate() increment;
          void oops() const { this.increment(); }
 }
 void main()
 {
          auto c = new const(S)(0);
          writeln(c.a);
          c.oops();
          writeln(c.a);
 }

I'm not sure. I believe that it gets away with it, because none of the member variable are actually const until the constructor has completed, in which case, you've managed to hide away a non-const reference to what is supposed to be a const object. Technically, I don't think that it breaks const, but it does seem rather off. However, if the object isn't actually const until the constructor is complete, then it _is_ perfectly legimate. Now, what worries me is that this seems to compile: import std.stdio; class S { this(int a) immutable { this.a = a; this.increment = { this.a++; }; } int a; void delegate() increment; void oops() const { this.increment(); } } void main() { auto c = new const(S)(0); writeln(c.a); c.oops(); writeln(c.a); } As I understand it, that should _not_ compile. An immutable constructor should disallow all mutation of member variables after their initial assignment within the constructor, but clearly that's not happening. It even allows adding ++a; on its own inside of the constructor, so it's not just an issue with the delegate either. - Jonathan M Davis
Jun 18 2012
prev sibling next sibling parent reply deadalnix <deadalnix gmail.com> writes:
Le 18/06/2012 07:36, Mehrdad a écrit :
 Is it just me, or did I subvert the type system here?

delegate fail to ensure transitivity of type qualifier. This is no news. This is however a big error. I proposed a fix to that by changing the semantic of the type qualifier depending on its position in the declaration, but didn't received much feedback at the time.
 import std.stdio;

 struct Const
 {
 this(void delegate() increment)
 { this.increment = increment; }
 int a;
 void delegate() increment;
 void oops() const { this.increment(); }
 }

 void main()
 {
 Const c;
 c = Const({ c.a++; });
 writeln(c.a);
 c.oops();
 writeln(c.a);
 }

Jun 18 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 6/18/12 7:43 AM, deadalnix wrote:
 Le 18/06/2012 07:36, Mehrdad a écrit :
 Is it just me, or did I subvert the type system here?

delegate fail to ensure transitivity of type qualifier. This is no news. This is however a big error. I proposed a fix to that by changing the semantic of the type qualifier depending on its position in the declaration, but didn't received much feedback at the time.

I don't think that's the correct explanation. Andrei
Jun 18 2012
parent reply deadalnix <deadalnix gmail.com> writes:
Le 18/06/2012 16:43, Andrei Alexandrescu a écrit :
 On 6/18/12 7:43 AM, deadalnix wrote:
 Le 18/06/2012 07:36, Mehrdad a écrit :
 Is it just me, or did I subvert the type system here?

delegate fail to ensure transitivity of type qualifier. This is no news. This is however a big error. I proposed a fix to that by changing the semantic of the type qualifier depending on its position in the declaration, but didn't received much feedback at the time.

I don't think that's the correct explanation. Andrei

I fail to understand what you mean here. This post ins't supposed to explain anything. I've however made other posts to explain why transitivity is broken when it comes to delegate.
Jun 18 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 6/18/12 9:45 AM, deadalnix wrote:
 Le 18/06/2012 16:43, Andrei Alexandrescu a écrit :
 On 6/18/12 7:43 AM, deadalnix wrote:
 Le 18/06/2012 07:36, Mehrdad a écrit :
 Is it just me, or did I subvert the type system here?

delegate fail to ensure transitivity of type qualifier. This is no news. This is however a big error. I proposed a fix to that by changing the semantic of the type qualifier depending on its position in the declaration, but didn't received much feedback at the time.

I don't think that's the correct explanation. Andrei

I fail to understand what you mean here. This post ins't supposed to explain anything.

Mehrdad posted a code sample and you replied to it with what I could only assume was an explanation of the code's behavior. I hope you'd agree it was a reasonable assumption, whether or not it was correct. The problem at work here is that "this" is typed incorrectly during the construction of a qualified object. It should go progressively from a "raw" to a "cooked" state, and cannot be passed to any function while raw.
 I've however made other posts to explain why
 transitivity is broken when it comes to delegate.

I looked at your posts through June and couldn't find such, so a couple of links would be great. Of course, bug reports would be even better! Andrei
Jun 18 2012
parent reply deadalnix <deadalnix gmail.com> writes:
Le 18/06/2012 16:51, Andrei Alexandrescu a écrit :
 Mehrdad posted a code sample and you replied to it with what I could
 only assume was an explanation of the code's behavior. I hope you'd
 agree it was a reasonable assumption, whether or not it was correct.

 The problem at work here is that "this" is typed incorrectly during the
 construction of a qualified object. It should go progressively from a
 "raw" to a "cooked" state, and cannot be passed to any function while raw.

 I've however made other posts to explain why
 transitivity is broken when it comes to delegate.

I looked at your posts through June and couldn't find such, so a couple of links would be great. Of course, bug reports would be even better!

I'm not sure you are talking about the proposal or the transitivity issue here. I'll explain the proposal below, for the explaination of why the transitivity is broken, see : http://forum.dlang.org/thread/ywispsasaylqscyuayae forum.dlang.org?page=2#post-jrn7sv:24jjj:241:40digitalmars.com To explain my proposal, I have to make several thing clear first. You have here 2 things to qualify : the delegate itself, and the hidden parameter. As type qualifier are transitive in D, qualify the delegate also transitively qualify the hidden parameter. The proposal was to use postfix qualifier notation to qualify the hidden parameter, and qualifier between return type and delegate keyword to qualify the delegate itself. My original proposal stated that the type qualifier before the return type qualify the return type, but this is another topic. To make is clearer, some examples : void delegate() const; // A mutable delegate that use const frame pointer (all variable accessed throw the frame pointer are made const). void const delegate(); // A const delegate that use a const frame pointer (transitivity). void const delegate() immutable; // A const delegate that use an immutable frame pointer (delegate can only access immutable data from the frame pointer). void immutable delegate() const; // Error, immutable data cannot refers to const data. Note that the implicit cast goes the other way around than usual. void delegate() const can be safely casted to void delegate(), but not the other way around, as you are allowed to not mutate mutable data, but not the other way around.
Jun 18 2012
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 06/18/2012 05:12 PM, deadalnix wrote:
 Le 18/06/2012 16:51, Andrei Alexandrescu a écrit :
 Mehrdad posted a code sample and you replied to it with what I could
 only assume was an explanation of the code's behavior. I hope you'd
 agree it was a reasonable assumption, whether or not it was correct.

 The problem at work here is that "this" is typed incorrectly during the
 construction of a qualified object. It should go progressively from a
 "raw" to a "cooked" state, and cannot be passed to any function while
 raw.

 I've however made other posts to explain why
 transitivity is broken when it comes to delegate.

I looked at your posts through June and couldn't find such, so a couple of links would be great. Of course, bug reports would be even better!

I'm not sure you are talking about the proposal or the transitivity issue here. I'll explain the proposal below, for the explaination of why the transitivity is broken, see : http://forum.dlang.org/thread/ywispsasaylqscyuayae forum.dlang.org?page=2#post-jrn7sv:24jjj:241:40digitalmars.com To explain my proposal, I have to make several thing clear first. You have here 2 things to qualify : the delegate itself, and the hidden parameter. As type qualifier are transitive in D, qualify the delegate also transitively qualify the hidden parameter. The proposal was to use postfix qualifier notation to qualify the hidden parameter, and qualifier between return type and delegate keyword to qualify the delegate itself.

Why would the second part of the proposal be necessary?
 My original proposal stated that the type
 qualifier before the return type qualify the return type, but this is
 another topic.

 To make is clearer, some examples :

 void delegate() const; // A mutable delegate that use const frame
 pointer (all variable accessed throw the frame pointer are made const).
 void const delegate(); // A const delegate that use a const frame
 pointer (transitivity).
 void const delegate() immutable; // A const delegate that use an
 immutable frame pointer (delegate can only access immutable data from
 the frame pointer).
 void immutable delegate() const; // Error, immutable data cannot refers
 to const data.

 Note that the implicit cast goes the other way around than usual. void
 delegate() const can be safely casted to void delegate(), but not the
 other way around, as you are allowed to not mutate mutable data, but not
 the other way around.

Comments: 1. struct S{ int x; void foo()pure{x++;} // ok } void main(){ int x; void foo()pure{x++;} // currently an error } The second part should be allowed. It is qualifying foo as 'immutable' which should fail. 2. int y; struct S{ int x; void foo()immutable{y++;} // ok } void main(){ void foo()immutable{y++;} // should be allowed } The problem is the unfortunate annotation overhead. It would be better to have 'nostatic' as the keyword for the current 'pure' and 'pure' as a shortcut for 'immutable nostatic'. This would also silence the 'pure is a misnomer' crowd.
Jun 18 2012
parent reply deadalnix <deadalnix gmail.com> writes:
Le 18/06/2012 18:04, Timon Gehr a écrit :
 To explain my proposal, I have to make several thing clear first. You
 have here 2 things to qualify : the delegate itself, and the hidden
 parameter. As type qualifier are transitive in D, qualify the delegate
 also transitively qualify the hidden parameter.

 The proposal was to use postfix qualifier notation to qualify the hidden
 parameter, and qualifier between return type and delegate keyword to
 qualify the delegate itself.

Why would the second part of the proposal be necessary?

What do you qualify as the second part ?
 My original proposal stated that the type
 qualifier before the return type qualify the return type, but this is
 another topic.

 To make is clearer, some examples :

 void delegate() const; // A mutable delegate that use const frame
 pointer (all variable accessed throw the frame pointer are made const).
 void const delegate(); // A const delegate that use a const frame
 pointer (transitivity).
 void const delegate() immutable; // A const delegate that use an
 immutable frame pointer (delegate can only access immutable data from
 the frame pointer).
 void immutable delegate() const; // Error, immutable data cannot refers
 to const data.

 Note that the implicit cast goes the other way around than usual. void
 delegate() const can be safely casted to void delegate(), but not the
 other way around, as you are allowed to not mutate mutable data, but not
 the other way around.

Comments: 1. struct S{ int x; void foo()pure{x++;} // ok } void main(){ int x; void foo()pure{x++;} // currently an error } The second part should be allowed. It is qualifying foo as 'immutable' which should fail.

Yes.
 2.

 int y;
 struct S{
 int x;
 void foo()immutable{y++;} // ok
 }

 void main(){
 void foo()immutable{y++;} // should be allowed
 }

 The problem is the unfortunate annotation overhead.

And yes. This is another topic, but I think the way to go is inference.
 It would be better to have 'nostatic' as the keyword for the
 current 'pure' and 'pure' as a shortcut for 'immutable nostatic'.

 This would also silence the 'pure is a misnomer' crowd.

I don't really have an opinion on that subject. I understand the keywork choice can be confusing, but the overall design is really nice.
Jun 18 2012
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 06/18/2012 06:12 PM, deadalnix wrote:
 Le 18/06/2012 18:04, Timon Gehr a écrit :
 It would be better to have 'nostatic' as the keyword for the
 current 'pure' and 'pure' as a shortcut for 'immutable nostatic'.

 This would also silence the 'pure is a misnomer' crowd.

I don't really have an opinion on that subject. I understand the keywork choice can be confusing, but the overall design is really nice.

I agree. Some of the keywords are poorly chosen, but this does not have any actual _practical_ implications for coding. Changing them, however, does. It is just frustrating to always be stopped mid-sentence when explaining the design to someone unfamiliar with it, or even have them lose interest because they think to have found a 'problem' with it. There is also a group of one or two guys who cannot mention 'pure' without also mentioning that they disagree with the keyword choice. Interestingly, few oppose to 'const'. This is probably because C++ has paved the way for it. TL;DR: poorly chosen keywords create needless communication overhead
Jun 18 2012
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 06/18/2012 06:30 PM, Mehrdad wrote:
 On Monday, 18 June 2012 at 16:23:39 UTC, Timon Gehr wrote:
 I agree. Some of the keywords are poorly chosen, but this does not
 have any actual _practical_ implications for coding. Changing them,
 however, does.

Not sure if this was intended to be referring to my post or not, but just to clarify:

It was not. ;)
 The real problem is _not_ the fact that there is a technical issue with
 const/pure/immutable/whatever.
 Like you said, that might not have any practical consequences.


 The problem is that when the compiler _uses_ const/pure/immutable to
 make decisions regarding optimizations.

It is important that the type system is sound.
 When that's the case, then IMHO they **MUST** be foolproof, no matter
 how rare/common they are (assuming no casts and such, to subvert the
 system).

This must be the case even if the compiler does not optimize.
 Otherwise the compiler generates wrong binaries for correct code.

Then the compiler is wrong.
Jun 18 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 18 June 2012 at 14:37:26 UTC, Andrei Alexandrescu 
wrote:
 On 6/18/12 1:35 AM, Matthias Walter wrote:
 On 06/18/2012 08:19 AM, Mehrdad wrote:
 On Monday, 18 June 2012 at 06:14:22 UTC, Matthias Walter 
 wrote:
 Its not, that a const method cannot modify an object, it just
 ensures that the const method cannot modify the object *by 
 using
 the this-pointer*.

I see... So that means you /can't/ tell something just by looking at a part of the code, right? (Just mentioning this since this idea seemed to be emphasized a lot by D.)

Yes, you are right with that.

Actually things are a fair amount subtler. On the face of it, immutable does fulfill the OP's expectation. But even for const code, there's still a lot of guarantees that can be inferred depending on the types involved. Andrei

Changing "new const(S)(0);" to "new immutable(S)(0);" still doesn't work tho.
Jun 18 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 18 June 2012 at 12:43:04 UTC, deadalnix wrote:
 Le 18/06/2012 07:36, Mehrdad a écrit :
 Is it just me, or did I subvert the type system here?

delegate fail to ensure transitivity of type qualifier. This is no news. This is however a big error.

Oh lol :( thought I'd discovered something. Well in that case, shouldn't the system protect itself then? Otherwise it seems like 'const' in D becomes just like C++ const (doesn't let you "prove" anything)...
 I proposed a fix to that by changing the semantic of the type 
 qualifier depending on its position in the declaration, but 
 didn't received much feedback at the time.

Interesting!
Jun 18 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 18 June 2012 at 14:41:02 UTC, deadalnix wrote:
 Le 18/06/2012 16:28, Artur Skawina a écrit :
 It's fine, if you view a delegate as opaque.

No it isn't. You cannot ensure transitivity anywhere. This have obvious, and severe drawback for concurrent programing (implicit sharing is back) and GC performances (GC can eb crazy fast when it come to transitive immutable data, see OCaml's GC performances for instance).
 'this' being const does not preclude accessing the object that 
 'this'
 points to via another, mutable, reference.

 Consider the alternative - you'd have to forbid storing any 
 delegate
 with a non-const non-value argument inside any object.

 And "breaking" const would then _still_ be possible and 
 trivial.

No, and your example don't demonstrate that in any way. Transitivity is maintained in the example below, because g isn't a member of s, and if it were, then the example would break at compile time.
    import std.stdio;

    S*[const(S)*] g;

    struct S {
       int i;
       this(int i) { this.i = i; g[&this] =&this; }
       void f() const { g[&this].i=666; }
    }

    void main() {
       const s = S(42);
       writeln(s);
       s.f();
       writeln(s);
    }

 Yes, D's "pure" could help here, only it's misnamed (even if in
 this particular case that term would be fitting).

purity is another beast altogether.

Interesting, making the delegate `pure' doesn't change anything either. So 'pure' doesn't let you "infer something just by looking at the code either", right?
Jun 18 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 18 June 2012 at 14:48:37 UTC, deadalnix wrote:
 Le 18/06/2012 16:44, Mehrdad a écrit :
 Interesting, making the delegate `pure' doesn't change 
 anything either.

 So 'pure' doesn't let you "infer something just by looking at 
 the code
 either", right?

It does ! It tell you that the function have no side effect, and that the function called with identical arguments will return identical results. pure will not ensure constness or immutability. const and immutable are made for that. The fact that D decouple purity and immutability is a very nice design decision and is explained nicely here : http://klickverbot.at/blog/2012/05/purity-in-d/

Identical calls giving identical results? What? import std.stdio; struct S { this(int a) { this.a = a; this.increment = { return this.a++; }; } int a; int delegate() pure increment; auto oops() const { return this.increment(); } } void main() { auto c = immutable(S)(0); writeln(c.oops()); // 0 writeln(c.oops()); // 1 writeln(c.oops()); // 2 writeln(c.oops()); // 3 writeln(c.oops()); // 4 writeln(c.oops()); // 5 }
Jun 18 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 18 June 2012 at 14:55:42 UTC, Mehrdad wrote:
 Identical calls giving identical results? What?


 import std.stdio;
 struct S
 {
          this(int a)
          {
                  this.a = a;
                  this.increment = { return this.a++; };
          }
          int a;
          int delegate() pure increment;
          auto oops() const { return this.increment(); }
 }
 void main()
 {
          auto c = immutable(S)(0);
          writeln(c.oops()); // 0
          writeln(c.oops()); // 1
          writeln(c.oops()); // 2
          writeln(c.oops()); // 3
          writeln(c.oops()); // 4
          writeln(c.oops()); // 5
 }

Heck, make that all safe pure nothrow: int delegate() pure safe nothrow increment; auto oops() pure const safe nothrow { return this.increment(); }
Jun 18 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 18 June 2012 at 15:11:00 UTC, Timon Gehr wrote:
 On 06/18/2012 04:55 PM, Mehrdad wrote:
 Identical calls giving identical results? What?


 import std.stdio;
 struct S
 {
          this(int a)
          {
                  this.a = a;
                  this.increment = { return this.a++; };
          }
          int a;
          int delegate() pure increment;
          auto oops() const { return this.increment(); }
 }
 void main()
 {
          auto c = immutable(S)(0);
          writeln(c.oops()); // 0
          writeln(c.oops()); // 1
          writeln(c.oops()); // 2
          writeln(c.oops()); // 3
          writeln(c.oops()); // 4
          writeln(c.oops()); // 5
 }

Now you have managed to break the type system. The underlying issue is unrelated to delegates though.

Yeah, I didn't mean to say it's a delegate issue either. That's why the title was saying "how to break _const_". Delegates were just a means to an end. :) So (**IMHO**) if that's really the case, we should really spend some time fixing the /design/ of const before the implementation... good idea or no?
Jun 18 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 18 June 2012 at 15:13:33 UTC, deadalnix wrote:
 Identical calls giving identical results? What?


 import std.stdio;
 struct S
 {
 this(int a)
 {
 this.a = a;
 this.increment = { return this.a++; };
 }
 int a;
 int delegate() pure increment;
 auto oops() const { return this.increment(); }
 }
 void main()
 {
 auto c = immutable(S)(0);
 writeln(c.oops()); // 0
 writeln(c.oops()); // 1
 writeln(c.oops()); // 2
 writeln(c.oops()); // 3
 writeln(c.oops()); // 4
 writeln(c.oops()); // 5
 }

They are not call with the same parameters. The hidden parameter have changed (I know this is tricky).

Explain it however you want. The bottom line I'm getting at is, you can't re-order the calls, EVEN IF by "looking at them" you can tell they're pure safe nothrow const...
Jun 18 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 18 June 2012 at 15:16:58 UTC, Mehrdad wrote:
 Explain it however you want.

 The bottom line I'm getting at is, you can't re-order the 
 calls, EVEN IF by "looking at them" you can tell they're pure 
  safe nothrow const...

(Sorry wasn't intending to make a mean comment -- my bad if it came across that way.) And not just re-ordering for that matter. I can't see /any/ optimizations happening even though these are const/pure/ safe/nothrow/whatever...
Jun 18 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 18 June 2012 at 15:19:26 UTC, deadalnix wrote:
 Yes, but this isn't a problem with pure here, this is a problem 
 with const.

Hence the title...
 The sample code should not compile.

What should the error be? Is it correct in the language spec?
Jun 18 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 18 June 2012 at 15:21:36 UTC, Timon Gehr wrote:
 So (**IMHO**) if that's really the case, we should really 
 spend some
 time fixing the /design/ of const before the implementation...

This is mostly about the design of object initialisation.
 good idea or no?

Certainly.

My initial instinct would be to require a "const constructor" in order for an object to be const-able, but I'm not sure if that would work correctly or not..
Jun 18 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 18 June 2012 at 15:24:31 UTC, Mehrdad wrote:
 On Monday, 18 June 2012 at 15:21:36 UTC, Timon Gehr wrote:
 So (**IMHO**) if that's really the case, we should really 
 spend some
 time fixing the /design/ of const before the implementation...

This is mostly about the design of object initialisation.
 good idea or no?

Certainly.

My initial instinct would be to require a "const constructor" in order for an object to be const-able, but I'm not sure if that would work correctly or not..

Come to think of it, that would play REALLY nicely with 'scope' -- a reference to a non-const object can be escaped from a 'const constructor' if and only if the reference is scope! Bingo! Does that work??
Jun 18 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 18 June 2012 at 15:36:23 UTC, deadalnix wrote:
 Le 18/06/2012 17:28, Mehrdad a écrit :
 On Monday, 18 June 2012 at 15:24:31 UTC, Mehrdad wrote:
 On Monday, 18 June 2012 at 15:21:36 UTC, Timon Gehr wrote:
 So (**IMHO**) if that's really the case, we should really 
 spend some
 time fixing the /design/ of const before the 
 implementation...

This is mostly about the design of object initialisation.
 good idea or no?

Certainly.

My initial instinct would be to require a "const constructor" in order for an object to be const-able, but I'm not sure if that would work correctly or not..

Come to think of it, that would play REALLY nicely with 'scope' -- a reference to a non-const object can be escaped from a 'const constructor' if and only if the reference is scope! Bingo! Does that work??

Indeed, this should be scope for ctor (avoid partially initialized object in 3rd party code) /dtor (avoid resurrection, which is a real pain for any GC, and a very good way to ends up with alive object in invalid state).

note: "for a CONST ctor", not just any ctor
Jun 18 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 18 June 2012 at 16:23:39 UTC, Timon Gehr wrote:
 I agree. Some of the keywords are poorly chosen, but this does 
 not have any actual _practical_ implications for coding. 
 Changing them, however, does.

Not sure if this was intended to be referring to my post or not, but just to clarify: The real problem is _not_ the fact that there is a technical issue with const/pure/immutable/whatever. Like you said, that might not have any practical consequences. The problem is that when the compiler _uses_ const/pure/immutable to make decisions regarding optimizations. When that's the case, then IMHO they **MUST** be foolproof, no matter how rare/common they are (assuming no casts and such, to subvert the system). Otherwise the compiler generates wrong binaries for correct code.
Jun 18 2012
prev sibling next sibling parent "Era Scarecrow" <rtcvb32 yahoo.com> writes:
On Monday, 18 June 2012 at 15:24:31 UTC, Mehrdad wrote:
 On Monday, 18 June 2012 at 15:21:36 UTC, Timon Gehr wrote:
 So (**IMHO**) if that's really the case, we should really 
 spend some time fixing the /design/ of const before the 
 implementation...

This is mostly about the design of object initialisation.
 good idea or no?

Certainly.

My initial instinct would be to require a "const constructor" in order for an object to be const-able, but I'm not sure if that would work correctly or not..

Hmmm... Reminds me of an issue that ended up coming up regarding code I was transforming to use immutable rather than mutable data. I kept coming up with the same issues. Without a const specific constructor, the immutable data I was basing the information off refused to go to a mutable temporary (since I needed it to be mutable), even if the end object was immutable and the data was untouched. I ended up with having to make a separate function that acted as a constructor, copying immutable elements and data as needed as mutable, then passing it back as immutable afterwards. A little ugly but it got the job done. I also came across an interesting problem. struct X { immutable int i_i = 10; const int c_i = 10; //same with immutable immutable int i_i2; const int c_i2; this (int n) { i_i = n; //ct error c_i = n; //ct error i_i2 = n; c_i2 = n; } } void main(){ X x = X(15); } In this case it refuses to modify i_i & c_i during the constructor, the value of i_i & c_i specify is basically a replacement default(init) and should remain mutable until the constructor is finished (Or at least allowed to set once). If I wanted a strictly unchanged value I probably would have used an enum, and not used the space. Is this a bug or intentional?
Jun 18 2012
prev sibling next sibling parent Iain Buclaw <ibuclaw ubuntu.com> writes:
On 19 June 2012 09:18, Don Clugston <dac nospam.com> wrote:
 So would I. Can you think of one?
 It was the best name I could come up with, given that the 'pure' was the
 keyword.
 We want a word that means 'no hidden state'.

I thought that was what pure was for. :~) -- Iain Buclaw *(p < e ? p++ : p) = (c & 0x0f) + '0';
Jun 19 2012
prev sibling next sibling parent Iain Buclaw <ibuclaw ubuntu.com> writes:
On 19 June 2012 10:47, Christophe Travert <travert phare.normalesup.org> wr=
ote:
 Iain Buclaw , dans le message (digitalmars.D:170145), a =E9crit=A0:
 On 19 June 2012 09:18, Don Clugston <dac nospam.com> wrote:
 So would I. Can you think of one?
 It was the best name I could come up with, given that the 'pure' was th=



 keyword.
 We want a word that means 'no hidden state'.

I thought that was what pure was for. :~) -- Iain Buclaw *(p < e ? p++ : p) =3D (c & 0x0f) + '0';

A delegate does have a frame pointer, and it's not that well hidden. If you want no such 'hidden state', you do not want a delegate, you want a function pointer. That means all delegates are weakly pure (until they have an immutable frame pointer qualifier). If you want that this 'hidden state' does not change, that is another story. pure for a delegate could mean that the frame pointer does not change, but then, pure methods wouldn't allow you to make pure delegates: struct S { =A0int i; =A0int foo() pure { return i; } } S s; int delegate() pure dg =3D &s.foo; // error if pure change meaning when applied to a delegate

So we have a few combinations then: pure - as in weakly pure, guarantees not to change global state, but may alter it's own hidden state. pure nothrow - as in strongly pure, where is guaranteed not to have any side effects, so is suitable for constant folding / the usual optimisations for a function typically marked as __pure__ in C. pure const - similar to strongly pure, as is guaranteed not to be able to alter its own state (as it's const), but still may have side effects / throw an exception. Make much sense? :-) --=20 Iain Buclaw *(p < e ? p++ : p) =3D (c & 0x0f) + '0';
Jun 19 2012
prev sibling next sibling parent Iain Buclaw <ibuclaw ubuntu.com> writes:
On 19 June 2012 12:03, deadalnix <deadalnix gmail.com> wrote:
 Le 19/06/2012 12:49, Iain Buclaw a =E9crit :

 So we have a few combinations then:

 pure =A0- as in weakly pure, guarantees not to change global state, but
 may alter it's own hidden state.

 pure nothrow - as in strongly pure, where is guaranteed not to have
 any side effects, so is suitable for constant folding / the usual
 optimisations for a function typically marked as __pure__ in C.

 pure const - similar to strongly pure, as is guaranteed not to be able
 to alter its own state (as it's const), but still may have side
 effects / throw an exception.


 Make much sense? :-)

You know that it doesn't make any sense, right ?

ramble, ramble, ramble, ramble. :o) --=20 Iain Buclaw *(p < e ? p++ : p) =3D (c & 0x0f) + '0';
Jun 19 2012
prev sibling next sibling parent Iain Buclaw <ibuclaw ubuntu.com> writes:
On 19 June 2012 12:04, Timon Gehr <timon.gehr gmx.ch> wrote:
 On 06/19/2012 12:49 PM, Iain Buclaw wrote:
 On 19 June 2012 10:47, Christophe Travert<travert phare.normalesup.org>
 =A0wrote:
 Iain Buclaw , dans le message (digitalmars.D:170145), a =E9crit :
 On 19 June 2012 09:18, Don Clugston<dac nospam.com> =A0wrote:
 So would I. Can you think of one?
 It was the best name I could come up with, given that the 'pure' was
 the
 keyword.
 We want a word that means 'no hidden state'.

I thought that was what pure was for. :~) -- Iain Buclaw *(p< =A0e ? p++ : p) =3D (c& =A00x0f) + '0';

A delegate does have a frame pointer, and it's not that well hidden. If you want no such 'hidden state', you do not want a delegate, you want a function pointer. That means all delegates are weakly pure (until they have an immutable frame pointer qualifier). If you want that this 'hidden state' does not change, that is another story. pure for a delegate could mean that the frame pointer does not change, but then, pure methods wouldn't allow you to make pure delegates: struct S { =A0int i; =A0int foo() pure { return i; } } S s; int delegate() pure dg =3D&s.foo; // error if pure change meaning when applied to a delegate

So we have a few combinations then: pure =A0- as in weakly pure, guarantees not to change global state, but may alter it's own hidden state. pure nothrow - as in strongly pure, where is guaranteed not to have any side effects, so is suitable for constant folding

This would need to be qualified 'pure immutable nothrow', otherwise it might change it's own hidden state.
 / the usual optimisations for a function typically marked as __pure__ in
 C.

Some of them can even be applied if it is just 'pure immutable '. loop invariant code motion, for instance.
 pure const - similar to strongly pure, as is guaranteed not to be able
 to alter its own state (as it's const), but still may have side
 effects / throw an exception.

Therefore, you think 'pure const' should be allowed to have side effects?

Throwing exceptions is a bit of a side effect... --=20 Iain Buclaw *(p < e ? p++ : p) =3D (c & 0x0f) + '0';
Jun 19 2012
prev sibling next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Mon, 18 Jun 2012 01:36:26 -0400, Mehrdad <wfunction hotmail.com> wrote:

 Is it just me, or did I subvert the type system here?


 import std.stdio;

 struct Const
 {
 	this(void delegate() increment)
 	{ this.increment = increment; }
 	int a;
 	void delegate() increment;
 	void oops() const { this.increment(); }
 }

 void main()
 {
 	Const c;
 	c = Const({ c.a++; });
 	writeln(c.a);
 	c.oops();
 	writeln(c.a);
 }

This is quite a long thread, and I didn't read everything, but here is my take on this: 1. The issue is that the context pointer of the delegate, even though it is stored as part of the struct data, is not affected by coloring the containing struct as 'const'. 2. The issue is simply with delegate implicit casting, which the compiler does just about nothing with today. Consider the following struct: struct S { int x; void foo() {++x;} void fooconst() const {} } Now, consider the following function: void callit(void delegate() dg) { dg(); } One of the *really* cool benefits of delegates is that you can pass both foo and fooconst to callit -- callit doesn't have to care what attributes are applied to the delegate's context pointer, it simply calls them. However, consider if we *want* to make sure dg doesn't modify its context pointer, how can we write dg's type? You could do this (if it worked): void callit(void delegate() const dg) { dg(); } What should this mean? In my interpretation, it should mean that only a delegate that uses a const context pointer should be able to be bound to dg. So &foo should *not* be able to be passed to callit. If we can implement something like this, I think the problem would be solved. What would happen to your original code? oops would not compile, because you could not automatically convert a delegate to a const delegate (unless it was marked as const). And I think it would be possible to keep the nifty feature of not caring what the context parameter is marked as. -Steve
Jun 25 2012
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 06/25/2012 04:08 PM, Steven Schveighoffer wrote:
 ...

 This is quite a long thread, and I didn't read everything, but here is
 my take on this:

 1. The issue is that the context pointer of the delegate, even though it
 is stored as part of the struct data, is not affected by coloring the
 containing struct as 'const'.

 2. The issue is simply with delegate implicit casting, which the
 compiler does just about nothing with today.

 Consider the following struct:

 struct S
 {
     int x;
     void foo() {++x;}
     void fooconst() const {}
 }

 Now, consider the following function:

 void callit(void delegate() dg) { dg(); }

 One of the *really* cool benefits of delegates is that you can pass both
 foo and fooconst to callit -- callit doesn't have to care what
 attributes are applied to the delegate's context pointer, it simply
 calls them.

 However, consider if we *want* to make sure dg doesn't modify its
 context pointer, how can we write dg's type?  You could do this (if it
 worked):

 void callit(void delegate() const dg) { dg(); }

 What should this mean?  In my interpretation, it should mean that only a
 delegate that uses a const context pointer should be able to be bound to
 dg.  So &foo should *not* be able to be passed to callit.

It depends on what 'const' means. If 'const' means 'this reference cannot be used to change the data', then this is the only valid interpretation.
 If we can implement something like this, I think the problem would be
 solved.  What would happen to your original code?  oops would not
 compile, because you could not automatically convert a delegate to a
 const delegate (unless it was marked as const).  And I think it would be
 possible to keep the nifty feature of not caring what the context
 parameter is marked as.

 -Steve

This is essentially the conclusion reached in the other parts of the discussion as well. The issue is that implementing delegate context qualifiers correctly requires some changes, eg: currently 'pure' for function literals means 'cannot access non-immutable free variables', while actually it should mean 'cannot access non-immutable static variables'. Last time I asked, Walter made clear that such a change is not planned.
Jun 25 2012
prev sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 25 June 2012 at 17:33:18 UTC, Timon Gehr wrote:
 On 06/25/2012 04:08 PM, Steven Schveighoffer wrote:
 Last time I asked, Walter made clear that such a change is not 
 planned.

Assuming that's still true today, do we have a proposal for fixing this? (If not, then is it safe to say that the compiler may not optimize code based on purity/const-ness?)
Jun 25 2012