Domanda

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.

È stato utile?

Soluzione

  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);
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top