On Thursday, 30 April 2020 at 18:30:14 UTC, H. S. Teoh wrote:
On Thu, Apr 30, 2020 at 06:05:55PM +0000, Paul Backus via
Digitalmars-d-learn wrote: [...]
Doing work in popFront instead of front is usually an
anti-pattern, since it forces eager evaluation of the next
element even when that element is never used. You should only
do this if there's no reasonable way to avoid it.
Really?? IME, usually you *need* to do work in popFront instead
of front, because otherwise .empty wouldn't be able to tell
whether there is another element or not. E.g., in filtering a
range based on some criterion on each element, you can't defer
computing the next element until .front because you can't
predict whether there will be another element that won't be
dropped by popFront.
There are certainly cases where it can't be avoided. Filter is
one; file I/O (as Steven Schveighoffer pointed out) is another
one. Obviously, you gotta do what you gotta do. Still, I think
that as long as work *can* be deferred to .front, it should be.
That's the essence of lazy evaluation: only do your computation
once you're absolutely sure it's necessary.
Also, for ranges based on generator functions, if .front is
lazy then you need to keep extra baggage around your range to
indicate whether or not the generator has been invoked yet;
it's easier to just always compute the next element eagerly and
cache it, and .front just returns the cached data.
std.range.generate is actually a perfect example of the problem
with doing work in popFront. Because it has to call popFront both
on construction *and* after every element, consuming n elements
will call the generator function n+1 times. The final call is
completely wasted.