www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - I wrote a function that accepts input ranges, and I get compile errors

reply pineapple <meapineapple gmail.com> writes:
I'm writing my own map function modeled after the one in phobos. 
(because I feel like it, that's why. good learning experience.) 
I've encountered one remarkable difference: The phobos function 
accepts arrays and mine does not. I understand why - I'm calling 
methods that arrays don't have - but what I don't understand is 
why the phobos function _does_ work. I haven't been able to find 
what in the phobos code accounts for iterables that aren't ranges.

What am I missing?


     enum canMap(T) = isInputRange!(Unqual!T);

     auto map(alias func, Range)(Range range) if(canMap!Range){
         return Mapping!(func, Range)(range);
     }

     struct Mapping(alias func, Range) if(canMap!Range){

         alias URange = Unqual!Range;
         Range input;

         this(URange input){
             this.input = input;
         }

         void popFront(){
             this.input.popFront();
         }
          property auto ref front(){
             return func(this.input.front);
         }

         static if(isBidirectionalRange!URange){
              property auto ref back(){
                 return func(this.input.back);
             }
             void popBack(){
                 this.input.popBack();
             }
         }

         static if(isInfinite!URange){
             enum bool empty = false;
         }else{
              property bool empty(){
                 return this.input.empty;
             }
         }

         static if(isRandomAccessRange!URange){
             static if(is(typeof(URange.opIndex) == function)){
                 alias Index = Parameters!(URange.opIndex)[0];
             }else{
                 alias Index = size_t;
             }
             auto ref opIndex(Index index){
                 return func(this.input[index]);
             }
         }

         static if(is(typeof(URange.opDollar))){
             alias opDollar = URange.opDollar;
         }

         static if(hasLength!URange){
              property auto length(){
                 return this.input.length;
             }
         }

         static if(hasSlicing!URange){
             static if(is(typeof(URange.opIndex) == function)){
                 alias SliceIndex = Parameters!(URange.opIndex)[0];
             }else{
                 alias SliceIndex = size_t;
             }
             auto opSlice(SliceIndex low, SliceIndex high){
                 return typeof(this)(this.input[low .. high]);
             }
         }

         static if(isForwardRange!URange){
              property auto save(){
                 return typeof(this)(this.input.save);
             }
         }

     }

     version(unittest) import mach.error.unit;
     unittest{
         import std.stdio;
         //import std.algorithm : map; // Works with this

         // no property 'popFront', etc for type 'int[]'
         writeln(
             [1, 2, 3].map!((item) => (item * item))
         );
     }

Tangentially related question - Why does phobos use 
isInputRange!(Unqual!T) instead of just isInputRange!T? What's 
the functional difference here?
May 27 2016
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Friday, 27 May 2016 at 14:54:30 UTC, pineapple wrote:
 I've encountered one remarkable difference: The phobos function 
 accepts arrays and mine does not.
add `import std.array;` i think to your module and it should make arrays ranges
May 27 2016
parent reply Seb <seb wilzba.ch> writes:
On Friday, 27 May 2016 at 14:59:25 UTC, Adam D. Ruppe wrote:
 On Friday, 27 May 2016 at 14:54:30 UTC, pineapple wrote:
 I've encountered one remarkable difference: The phobos 
 function accepts arrays and mine does not.
add `import std.array;` i think to your module and it should make arrays ranges
you would get a more fine-grained import with std.range (or more even more detailed std.range.primitives). If you are interested how it works under the hood - it's pretty simple & elegant: https://github.com/dlang/phobos/blob/master/std/range/primitives.d#L2038
May 28 2016
parent reply pineapple <meapineapple gmail.com> writes:
On Saturday, 28 May 2016 at 16:25:02 UTC, Seb wrote:
 If you are interested how it works under the hood - it's pretty 
 simple & elegant:
I checked up on the phobos implementation and found that arrays are mutated when iterated over as ranges, which didn't rest well with me. Nor did the idea of importing some module having such a significant side-effect as whether some type can act as a range or not. So I ended up making a sort of factory that turns arbitrary objects into ranges, including arrays. Seems to work pretty well.
May 28 2016
parent Seb <seb wilzba.ch> writes:
On Saturday, 28 May 2016 at 20:43:00 UTC, pineapple wrote:
 On Saturday, 28 May 2016 at 16:25:02 UTC, Seb wrote:
 If you are interested how it works under the hood - it's 
 pretty simple & elegant:
I checked up on the phobos implementation and found that arrays are mutated when iterated over as ranges, which didn't rest well with me. Nor did the idea of importing some module having such a significant side-effect as whether some type can act as a range or not. So I ended up making a sort of factory that turns arbitrary objects into ranges, including arrays. Seems to work pretty well.
Arrays in D work differently to other languages. That's why we call them Slices ;-) See: https://dlang.org/d-array-article.html Phobos just modifies the pointer - so `a = a[1 .. $];` is nothing more than creating a new pointer.
 are mutated when iterated over as ranges,
Btw all ranges are modified during iteration, they have a state and with every popFront you change that. If you want to save the state before, you can call `.save` if it's at least a ForwardRange
May 28 2016