www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - std.range.cacheFront proposal&working code: wraps a range to enforce

reply Timothee Cour <thelastmammoth gmail.com> writes:
Following the recent thread "front evaluated multiple time with joiner
depending on where extra arg given", I'd like to propose the following
addition in std.range :

The goal is to ensure that a given range's 'front' method is called only
once per element, allowing one to handle safely side effects in 'front'
methods

eg use case: lambdas with side effects given to a map/reduce/filter
function, etc.

import std.traits;
import std.range;
struct CacheFront(R){
  alias T=ElementType!R;
  R a;
  import util.traits;
  enum isRef=mixin(isLvalue(q{a.front}));
  static if(isRef)
    T* ai;
  else{
    T ai;
    bool isValid=false;
  }
  auto ref front(){
    //TODO: could also depend on whether front is pure
    static if(isRef){
      if(ai)
        return *ai;
      else{
        ai=&a.front;
        return *ai;
      }
    }
    else{
      if(isValid){
        return ai;
      }
      else{
        isValid=true;
        ai=a.front;
        return ai;
      }
    }
  }
  void popFront(){
    a.popFront;
    static if(isRef)
      ai=null;
    else
      isValid=false;
  }
  //forward other properties automatically:
  alias a this;
}
auto cacheFront(R)(R a) if(isInputRange!R){
  return CacheFront!R(a);
}

//helper function (should be in phobos' std.traits)
void requireLvalue(T)(ref T);
string isLvalue(string a){
return `__traits(compiles, requireLvalue(`~a~`))`;
}

void main(){
  import std.algorithm;
  import std.array;
  {
    //checks that it calls front only once per element
    uint counter;
    auto b=[1,2,3].map!((a){counter++; return
[a];}).cacheFront.joiner.array;
    assert(counter==3);
    assert(b==[1,2,3]);
  }
  {
    int counter=0;
    auto b=[1,2,3].map!((a){counter++; return [a];}).joiner.array;
    assert(counter==6);
  }
 {
    //checks that it works with ref front
    auto a0=[1,2,3];
    auto ref fun0(ref int a){a=0; return a;}
    auto b=a0. cacheFront.map!fun0.array;
    assert(b==[0,0,0]);
    assert(a0==[0,0,0]);

    //checks that it forwards properties of range:
    assert([1,2,3]. cacheFront.length==3);
  }
}


And a side question: is there a way to denote that a lambda can return by
ref?
Oct 22 2013
parent reply "deadalnix" <deadalnix gmail.com> writes:
Overall, I think it is greatly needed :D

You should propose that as a pull request. Also, you shouldn't 
alias this the source range, as thing now become impracticable.

IMO, CacheFront should be a froward range whatever the source is 
and only provide front/popFront.
Oct 23 2013
parent Timothee Cour <thelastmammoth gmail.com> writes:
On Wed, Oct 23, 2013 at 5:47 PM, deadalnix <deadalnix gmail.com> wrote:

 Overall, I think it is greatly needed :D

 You should propose that as a pull request. Also, you shouldn't alias this
 the source range, as thing now become impracticable.
could you elaborate on that?
 IMO, CacheFront should be a froward range whatever the source is and only
 provide front/popFront.
some range properties still make sense to forward: * length if it's there * if the range is random access, I could use memoize (on array index) * other cases possible
Oct 24 2013