Question

I'm trying to implement the classic higher order range zipWith as follows

import std.traits: allSatisfy;
import std.range: isInputRange;

auto zipWith(fun, Ranges...)(Ranges ranges) if (Ranges.length >= 2 && allSatisfy!(isInputRange, Ranges))
{
    import std.range: zip;
    return zip(ranges).map!fun;
}

but the

unittest
{
    auto x = [1, 2, 3, 4, 5];
    zipWith!((a, b) => a + b)(x, x);
}

fails with error

template algorithm_ex.zipWith cannot deduce function from argument types !((a, b) => a + b)(int[], int[]), candidates are: (d-dmd-unittest)
algorithm_ex.zipWith(fun, Ranges...)(Ranges ranges) if (Ranges.length && allSatisfy!(isInputRange, Ranges))

and I don't understand why. Clues anyone?

Update:

After CyberShadows nice answer I now have

import std.traits: allSatisfy;

/** Zip $(D ranges) together with operation $(D fun).
   TODO: Simplify when Issue 8715 is fixed providing zipWith
 */
auto zipWith(alias fun, Ranges...)(Ranges ranges) if (Ranges.length >= 2 && allSatisfy!(isInputRange, Ranges)) {
    import std.range: zip;
    import std.algorithm: map;
    import std.functional: binaryFun;
    static if (ranges.length == 2)
        return zip(ranges).map!(a => binaryFun!fun(a.expand));
    else if (ranges.length >= 3)
        return zip(ranges).map!(a => naryFun(a.expand));
    else
        static assert(false, "Need at least 2 range arguments.");
}
unittest {
    auto x = [1, 2, 3];
    import std.array: array;
    assert(zipWith!"a+b"(x, x).array == [2, 4, 6]);
    assert(zipWith!((a, b) => a + b)(x, x).array == [2, 4, 6]);
    assert(zipWith!"a+b+c"(x, x, x).array == [3, 6, 9]);
}

Is it possible to extend it to support nary fun's via strings, like zipWith!"a+b+c"(x,x,x)? I ask especially because I notice that there is code for naryFun in std.functional but it's commented out.

Was it helpful?

Solution

  1. You must declare the fun template parameter as an alias parameter, otherwise it is declared as a type parameter:

    auto zipWith(alias fun, Ranges...)( // ...
    
  2. You need to import std.algorithm for map.

  3. std.range.zip will return a range of std.typecons.Tuple, which will not automatically expand into your lambda's two parameters. You need to expand the tuple explicitly.

Fixed code:

import std.traits: allSatisfy;
import std.range: isInputRange;
import std.algorithm: map;

auto zipWith(alias fun, Ranges...)(Ranges ranges) if (Ranges.length >= 2 && allSatisfy!(isInputRange, Ranges))
{
    import std.range: zip;
    return zip(ranges).map!(t => fun(t.expand));
}

unittest
{
    auto x = [1, 2, 3, 4, 5];
    zipWith!((a, b) => a + b)(x, x);
}

Is it possible to extend it to support nary fun's via strings, like zipWith!"a+b+c"(x,x,x)?

I don't see why not:

import std.string;

private string genNaryFun(string fun, V...)()
{
    string code;
    foreach (n, v; V)
        code ~= "alias values[%d] %s;".format(n, cast(char)('a'+n));
    code ~= "return " ~ fun ~ ";";
    return code;
}

template naryFun(string fun)
{
    auto naryFun(V...)(V values)
    {
        mixin(genNaryFun!(fun, V));
    }
}

unittest
{
    alias naryFun!"a + b + c" test;
    assert(test(1, 2, 3) == 6);
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top