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








Jean-Louis Leroy <jl leroy.nyc>