Thread overview
Lambda surprise
Feb 08, 2020
Jean-Louis Leroy
Feb 08, 2020
Basile B.
Feb 09, 2020
Jean-Louis Leroy
February 08, 2020
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/algorithm/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/algorithm/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?

February 08, 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/algorithm/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/algorithm/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(":");
}
---
February 09, 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);
    }
}
---