digitalmars.D - Lambda surprise
- Jean-Louis Leroy (40/40) Feb 07 2020 While implementing support for parameter storage classes in my
- Basile B. (95/134) Feb 08 2020 The problem is a bit more subtle. It is that `map` becomes a
- Jean-Louis Leroy (20/36) Feb 08 2020 Well, this helped a lot. I copied your code to make a 'mapStatic'
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 2020
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 2020
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 2020