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

What about conversion from/to immutable ?
June 20, 2012
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'.
>>>>>>
>>>>> 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.
>
> 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
));



June 20, 2012
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.
> 
The spec, by saying it is necessary to maintain immutability, has its 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.
>> 
> I understand the implications. There is nothing dangerous about it.

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.

June 20, 2012
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.
>>
> The spec, by saying it is necessary to maintain immutability, has its
> 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.
>>>
>> I understand the implications. There is nothing dangerous about it.
>
> 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.

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