Thread overview
Passing iterators into functions
Jun 25, 2020
repr-man
Jun 25, 2020
Max Haughton
Jun 25, 2020
Ali Çehreli
Jun 25, 2020
Mike Parker
June 25, 2020
I have the code:

int[5] a = [0, 1, 2, 3, 4];
int[5] b = [5, 6, 7, 8, 9];
auto x = chain(a[], b[]).chunks(5);
writeln(x);

It produces a range of slices as is expected: [[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]

However, when I define a function as follows and pass in the result of the chain iterator:

auto func(R)(R r, size_t width)
if(isRandomAccessRange!R)
{
    return r.chunks(width);
}

void main()
{
    int[5] a = [0, 1, 2, 3, 4];
    int[5] b = [5, 6, 7, 8, 9];
    auto x = func!(int[])(chain(a[], b[]), 5);
    writeln(x);
}

It gives me an error along the lines of:
Error: func!(int[]).func(int[] r, ulong width) is not callable using argument types (Result, int)
       cannot pass argument chain(a[], b[]) of type Result to parameter int[] r

I was hoping it would return the same result as the first program.

This seems to have to do with the fact that all iterators return their own unique type.  Could someone help me understand the reason behind this design and how to remedy my situation?
June 25, 2020
On Thursday, 25 June 2020 at 03:35:00 UTC, repr-man wrote:
> I have the code:
>
> int[5] a = [0, 1, 2, 3, 4];
> int[5] b = [5, 6, 7, 8, 9];
> auto x = chain(a[], b[]).chunks(5);
> writeln(x);
>
> It produces a range of slices as is expected: [[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]
>
> However, when I define a function as follows and pass in the result of the chain iterator:
>
> auto func(R)(R r, size_t width)
> if(isRandomAccessRange!R)
> {
>     return r.chunks(width);
> }
>
> void main()
> {
>     int[5] a = [0, 1, 2, 3, 4];
>     int[5] b = [5, 6, 7, 8, 9];
>     auto x = func!(int[])(chain(a[], b[]), 5);
>     writeln(x);
> }
>
> It gives me an error along the lines of:
> Error: func!(int[]).func(int[] r, ulong width) is not callable using argument types (Result, int)
>        cannot pass argument chain(a[], b[]) of type Result to parameter int[] r
>
> I was hoping it would return the same result as the first program.
>
> This seems to have to do with the fact that all iterators return their own unique type.  Could someone help me understand the reason behind this design and how to remedy my situation?

Chain returns a range not an int[]. You need to either convert the range to an array via .array or allow the compiler to infer the type of the parameter of func (You'll need to import std.range to have the range interface available)

mhh
June 25, 2020
Collection elements are accessed by ranges in D. Although both iterators and ranges fundamentally do the same thing (access elements). More accurately, ranges correspond to a pair iterators.

On 6/24/20 8:35 PM, repr-man wrote:

> auto func(R)(R r, size_t width)
> if(isRandomAccessRange!R)
> {
>      return r.chunks(width);
> }
>
> void main()
> {
>      int[5] a = [0, 1, 2, 3, 4];
>      int[5] b = [5, 6, 7, 8, 9];
>      auto x = func!(int[])(chain(a[], b[]), 5);

Is there a reason why you specify the template argument there?

> This seems to have to do with the fact that all iterators return their
> own unique type.

When the element is normally different, there would be no way of using one type anyway. This is similar to how vector<int>::iterator is a different type from e.g. vector<double>::iterator.

> Could someone help me understand the reason behind
> this design

Andrei Alexandrescu has the following article on D's ranges:

  https://www.informit.com/articles/printerfriendly/1407357

> and how to remedy my situation?

Just don't specify the function template argument and it will work.

Ali

June 25, 2020
On Thursday, 25 June 2020 at 03:35:00 UTC, repr-man wrote:

>
> This seems to have to do with the fact that all iterators return their own unique type.  Could someone help me understand the reason behind this design and how to remedy my situation?

Ranges conform to well-defined interfaces. Given that the algorithms in Phobos are templates and use structs rather than classes, this means the interfaces are checked at compile time (see the `is*` templates in std.range.primitives [1]). It also means that there is no concrete range "type" returned by these functions. That's why all of them return `auto` instead of a specific type. You never need to know the specific type of a range.

Arrays are ranges only because of the free-function implementation of the range interfaces in std.range.primitives which all take an array as the first argument so that they may be called using UFCS in any templated range algorithm.

So when you input an array to a range algorithm, the algorithm has no idea it has an array. It just calls R.emtpy, R.front, etc., without ever caring what the actual type is. And you can chain them because each algorithm wraps the input range with custom range type that actually implements the algorithm (in e.g., `popFront`--that's also why ranges are lazy), and that's what is returned. In a chain of function calls, popFront is called on the outermost range, which calls popFront on its wrapped range, which calls popFront on its wrapped range, etc. So if you append std.array.array to the end of the chain, it will kick off execution of the whole chain of calls by making the first call to popFront in order to copy the result into a new array.

Chapter 6, "Understanding Ranges", from "Learning D" is available freely online [2]. You should give it a read.


[1] https://dlang.org/phobos/std_range_primitives.html
[2] https://hub.packtpub.com/understanding-ranges/