Truly lazy ranges, transient .front, and std.range.Generator

Joseph Rushton Wakeling via Digitalmars-d digitalmars-d at puremagic.com
Tue Aug 18 12:37:34 PDT 2015


On 18/08/15 10:59, HaraldZealot via Digitalmars-d wrote:
> On Saturday, 15 August 2015 at 10:06:13 UTC, Joseph Rushton Wakeling wrote:
>> ...
>>
>> 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.
>>
>> ...
>>
>>     -- Joe
>
> Do you mean something like that:

Yes, broadly like your example, although note that your version won't handle 
multiple popFront() calls in succession without any .front call in-between.

My own approach, which I knocked up earlier this month, was to create a generic 
mixin template that could be used to construct a lazy input range:

// ---------------------------------------------------------
mixin template LazyGen(T)
{
     T front__;

     bool evaluated__ = false;


   public:
     enum bool empty = false;

     T front() @property
     {
         if (!this.evaluated__)
         {
             this.front__ = this.evaluateFront__();
             this.evaluated__ = true;
         }

         return this.front__;
     }

     void popFront()
     {
         if (this.evaluated__)
         {
             this.evaluated__ = false;
         }
         else
         {
             this.front__ = this.evaluateFront__();
         }

         assert(!this.evaluated__);
     }
}
// ---------------------------------------------------------

... which could be mixin'd to any struct or class defining an evaluateFront__ 
method; although I think in retrospect I might rewrite that a bit to be more 
generic, not making assumptions about persistent values or the .empty condition:

// ---------------------------------------------------------
mixin template LazyPopFront()
{
     private bool evaluated__ = false;

     public T front() @property
     {
         if (!this.evaluated__)
         {
             this.popFront__();  // the 'true', private popFront
             this.evaluated__ = true;
         }

         return this.front__();  // private @property method
     }

     public void popFront()
     {
         if (this.evaluated__)
         {

             this.evaluated__ = false;
         }
         else
         {
             this.popFront__();
         }

         assert(!this.evaluated__);
     }
}
// ---------------------------------------------------------

There are probably some other subtleties that need to be taken into account 
here, but broadly I think the above covers the fundamental requirements for a 
range whose front is truly lazily evaluated.

Anyway, I'll try and follow up in the not-too-distant future with some more 
concrete example of how this can be used for some interesting stuff with RNGs. 
It would be interesting to know if the above solves any use-cases for anyone 
else, too.


More information about the Digitalmars-d mailing list