www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Voldemort declarations inside structs with ctor initialization

reply =?UTF-8?B?Ikx1w61z?= Marques" <luis luismarques.eu> writes:
Considering something like this:

     auto stuff = [1, 2, 3];

     struct Foo
     {
         typeof(stuff.filter!("a != 2")) foo;

         this(int = 0)
         {
             // assume we must initialize foo in ctor,
             // for instance because it needs ctor args;
             // in this contrived case because 'stuff'
             // is not known at compile-time
             foo = stuff.filter!("a != 2");
         }

         auto front() { return foo.front(); }
         void popFront() { return foo.popFront(); }
         bool empty() { return foo.empty; }
     }

If the language allowed foo to be declared using auto (which 
would be deduced from the assignment in the ctor), that would be 
nice, right? Is that too hard to implement? As it stands, in some 
situations the declaration can get rather awkward and unwieldy 
(yes, an alias helps). It also violates the DRY principle, 
requiring changes in two places, when they do occur.

BTW, why doesn't this example work with lambdas (a => a != 2) 
instead of a string mixin ("a != 2")?

BTW 2, is `this(int = 0)' the best workaround (still?) for the 
fact that I want the ctor to be called, even if it doesn't really 
require any parameter?
May 27 2014
next sibling parent reply "bearophile" <bearophileHUGS lycos.com> writes:
Luís Marques:

 If the language allowed foo to be declared using auto (which 
 would be deduced from the assignment in the ctor), that would 
 be nice, right?

This is a kind of flow typing, it's rather useful but it introduces several complexities, so I think it's now too much late to add it to D.
 BTW, why doesn't this example work with lambdas (a => a != 2) 
 instead of a string mixin ("a != 2")?

I think lambda instantiations defines a different type. So it's incompatible.
 BTW 2, is `this(int = 0)' the best workaround (still?)

I think it's not a good workaround. Bye, bearophile
May 27 2014
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 5/27/14, 6:26 AM, Idan Arye wrote:
 On Tuesday, 27 May 2014 at 15:40:04 UTC, Luís Marques wrote:
 On Tuesday, 27 May 2014 at 15:06:53 UTC, bearophile wrote:
 BTW, why doesn't this example work with lambdas (a => a != 2)
 instead of a string mixin ("a != 2")?

I think lambda instantiations defines a different type. So it's incompatible.

Incompatible with what? I meant changing it in both the declaration and the initialization.

Lambdas are not "cached", so each lambda is unique even if it's code is the same: void main(){ pragma(msg,(int a)=>a); //prints __lambda1 pragma(msg,(int a)=>a); //prints __lambda2 } At any rate, you can't use lambdas(neither `delegate` nor `function`) when declaring a member of class or struct, since D will treat it as a method(rather than a static function) and complain about the `this` reference.

I think there was either or both a discussion and a bug report on this, but can't find either. Basically we need to clarify what it means to compare two function literals for equality. -- Andrei
May 27 2014
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 5/27/14, 7:51 AM, Meta wrote:
 On Tuesday, 27 May 2014 at 17:22:43 UTC, Andrei Alexandrescu wrote:
 I think there was either or both a discussion and a bug report on
 this, but can't find either. Basically we need to clarify what it
 means to compare two function literals for equality. -- Andrei

I remember the thread where the consensus seemed to be that hashing was the way to go. I think it's just that nobody ever implemented it.

Hmmm, are you sure? What about alpha renaming? -- Andrei
May 27 2014
prev sibling next sibling parent =?UTF-8?B?Ikx1w61z?= Marques" <luis luismarques.eu> writes:
On Tuesday, 27 May 2014 at 15:06:53 UTC, bearophile wrote:
 BTW, why doesn't this example work with lambdas (a => a != 2) 
 instead of a string mixin ("a != 2")?

I think lambda instantiations defines a different type. So it's incompatible.

Incompatible with what? I meant changing it in both the declaration and the initialization.
 BTW 2, is `this(int = 0)' the best workaround (still?)

I think it's not a good workaround.

What should I use then?
May 27 2014
prev sibling next sibling parent "Idan Arye" <GenericNPC gmail.com> writes:
On Tuesday, 27 May 2014 at 15:40:04 UTC, Luís Marques wrote:
 On Tuesday, 27 May 2014 at 15:06:53 UTC, bearophile wrote:
 BTW, why doesn't this example work with lambdas (a => a != 2) 
 instead of a string mixin ("a != 2")?

I think lambda instantiations defines a different type. So it's incompatible.

Incompatible with what? I meant changing it in both the declaration and the initialization.

Lambdas are not "cached", so each lambda is unique even if it's code is the same: void main(){ pragma(msg,(int a)=>a); //prints __lambda1 pragma(msg,(int a)=>a); //prints __lambda2 } At any rate, you can't use lambdas(neither `delegate` nor `function`) when declaring a member of class or struct, since D will treat it as a method(rather than a static function) and complain about the `this` reference.
May 27 2014
prev sibling next sibling parent "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> writes:
On Tuesday, 27 May 2014 at 15:06:53 UTC, bearophile wrote:
 Luís Marques:

 If the language allowed foo to be declared using auto (which 
 would be deduced from the assignment in the ctor), that would 
 be nice, right?

This is a kind of flow typing, it's rather useful but it introduces several complexities, so I think it's now too much late to add it to D.

Not necessarily. The first assignment of a member in a constructor is already treated as special, so I guess we're halfway there.
May 27 2014
prev sibling next sibling parent =?UTF-8?B?Ikx1w61z?= Marques" <luis luismarques.eu> writes:
On Tuesday, 27 May 2014 at 17:23:24 UTC, Marc Schütz wrote:
 Not necessarily. The first assignment of a member in a 
 constructor is already treated as special, so I guess we're 
 halfway there.

That's what I was thinking. *crosses fingers*
May 27 2014
prev sibling next sibling parent "Meta" <jared771 gmail.com> writes:
On Tuesday, 27 May 2014 at 17:22:43 UTC, Andrei Alexandrescu 
wrote:
 I think there was either or both a discussion and a bug report 
 on this, but can't find either. Basically we need to clarify 
 what it means to compare two function literals for equality. -- 
 Andrei

I remember the thread where the consensus seemed to be that hashing was the way to go. I think it's just that nobody ever implemented it.
May 27 2014
prev sibling next sibling parent "H. S. Teoh via Digitalmars-d" <digitalmars-d puremagic.com> writes:
On Tue, May 27, 2014 at 08:17:26AM -1000, Andrei Alexandrescu via Digitalmars-d
wrote:
 On 5/27/14, 7:51 AM, Meta wrote:
On Tuesday, 27 May 2014 at 17:22:43 UTC, Andrei Alexandrescu wrote:
I think there was either or both a discussion and a bug report on
this, but can't find either. Basically we need to clarify what it
means to compare two function literals for equality. -- Andrei

I remember the thread where the consensus seemed to be that hashing was the way to go. I think it's just that nobody ever implemented it.

Hmmm, are you sure? What about alpha renaming? -- Andrei

If we want to be technically correct, we would have to define function literal comparison as being true iff the two functions produce an identical computation. So: (x) => true would have to be equal to: (x) => solveFermatsLastTheorem() But unfortunately, this kind of equivalence is unsolvable in the general case (it requires solving the halting problem), and infeasible except for the most trivial cases (the compiler has to spend way too much time to compute equivalence). Hashing gives a crude first approximation that's both easy to implement, fast, works in the cases where this issue came up in the first place, and doesn't require solving unsolvable problems. Alpha-renaming is not hard to support if the compiler just assigns each declared identifier in the function literal to a numerical id. So a literal like: (xcoor,ycoor) => sqrt(xcoor*xcoor, ycoor*ycoor) and (x,y) => sqrt(x*x, y*y) would both get translated to: (id1, id2) => sqrt(id1*id1, id2*id2) and so they would compare as equal. This does require a little more effort on the part of the compiler, though -- you'd have to walk the AST of the function body. I wouldn't go any farther than this, because this quickly approaches the no-man's land of whether (x) => x+x+x should compare equal to (y) => y*3 (if floating-point is involved the two functions may *not* in fact be equivalent, for example), whether to compare equal if two different functions get optimized to the same assembly code (which may differ depending on compiler / compile flags), etc.. Honestly, I don't see the need even for alpha-renaming -- I can't imagine a use case where you'd want to do something like that. I think straight up hashing of the function body is Good Enough(tm). T -- Once bitten, twice cry...
May 27 2014
prev sibling next sibling parent "Idan Arye" <GenericNPC gmail.com> writes:
On Tuesday, 27 May 2014 at 18:54:05 UTC, H. S. Teoh via 
Digitalmars-d wrote:
 Honestly, I don't see the need even for alpha-renaming -- I 
 can't
 imagine a use case where you'd want to do something like that. 
 I think
 straight up hashing of the function body is Good Enough(tm).


 T

Hashing the function body is not enough - you must also consider the closure! template Template(alias func){ bool Template=func(); } void foo(){ int a; writeln(Template!(()=>is(typeof(a) : char))); //prints "false" } void bar(){ char a; writeln(Template!(()=>is(typeof(a) : char))); //prints "true" } If two delegates must have exactly the same scope, the usefulness of the hashing will be quite limited, but in many cases the same lambda can be declared in different scopes and still be the same. It all depends on how the lambda uses the closure - and checking this will be quite hard to implement...
May 27 2014
prev sibling next sibling parent "Meta" <jared771 gmail.com> writes:
On Tuesday, 27 May 2014 at 19:39:23 UTC, Idan Arye wrote:
 Hashing the function body is not enough - you must also 
 consider the closure!

     template Template(alias func){
         bool Template=func();
     }

     void foo(){
         int a;
         writeln(Template!(()=>is(typeof(a) : char))); //prints 
 "false"
     }

     void bar(){
         char a;
         writeln(Template!(()=>is(typeof(a) : char))); //prints 
 "true"
     }

 If two delegates must have exactly the same scope, the 
 usefulness of the hashing will be quite limited, but in many 
 cases the same lambda can be declared in different scopes and 
 still be the same. It all depends on how the lambda uses the 
 closure - and checking this will be quite hard to implement...

If I remember correctly, the main use-case of comparing lambda functions was for cases like this: auto rb1 = make!(RedBlackTree!(int, (a, b) => a < b))([4, 2, 3, 1]); auto rb2 = make!(RedBlackTree!(int, (a, b) => a < b))([4, 2, 3, 1]); assert(is(typeof(rb1) == typeof(rb2))); //FAIL assert(rb1 == rb2); //FAIL Note that this code passes if you define a top-level function "less" and pass it to make. Functions don't have function pointers, only delegates, so as a first step we could implement hashing for functions, which don't have to deal with all this context pointer business. Also, couldn't you achieve the same for delegates if you specify that only pure nogc delegates can be compared?
May 27 2014
prev sibling next sibling parent "Idan Arye" <GenericNPC gmail.com> writes:
On Tuesday, 27 May 2014 at 20:02:34 UTC, Meta wrote:
 On Tuesday, 27 May 2014 at 19:39:23 UTC, Idan Arye wrote:
 Hashing the function body is not enough - you must also 
 consider the closure!

    template Template(alias func){
        bool Template=func();
    }

    void foo(){
        int a;
        writeln(Template!(()=>is(typeof(a) : char))); //prints 
 "false"
    }

    void bar(){
        char a;
        writeln(Template!(()=>is(typeof(a) : char))); //prints 
 "true"
    }

 If two delegates must have exactly the same scope, the 
 usefulness of the hashing will be quite limited, but in many 
 cases the same lambda can be declared in different scopes and 
 still be the same. It all depends on how the lambda uses the 
 closure - and checking this will be quite hard to implement...

If I remember correctly, the main use-case of comparing lambda functions was for cases like this: auto rb1 = make!(RedBlackTree!(int, (a, b) => a < b))([4, 2, 3, 1]); auto rb2 = make!(RedBlackTree!(int, (a, b) => a < b))([4, 2, 3, 1]); assert(is(typeof(rb1) == typeof(rb2))); //FAIL assert(rb1 == rb2); //FAIL Note that this code passes if you define a top-level function "less" and pass it to make. Functions don't have function pointers, only delegates, so as a first step we could implement hashing for functions, which don't have to deal with all this context pointer business. Also, couldn't you achieve the same for delegates if you specify that only pure nogc delegates can be compared?

Well, it won't work for the example that opened this thread(converted to use lambdas). As for limiting the delegate version to ones that use pure and nogc, take another look at my example. The lambdas don't allocate anything so they're obviously nogc. As for pure, they doesn't have any side effects, and depend only on their arguments(they don't have arguments, and they are constant functions) and their scope(which happens to be mutable, but you could easily make it immutable and nothing will change).
May 27 2014
prev sibling next sibling parent "Meta" <jared771 gmail.com> writes:
On Tuesday, 27 May 2014 at 23:18:12 UTC, Idan Arye wrote:
 Well, it won't work for the example that opened this 
 thread(converted to use lambdas).

 As for limiting the delegate version to ones that use  pure and 
  nogc, take another look at my example. The lambdas don't 
 allocate anything so they're obviously  nogc. As for  pure, 
 they doesn't have any side effects, and depend only on their 
 arguments(they don't have arguments, and they are constant 
 functions) and their scope(which happens to be mutable, but you 
 could easily make it immutable and nothing will change).

Delegates by default allocate a closure. If they're marked nogc, they'd be unable to allocate that closure... I think. The pure annotation is to stop them from accessing global mutable state.
May 27 2014
prev sibling next sibling parent "Idan Arye" <GenericNPC gmail.com> writes:
On Wednesday, 28 May 2014 at 02:05:43 UTC, Meta wrote:
 On Tuesday, 27 May 2014 at 23:18:12 UTC, Idan Arye wrote:
 Well, it won't work for the example that opened this 
 thread(converted to use lambdas).

 As for limiting the delegate version to ones that use  pure 
 and  nogc, take another look at my example. The lambdas don't 
 allocate anything so they're obviously  nogc. As for  pure, 
 they doesn't have any side effects, and depend only on their 
 arguments(they don't have arguments, and they are constant 
 functions) and their scope(which happens to be mutable, but 
 you could easily make it immutable and nothing will change).

Delegates by default allocate a closure. If they're marked nogc, they'd be unable to allocate that closure... I think. The pure annotation is to stop them from accessing global mutable state.

From what I know(don't know how it is implemented in D. I know it doesn't work that way in languages that emulate closures like C++ and Java) delegates don't "allocate a closure" - rather, they use a the callstack frame that was already allocated by the function as a closure. While it's true that they have to save a reference to that frame, there is no separately GCed memory for saving that reference - it's stored in the fat pointer together with the function pointer. Either this or that - it doesn't affect nogc. nogc means that when you *run* the function no GCed memory is allocated. Whatever memory allocated for the closure(and the function pointer! which regular `function`s also have!) is allocated when you *create* the delegate. The lambdas in my example don't allocate any memory when you *run* them. Any lambda - with or without closure - needs to allocate memory when you *create* it. At any rate, a delegate without a closure is either a `function`(which can be body-hashed without any problem) or a method(which doesn't need body-hashing), so having the nogc restriction would be pointless even if nogc prevented closures. And when I think about it, I also fail to understand why you suggest the pure restriction - the problem in body-hashing is what the identifier refer to, not what the lambda does with it...
May 27 2014
prev sibling next sibling parent "Meta" <jared771 gmail.com> writes:
On Wednesday, 28 May 2014 at 06:20:29 UTC, Idan Arye wrote:
 From what I know(don't know how it is implemented in D. I know 
 it doesn't work that way in languages that emulate closures 
 like C++ and Java) delegates don't "allocate a closure" - 
 rather, they use a the callstack frame that was already 
 allocated by the function as a closure. While it's true that 
 they have to save a reference to that frame, there is no 
 separately GCed memory for saving that reference - it's stored 
 in the fat pointer together with the function pointer.

What I meant to say by "allocate a closure" is that variables in the stack frame that the delegate has a pointer to are moved to the heap when they go out of scope. I believe this implies GC allocation, but I'm not 100% sure. If that *were* the case, then you can see that marking the delegate as nogc would mean that code such as your example would fail to compile.
 The lambdas in my example don't allocate any memory when you 
 *run* them.

I'm not completely sure whether they will or they won't... But I'm pretty sure that this modified example will: auto foo() { int a; return () => is(typeof(a) : char); } void main() { writeln(foo()()); }
 At any rate, a delegate without a closure is either a 
 `function`(which can be body-hashed without any problem) or a 
 method(which doesn't need body-hashing), so having the  nogc 
 restriction would be pointless even if  nogc prevented closures.

How is this restriction pointless if it means that delegates that couldn't be hashed before due to their context can now be hashed?
May 28 2014
prev sibling next sibling parent "Idan Arye" <GenericNPC gmail.com> writes:
On Wednesday, 28 May 2014 at 14:06:38 UTC, Meta wrote:
 What I meant to say by "allocate a closure" is that variables 
 in the stack frame that the delegate has a pointer to are moved 
 to the heap when they go out of scope. I believe this implies 
 GC allocation, but I'm not 100% sure. If that *were* the case, 
 then you can see that marking the delegate as  nogc would mean 
 that code such as your example would fail to compile.

They probably work that way in C++ and Java, but that's not how closures work in D. Take a look at the this example: http://dpaste.dzfl.pl/69c7298b5a12 If you try to run it `foo()` will print different values each time, but `bar()` will always print 0. The reason is that `bar` returns a delegate. `foo` uses the standard call stack to conform to the C/++ ABI, but `bar` uses a different type of call stack - one that's implemented with a linked list. That way, when `bar` finishes, it's stack frame's memory will not get overwritten by the next function call, and the delegate can safely hold a pointer to it and use it directly. Now, that doesn't mean there is no GCed memory allocations involved. The stack frame is allocated and GCed, and the delegate object itself is allocated and GCed. But at any rate - these things happen *before* the delegate is called, so the delegate itself is nogc. Also, take a look at `baz`. It prints a value for EBP, so it uses the C/C++ ABI, but it clearly uses a delegate with a "closure". I'm not sure if it can be called a closure, since it doesn't hold a pointer to the stack(it doesn't have to), but at any rate - its clear that it can't be body-hashed.
 The lambdas in my example don't allocate any memory when you 
 *run* them.

I'm not completely sure whether they will or they won't... But I'm pretty sure that this modified example will: auto foo() { int a; return () => is(typeof(a) : char); } void main() { writeln(foo()()); }

The example as a whole allocates memory, but the delegate itself doesn't. A memory is allocated for the delegate when it's created, but when you run the delegate no memory needs to be allocated so the delegate is nogc.
 At any rate, a delegate without a closure is either a 
 `function`(which can be body-hashed without any problem) or a 
 method(which doesn't need body-hashing), so having the  nogc 
 restriction would be pointless even if  nogc prevented 
 closures.

How is this restriction pointless if it means that delegates that couldn't be hashed before due to their context can now be hashed?

OK, I retract that statement. I did farther checking and it turns out that functions can also refer to the context as long as that reference is static. That means that the no-closure restriction can be useful, but it also means that the restriction must also be applied to function lambdas.
May 28 2014
prev sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Tuesday, 27 May 2014 at 14:55:33 UTC, Luís Marques wrote:
 BTW 2, is `this(int = 0)' the best workaround (still?) for the 
 fact that I want the ctor to be called, even if it doesn't 
 really require any parameter?

AFAIK, no, since "Foo()" will actually do nothing. "this(Arg = dummy)" is actually useless, unless you manually call "__ctor", so I strongly advise against that.
May 28 2014