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


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