www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Lambda surprise

reply Jean-Louis Leroy <jl leroy.nyc> writes:
While implementing support for parameter storage classes in my 
openmethods library, I ran into a puzzling error.

While massaging code I came up with something like this:

// dmd -c surprise.d
import std.algorithm;
import std.range;

struct Method {
   static string foo() {
     enum foo = [ "a", "b" ].map!(x => x ~ x).join(":");
     return foo;
   }
}

pragma(msg, Method.foo()); // aa:bb

Now this is clearly a compile time constant, so I thought I would 
make it simpler and more explicit:

struct Method {
   enum foo = [ "a", "b" ].map!(x => x ~ x).join(":");
}

...which got me this error:
/home/jll/dlang/dmd-2.085.0/linux/bin64/../../src/phobos/std/algorit
m/iteration.d(475): Error: `this.__lambda2` has no value

It took me a while to realize that the root of the problem was 
that the lambda was trying to capture `this`. While fiddling I 
threw in a `static` in front of `enum`:

struct Method {
   static enum foo = [ "a", "b" ].map!(x => x ~ x).join(":");
}

...and I got a more useful error message:
/home/jll/dlang/dmd-2.085.0/linux/bin64/../../src/phobos/std/algorit
m/iteration.d(470): Error: function `surprise.Method.map!((x) => x ~
x).map!(string[]).map` need `this` to access member `map`

Thus the first version works because the lambda is formed inside 
a static method. This works as well:

struct Method {
   static string stutter(string s) { return s ~ s; }
   enum foo = [ "a", "b" ].map!(stutter).join(":");
}


I wonder:

1/ Could the error message could be made more explicit in my 
first attempt at making the expression an `enum`?

2/ Is the lambda capturing `this` inside a class, even if it is 
not referenced, a documented behavior? Is it the right thing to 
do?
Feb 07
parent reply Basile B. <b2.temp gmx.com> writes:
On Saturday, 8 February 2020 at 06:21:50 UTC, Jean-Louis Leroy 
wrote:
 While implementing support for parameter storage classes in my 
 openmethods library, I ran into a puzzling error.

 While massaging code I came up with something like this:

 // dmd -c surprise.d
 import std.algorithm;
 import std.range;

 struct Method {
   static string foo() {
     enum foo = [ "a", "b" ].map!(x => x ~ x).join(":");
     return foo;
   }
 }

 pragma(msg, Method.foo()); // aa:bb

 Now this is clearly a compile time constant, so I thought I 
 would make it simpler and more explicit:

 struct Method {
   enum foo = [ "a", "b" ].map!(x => x ~ x).join(":");
 }

 ...which got me this error:
 /home/jll/dlang/dmd-2.085.0/linux/bin64/../../src/phobos/std/algorit
m/iteration.d(475): Error: `this.__lambda2` has no value

 It took me a while to realize that the root of the problem was 
 that the lambda was trying to capture `this`. While fiddling I 
 threw in a `static` in front of `enum`:

 struct Method {
   static enum foo = [ "a", "b" ].map!(x => x ~ x).join(":");
 }

 ...and I got a more useful error message:
 /home/jll/dlang/dmd-2.085.0/linux/bin64/../../src/phobos/std/algorit
m/iteration.d(470): Error: function `surprise.Method.map!((x) => x ~
x).map!(string[]).map` need `this` to access member `map`

 Thus the first version works because the lambda is formed 
 inside a static method. This works as well:

 struct Method {
   static string stutter(string s) { return s ~ s; }
   enum foo = [ "a", "b" ].map!(stutter).join(":");
 }


 I wonder:
 [...]
 2/ Is the lambda capturing `this` inside a class, even if it is 
 not referenced, a documented behavior? Is it the right thing to 
 do?
The problem is a bit more subtle. It is that `map` becomes a member of `Method`. This is a problem that i've seen several times in bugzilla but more often people encounter it because their predicate is not `static` (even at the global scope !). This case could be solved by making the template map `static`. A general fix would be to infer automatically the `static` STC on templates but this is hard to implement (as I tried). Inference would be required because if you add `static` everywhere in the standard library you break all the uses that really require `this`. --- import std.algorithm.iteration, std.range, std.traits, std.functional; /*>>>*/ static /*<<<*/ template map(fun...) if (fun.length >= 1) { auto map(Range)(Range r) if (isInputRange!(Unqual!Range)) { import std.meta : AliasSeq, staticMap; alias RE = ElementType!(Range); alias _fun = unaryFun!fun; alias _funs = AliasSeq!(_fun); return MapResult!(_fun, Range)(r); } } private static struct MapResult(alias fun, Range) { alias R = Unqual!Range; R _input; property auto ref back()() { return fun(_input.back); } void popBack()() { _input.popBack(); } this(R input) { _input = input; } property bool empty() { return _input.empty; } void popFront() { assert(!empty, "Attempting to popFront an empty map."); _input.popFront(); } property auto ref front() { assert(!empty, "Attempting to fetch the front of an empty map."); return fun(_input.front); } static if (isRandomAccessRange!R) { static if (is(typeof(_input[ulong.max]))) private alias opIndex_t = ulong; else private alias opIndex_t = uint; auto ref opIndex(opIndex_t index) { return fun(_input[index]); } } static if (hasLength!R) { property auto length() { return _input.length; } } static if (hasSlicing!R) { static if (is(typeof(_input[ulong.max .. ulong.max]))) private alias opSlice_t = ulong; else private alias opSlice_t = uint; static if (hasLength!R) { auto opSlice(opSlice_t low, opSlice_t high) { return typeof(this)(_input[low .. high]); } } } } struct Method { enum foo = [ "a", "b" ].map!(x => x ~ x).join(":"); } ---
Feb 08
parent Jean-Louis Leroy <jl leroy.nyc> writes:
On Saturday, 8 February 2020 at 13:21:59 UTC, Basile B. wrote:

 ---
 import std.algorithm.iteration, std.range, std.traits, 
 std.functional;

 /*>>>*/ static /*<<<*/ template map(fun...)
 if (fun.length >= 1)
 {
     auto map(Range)(Range r) if (isInputRange!(Unqual!Range))
     {
         import std.meta : AliasSeq, staticMap;
         alias RE = ElementType!(Range);
         alias _fun = unaryFun!fun;
         alias _funs = AliasSeq!(_fun);
         return MapResult!(_fun, Range)(r);
     }
 }
 ---
Well, this helped a lot. I copied your code to make a 'mapStatic' template. I needed to make two fixes, here they are: --- static template mapStatic(fun...) if (fun.length >= 1) { auto mapStatic(Range)(Range r) if (isInputRange!(std.algorithm.mutation.Unqual!Range)) <-- needed fully qualified path { import std.meta : AliasSeq, staticMap; import std.functional : unaryFun; // <-- needed import alias RE = ElementType!(Range); alias _fun = unaryFun!fun; alias _funs = AliasSeq!(_fun); return MapResult!(_fun, Range)(r); } } ---
Feb 08