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

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

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