Thread overview
std.range.cacheFront proposal&working code: wraps a range to enforce front is called only once
Oct 23, 2013
Timothee Cour
Oct 24, 2013
deadalnix
Oct 25, 2013
Timothee Cour
October 23, 2013
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?


October 24, 2013
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.
October 25, 2013
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