Issue 5351 - Add template mixin for Range Primitives using random access
Summary: Add template mixin for Range Primitives using random access
Status: REOPENED
Alias: None
Product: D
Classification: Unclassified
Component: phobos (show other issues)
Version: D2
Hardware: Other All
: P2 enhancement
Assignee: No Owner
URL:
Keywords: bootcamp
Depends on:
Blocks:
 
Reported: 2010-12-14 09:27 UTC by Jesse Phillips
Modified: 2024-12-01 16:13 UTC (History)
5 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this issue.
Description Jesse Phillips 2010-12-14 09:27:14 UTC
This came form a post by Lars on how to remove the boilerplate if you already have a random access interface:

To avoid the boilerplate, you could write a mixin that defines the
iteration primitives for you.

  mixin template IterationFuncs()
  {
      int index;
      bool empty() { return index == length; }
      auto front() { return opIndex(index); }
      void popFront() { ++index; }
      // ... etc.
  }

Then you'd just have to define opIndex() and length(), and the mixin does
the rest for you.

  struct MyRange(T)
  {
      T opIndex(int i) { ... }
      @property int length() { ... }
      mixin IterationFuncs!();
  }

(I haven't tested the code above, so it probably has bugs, but you get
the point.)

-Lars
Comment 1 Infiltrator 2015-12-09 10:39:27 UTC
Are you suggesting that this boilerplate mixin should be added to phobos?  It seems too inflexible for general use.
Comment 2 Jesse Phillips 2016-02-11 00:29:25 UTC
(In reply to Infiltrator from comment #1)
> Are you suggesting that this boilerplate mixin should be added to phobos? 
> It seems too inflexible for general use.

Yes that is what's being requested
http://forum.dlang.org/post/rewptfpubrsjifwblump@forum.dlang.org

I agree it isn't flexible, that isn't the point. It only has to do one thing well.
Comment 3 Simen Kjaeraas 2017-10-30 20:16:55 UTC
The implementation above will fail for this simple case:

MyRange!int a = [1,2,3];
a.popFront();
assert(a[0] == a.front);

It could be made to work for ranges that support slicing, and assignment from the slice to the range:

void popFront() {
    this = this[1..$];
}

front and empty are then trivial calls to this[0] and length == 0, respectively.

One option that only requires opIndex and length would use a wrapper struct. This solution would leave MyRange as a non-range, though - only its sliced result would be a range:

struct MyRange(T) {
    T[] arr;
    T opIndex(size_t idx) {
        return arr[idx];
    }
    
    @property
    size_t length() {
        return arr.length;
    }
    
    mixin rangeFuncs!();
}

template rangeFuncs() {
    auto opSlice() {
        alias TT = typeof(this);
        struct Result {
            size_t _index;
            TT* _range;
            
            auto front() {
                return (*_range)[_index];
            }
            
            @property
            bool empty() {
                return _index == _range.length;
            }
            
            void popFront() {
                _index++;
            }
            
            auto opIndex(size_t idx) {
                return (*_range)[_index + idx];
            }
        }
        
        Result result;
        result._range = &this;
        return result;
    }
}

unittest {
    import std.range.primitives : isInputRange;
    import std.algorithm.comparison : equal;
    
    auto a = MyRange!int([1,2,3,4]);
    
    static assert(!isInputRange!(typeof(a)));
    static assert(isInputRange!(typeof(a[])));
    assert(a[].equal([1,2,3,4]));
}

In conclusion, opIndex + length is insufficient functionality to turn something into a range.
Comment 4 Jesse Phillips 2017-12-01 22:57:41 UTC
(In reply to Simen Kjaeraas from comment #3)
> The implementation above will fail for this simple case:
> 
> MyRange!int a = [1,2,3];
> a.popFront();
> assert(a[0] == a.front);
> 
> It could be made to work for ranges that support slicing, and assignment
> from the slice to the range:
> 
> void popFront() {
>     this = this[1..$];
> }

Actually this would be a good verification to add to isRandomAccessRange because your correct that this wouldn't match for random access, but such a behavior is fine for forward and bidirectional.

Now that being said, the confusion of having an indexable, non-randomAccessRange with this kind of behavior would be there.
Comment 5 Simen Kjaeraas 2017-12-04 09:55:27 UTC
isRandomAccessRange is a compile-time construct, and checking foo[0] == foo.front is generally not possible at compile-time. Even if it is, the fact they're equal right now doesn't mean they always will be (consider the fibonacci sequence 1,1,2,3,5,8,13,21..., which would have foo[0] == foo.front after one popFront, but not two).

In addition, as you say, having an indexable range with semantics different from random access ranges is not at all desirable, and a recipe for disaster at some later point.

I agree having this kind of functionality in Phobos would be nice, but I don't see a workable path to that goal. I'm closing this - please reopen if there are revelations that make it possible in the future.
Comment 6 Paul Backus 2023-03-05 19:00:20 UTC
Reopening this, since the proposed addition is still useful, even if (as discussed) it would require more from the "host" type than just indexing and length.
Comment 7 dlangBugzillaToGithub 2024-12-01 16:13:43 UTC
THIS ISSUE HAS BEEN MOVED TO GITHUB

https://github.com/dlang/phobos/issues/9894

DO NOT COMMENT HERE ANYMORE, NOBODY WILL SEE IT, THIS ISSUE HAS BEEN MOVED TO GITHUB