June 20, 2012
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.
June 20, 2012
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'.
>>>
>> const means: maybe immutable.
>
> 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.
June 20, 2012
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'.
>>>
>> const means: maybe immutable.
> 
> 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
June 20, 2012
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
June 20, 2012
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.
June 20, 2012
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.


June 20, 2012
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.
June 20, 2012
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'.
>>>>
>>> const means: maybe immutable.
>>
>> 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.
June 20, 2012
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'.
>>>>
>>> const means: maybe immutable.
>>
>> 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.
June 20, 2012
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.