Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Ranges Chainable through Concatenation Operator ~ #3353

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion std/algorithm/iteration.d
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ same cost or side effects.
auto r2 = r.cache().take(3);

assert(equal(r1, [0, 1, 2]));
assert(equal(r1 ~ r1, [0, 1, 2, 0, 1, 2]));
assert(i == 2); //The last "seen" element was 2. The data in cache has been cleared.

assert(equal(r2, [0, 1, 2]));
Expand Down Expand Up @@ -412,6 +413,8 @@ private struct _Cache(R, bool bidir)
}
}
}

mixin Chainable;
}

/**
Expand Down Expand Up @@ -608,6 +611,8 @@ private struct MapResult(alias fun, Range)
return typeof(this)(_input.save);
}
}

mixin Chainable;
}

@safe unittest
Expand All @@ -620,7 +625,9 @@ private struct MapResult(alias fun, Range)
writeln("unittest @", __FILE__, ":", __LINE__, " done.");

alias stringize = map!(to!string);
assert(equal(stringize([ 1, 2, 3, 4 ]), [ "1", "2", "3", "4" ]));
auto s = stringize([ 1, 2, 3, 4 ]);
assert(equal(s, [ "1", "2", "3", "4" ]));
assert(equal(s ~ s, [ "1", "2", "3", "4", "1", "2", "3", "4" ]));

uint counter;
alias count = map!((a) { return counter++; });
Expand Down Expand Up @@ -1020,6 +1027,8 @@ private struct FilterResult(alias pred, Range)
return typeof(this)(_input.save);
}
}

mixin Chainable;
}

@safe unittest
Expand All @@ -1034,6 +1043,8 @@ private struct FilterResult(alias pred, Range)
auto r = filter!("a > 3")(a);
static assert(isForwardRange!(typeof(r)));
assert(equal(r, [ 4 ][]));
assert(equal(r ~ r, [ 4, 4][]));
assert(equal(r ~ r ~ r, [ 4, 4, 4][]));

a = [ 1, 22, 3, 42, 5 ];
auto under10 = filter!("a < 10")(a);
Expand Down Expand Up @@ -1152,6 +1163,7 @@ template filterBidirectional(alias pred)
static assert(isBidirectionalRange!(typeof(small)));
assert(small.back == 2);
assert(equal(small, [ 1, 2 ]));
assert(equal(small ~ small, [ 1, 2, 1, 2]));
assert(equal(retro(small), [ 2, 1 ]));
// In combination with chain() to span multiple ranges
int[] a = [ 3, -2, 400 ];
Expand Down Expand Up @@ -1204,6 +1216,8 @@ private struct FilterBidiResult(alias pred, Range)
{
return typeof(this)(_input.save);
}

mixin Chainable;
}

// group
Expand Down Expand Up @@ -2056,6 +2070,8 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR)
return copy;
}
}

mixin Chainable;
}
return Result(r, sep);
}
Expand All @@ -2079,6 +2095,11 @@ if (isInputRange!RoR && isInputRange!(ElementType!RoR)
assert(equal(joiner(["Mary", "has", "a", "little", "lamb"], "..."),
"Mary...has...a...little...lamb"));
assert(equal(joiner(["abc", "def"]), "abcdef"));
/* TODO This fails, as
iteration.d(2098,18): Error: incompatible types for ((joiner(["abc", "def"])) ~ (joiner(["abc", "def"]))): 'Result' and 'Result'
why?:
assert(equal(joiner(["abc", "def"]) ~ joiner(["abc", "def"]), "abcdefabcdef"));
*/
}

unittest
Expand Down Expand Up @@ -3741,6 +3762,8 @@ if (isSomeChar!C)
{
return this;
}

mixin Chainable;
}
return Result(s);
}
Expand All @@ -3751,6 +3774,7 @@ if (isSomeChar!C)
import std.algorithm.comparison : equal;
auto a = " a bcd ef gh ";
assert(equal(splitter(a), ["a", "bcd", "ef", "gh"][]));
assert(equal(splitter(a) ~ splitter(a), ["a", "bcd", "ef", "gh", "a", "bcd", "ef", "gh"][]));
}

@safe pure unittest
Expand Down Expand Up @@ -4157,6 +4181,8 @@ private struct UniqResult(alias pred, Range)
return typeof(this)(_input.save);
}
}

mixin Chainable;
}

@safe unittest
Expand All @@ -4172,6 +4198,7 @@ private struct UniqResult(alias pred, Range)
static assert(isForwardRange!(typeof(r)));

assert(equal(r, [ 1, 2, 3, 4, 5 ][]));
assert(equal(r ~ r, [ 1, 2, 3, 4, 5, 1, 2, 3, 4, 5 ][]));
assert(equal(retro(r), retro([ 1, 2, 3, 4, 5 ][])));

foreach (DummyType; AllDummyRanges) {
Expand Down
46 changes: 44 additions & 2 deletions std/range/package.d
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,11 @@ if (Ranges.length > 0 &&

import std.typetuple : anySatisfy;

static if (anySatisfy!(isInfinite, R[0 .. $ - 1]))
{
static assert(false, "No sense in appending ranges after an infinite range in a chain.");
}

static if (anySatisfy!(isInfinite, R))
{
// Propagate infiniteness.
Expand Down Expand Up @@ -1106,6 +1111,16 @@ if (Ranges.length > 0 &&
}
return result;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this not doing the mixin?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because I needed a specialized logic for flattening of the chain-tree as mentioned below.

If this flattening is not needed we can reuse Chainable here, of course.

If not, I guess it's better to move this tree-flattening logic to Chainable...aah but because Result is a Voldemort type I don't think this is possible.

My plan now instead is to do something like

            auto opBinary(string op : "~", Range)(Range r)
            if (isInputRange!(Unqual!Range))
            {
                import std.range : chain;
                static if (isInstanceOf(Range, Result)) // if rhs r is a chain
                {
                    return chain(source, r.source); // flatten chain-tree
                }
                else
                {
                    return chain(source, r); // flatten chain-tree
                }
            }

Is there some way to check if rhs argument r is a chain?

What do you say?

Is this in vain?

auto opBinary(string op : "~", Range)(Range r)
if (isInputRange!(Unqual!Range)// &&
// is(CommonType!(RvalueElementType,
// ElementType!Range))
)
{
import std.range : chain;
return chain(source, r); // flatten tree
}
}
return Result(rs);
}
Expand Down Expand Up @@ -1168,13 +1183,16 @@ unittest
auto c = chain( iota(0, 10), iota(0, 10) );

// Test the case where infinite ranges are present.
auto inf = chain([0,1,2][], cycle([4,5,6][]), [7,8,9][]); // infinite range
auto inf = chain([0,1,2][], cycle([4,5,6][])); // infinite range
assert(inf[0] == 0);
assert(inf[3] == 4);
assert(inf[6] == 4);
assert(inf[7] == 5);
static assert(isInfinite!(typeof(inf)));

// Infinite ranges may only be placed at the end of a chain
static assert(!__traits(compiles, { auto inf = chain([0,1,2][], cycle([4,5,6][]), [7,8,9][]); }));

immutable int[] immi = [ 1, 2, 3 ];
immutable float[] immf = [ 1, 2, 3 ];
static assert(is(typeof(chain(immi, immf))));
Expand Down Expand Up @@ -1404,6 +1422,7 @@ if (isInputRange!(Unqual!R1) && isInputRange!(Unqual!R2) &&
else result.r2 = result.r2[begin .. end];
return result;
}
mixin Chainable;
}
return Result(condition, r1, r2);
}
Expand Down Expand Up @@ -1484,6 +1503,8 @@ unittest
uint[] foo = [1,2,3,4,5];
uint[] bar = [6,7,8,9,10];
auto c = chooseAmong(1,foo, bar);
assert(equal(c ~ c, [6,7,8,9,10,
6,7,8,9,10]));
assert(c[3] == 9);
c[3] = 42;
assert(c[3] == 42);
Expand Down Expand Up @@ -1609,6 +1630,8 @@ if (Rs.length > 1 && allSatisfy!(isInputRange, staticMap!(Unqual, Rs)))

alias opDollar = length;
}

mixin Chainable;
}

return Result(rs, 0);
Expand All @@ -1623,6 +1646,8 @@ if (Rs.length > 1 && allSatisfy!(isInputRange, staticMap!(Unqual, Rs)))
int[] b = [ 10, 20, 30, 40 ];
auto r = roundRobin(a, b);
assert(equal(r, [ 1, 10, 2, 20, 3, 30, 40 ]));
assert(equal(r ~ r, [ 1, 10, 2, 20, 3, 30, 40,
1, 10, 2, 20, 3, 30, 40 ]));
}

/**
Expand Down Expand Up @@ -1653,6 +1678,8 @@ if (isRandomAccessRange!(Unqual!R) && hasLength!(Unqual!R))
assert(equal(radial(a), [ 3, 4, 2, 5, 1 ]));
a = [ 1, 2, 3, 4 ];
assert(equal(radial(a), [ 2, 3, 1, 4 ]));
assert(equal(radial(a) ~ radial(a), [ 2, 3, 1, 4,
2, 3, 1, 4 ]));
}

@safe unittest
Expand Down Expand Up @@ -1875,6 +1902,8 @@ if (isInputRange!(Unqual!Range) &&
{
return _maxAvailable;
}

mixin Chainable;
}

// This template simply aliases itself to R and is useful for consistency in
Expand Down Expand Up @@ -1908,6 +1937,8 @@ if (isInputRange!(Unqual!R) && !isInfinite!(Unqual!R) && hasSlicing!(Unqual!R) &
assert(s.length == 5);
assert(s[4] == 5);
assert(equal(s, [ 1, 2, 3, 4, 5 ][]));
assert(equal(s ~ s, [ 1, 2, 3, 4, 5,
1, 2, 3, 4, 5 ][]));
}

// take(take(r, n1), n2)
Expand Down Expand Up @@ -2110,6 +2141,8 @@ if (isInputRange!R)
return _input.front = v;
}
}

mixin Chainable;
}

return Result(range, n);
Expand All @@ -2125,6 +2158,8 @@ if (isInputRange!R)

auto b = takeExactly(a, 3);
assert(equal(b, [1, 2, 3]));
assert(equal(b ~ b, [1, 2, 3,
1, 2, 3]));
static assert(is(typeof(b.length) == size_t));
assert(b.length == 3);
assert(b.front == 1);
Expand Down Expand Up @@ -2267,6 +2302,7 @@ auto takeOne(R)(R source) if (isInputRange!R)
}
// Non-standard property
@property R source() { return _source; }
mixin Chainable;
}

return Result(source, source.empty);
Expand All @@ -2276,7 +2312,10 @@ auto takeOne(R)(R source) if (isInputRange!R)
///
@safe unittest
{
import std.algorithm : equal;

auto s = takeOne([42, 43, 44]);
assert(equal(s ~ s, [42, 42]));
static assert(isRandomAccessRange!(typeof(s)));
assert(s.length == 1);
assert(!s.empty);
Expand Down Expand Up @@ -2701,6 +2740,7 @@ public:
private static struct DollarToken {}
enum opDollar = DollarToken.init;
auto opSlice(size_t, DollarToken) inout { return this; }
mixin Chainable;
}

/// Ditto
Expand All @@ -2711,7 +2751,9 @@ Repeat!T repeat(T)(T value) { return Repeat!T(value); }
{
import std.algorithm : equal;

assert(equal(5.repeat().take(4), [ 5, 5, 5, 5 ]));
assert(equal(5.repeat.take(4), [ 5, 5, 5, 5 ]));
assert(equal((1.repeat.take(2) ~ 5.repeat).take(4), [ 1, 1,
5, 5 ]));
}

@safe unittest
Expand Down
19 changes: 19 additions & 0 deletions std/range/primitives.d
Original file line number Diff line number Diff line change
Expand Up @@ -2239,3 +2239,22 @@ if (!isNarrowString!(T[]))
size_t i = a.length - std.utf.strideBack(a, a.length);
return decode(a, i);
}

/**
This template shall be mixed in at the top-scope inside of the $(D Result)
struct of each $(D InputRange) in order to support lazy chaining via
concatenation operation as `x ~ y` instead of via $(D chain(x, y))).
*/
template Chainable()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documentation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First try is done.

{
import std.traits: Unqual, CommonType;
import std.range: ElementType;
auto opBinary(string op : "~", Range)(Range r)
if (isInputRange!(Unqual!Range) &&
is(CommonType!(ElementType!(typeof(this)),
ElementType!Range)))
{
import std.range : chain;
return chain(this, r);
}
}