Skip to content

Commit

Permalink
Merge pull request #4528 from schveiguy/fixgenerate
Browse files Browse the repository at this point in the history
Fix generate to be correctly implemented range.
  • Loading branch information
dnadlinger committed Jul 29, 2016
2 parents 1595205 + f7d8db5 commit 79d1195
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 27 deletions.
11 changes: 11 additions & 0 deletions changelog.dd
Expand Up @@ -60,6 +60,8 @@ $(BUGSTITLE Library Changes,
$(REF_ALTTEXT `curl_escape`, curl_escape, etc,c,curl),
$(REF_ALTTEXT `curl_easy_unescape`, curl_easy_unescape, etc,c,curl), and
$(REF_ALTTEXT `curl_unescape`, curl_unescape, etc,c,curl).)
$(LI $(RELATIVE_LINK2 generate, `std.range.generate` fixed to be a proper
range.))
)

$(BUGSTITLE Library Changes,
Expand Down Expand Up @@ -327,6 +329,15 @@ static assert( isFinal!(C.ff));
-------
)

$(LI $(LNAME2 generate, `std.range.generate` fixed to be a proper range.)
$(P $(XREF range, generate) was set up to return a different value on each
call to `front`. In addition, `popFront` did nothing. This means that
manipulation functions like $(XREF range, drop) did nothing. The new
version uses a one-element cache to meet the expectations of the range
definition. It preserves the ref-ness of the generator as well.
)
)

)

Macros:
Expand Down
102 changes: 75 additions & 27 deletions std/range/package.d
Expand Up @@ -3146,41 +3146,36 @@ Take!(Repeat!T) repeat(T)(T value, size_t n)
}

/**
Given callable ($(REF isCallable, std,traits)) $(D fun), create as a range
whose front is defined by successive calls to $(D fun()).
Given callable ($(REF isCallable, std,traits)) `fun`, create as a range
whose front is defined by successive calls to `fun()`.
This is especially useful to call function with global side effects (random
functions), or to create ranges expressed as a single delegate, rather than
an entire $(D front)/$(D popFront)/$(D empty) structure.
$(D fun) maybe be passed either a template alias parameter (existing
function, delegate, struct type defining static $(D opCall)... ) or
a run-time value argument (delegate, function object... ).
an entire `front`/`popFront`/`empty` structure.
`fun` maybe be passed either a template alias parameter (existing
function, delegate, struct type defining `static opCall`) or
a run-time value argument (delegate, function object).
The result range models an InputRange
($(REF isInputRange, std,range,primitives)).
The resulting range will call $(D fun()) on every call to $(D front),
and only when $(D front) is called, regardless of how the range is
iterated.
It is advised to compose generate with either
$(REF cache, std,algorithm,iteration) or $(REF array, std,array), or to use it in a
foreach loop.
A by-value foreach loop means that the loop value is not $(D ref).
The resulting range will call `fun()` on construction, and every call to
`popFront`, and the cached value will be returned when `front` is called.
Params:
fun = is the $(D isCallable) that gets called on every call to front.
Returns: an $(D inputRange) that returns a new value generated by $(D Fun) on
any call to $(D front).
Returns: an `inputRange` where each element represents another call to fun.
*/
auto generate(Fun)(Fun fun)
if (isCallable!fun)
{
return Generator!(Fun)(fun);
auto gen = Generator!(Fun)(fun);
gen.popFront(); // prime the first element
return gen;
}

/// ditto
auto generate(alias fun)()
if (isCallable!fun)
{
return Generator!(fun)();
auto gen = Generator!(fun)();
gen.popFront(); // prime the first element
return gen;
}

///
Expand Down Expand Up @@ -3219,7 +3214,6 @@ unittest
format("%(%s %)", r);
}

//private struct Generator(bool onPopFront, bool runtime, Fun...)
private struct Generator(Fun...)
{
static assert(Fun.length == 1);
Expand All @@ -3231,18 +3225,41 @@ private:
else
alias fun = Fun[0];

enum returnByRef_ = (functionAttributes!fun & FunctionAttribute.ref_) ? true : false;
static if (returnByRef_)
ReturnType!fun *elem_;
else
ReturnType!fun elem_;
public:
/// Range primitives
enum empty = false;

/// ditto
auto ref front() @property
static if (returnByRef_)
{
return fun();
/// ditto
ref front() @property
{
return *elem_;
}
/// ditto
void popFront()
{
elem_ = &fun();
}
}
else
{
/// ditto
auto front() @property
{
return elem_;
}
/// ditto
void popFront()
{
elem_ = fun();
}
}

/// ditto
void popFront() {}
}

@safe unittest
Expand Down Expand Up @@ -3270,6 +3287,37 @@ public:
assert(equal(generate(op).take(10), repeat(5).take(10)));
}

// verify ref mechanism works
@system unittest
{
int[10] arr;
int idx;

ref int fun() {
auto x = idx++;
idx %= arr.length;
return arr[x];
}
int y = 1;
foreach (ref x; generate!(fun).take(20))
{
x += y++;
}
import std.algorithm.comparison : equal;
assert(equal(arr[], iota(12, 32, 2)));
}

// assure front isn't the mechanism to make generate go to the next element.
@safe unittest
{
int i;
auto g = generate!(() => ++i);
auto f = g.front;
assert(f == g.front);
g = g.drop(5); // reassign because generate caches
assert(g.front == f + 5);
}

/**
Repeats the given forward range ad infinitum. If the original range is
infinite (fact that would make $(D Cycle) the identity application),
Expand Down

0 comments on commit 79d1195

Please sign in to comment.