Truly lazy ranges, transient .front, and std.range.Generator
Joseph Rushton Wakeling via Digitalmars-d
digitalmars-d at puremagic.com
Sat Aug 15 02:55:18 PDT 2015
Hello all,
One of the design considerations I've been mulling over recently, in terms of
random number generation functionality, is that while we talk of ranges being
lazily evaluated, in fact this isn't strictly true.
Most ranges are in fact a mix of lazy and eager: lazy in their use of popFront,
but eager in that the current value of .front is always pre-calculated, usually
starting with the first value being calculated in the constructor. This is fine
for many ranges, whose outcome is in any case deterministic, but it's not
appropriate for many other cases, where the appropriate moment of evaluation of
'front' is arguably _the moment where front is called_.
This kind of requirement clearly holds anywhere where the values of the range
depend on some kind of external input, and that arguably should include things
like sources of randomness (even if they are, under the hood, only pseudo-random).
One possible support for this is given by std.range.Generator, which implements
a very simple wrapper of a callable entity via the following range primitives:
enum empty = false;
auto ref front() @property
{
return fun();
}
void popFront() { }
Now, first, I should say that I think this is a good example of how the
classification and concept of ranges is incomplete -- there is a case for an
even simpler range than InputRange, one which _just_ has .empty and .front
defined, and which we might call a TransientRange -- but second, it's
problematic, inasmuch as it's an incomplete solution to the problem of truly
lazy ranges.
In some cases we're going to want true laziness of evaluation, but _not_
transience of .front. In these cases, the _first_ time .front is called, its
value should be freshly evaluated; but thereafter, the value is cached, until
.popFront() is called, at which point .front will be re-evaluated lazily the
next time it's called. Something like std.algorithm.cache is inappropriate
here, precisely because it's eager in its calculation of the cached values.
As far as I can see, we don't have a valid solution for this case. We have some
functionality in std.random -- e.g. randomCover and randomSample -- which
implements rather shaky workarounds for that lack.
I can't imagine this isn't a known case/problem, but as far as I can see any
discussion of it has been more on GitHub than here. So before rushing off with
Yet Another Custom Solution, I thought I'd raise the issue here, to ask what the
current state of thought is on these kinds of challenge, and what may be
upcoming in people's roadmaps.
Thanks & best wishes,
-- Joe
More information about the Digitalmars-d
mailing list