June 18, 2012
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?
June 18, 2012
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?
June 18, 2012
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
June 18, 2012
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);
> }

June 18, 2012
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.
June 18, 2012
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.
June 18, 2012
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.


June 18, 2012
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
June 18, 2012
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 ».
June 18, 2012
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