www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - How to define an interator to provide array like behaviour in a class?

reply "Gary Willoughby" <dev kalekold.net> writes:
I want to do something like this (Collection is a custom type):

Collection x = new Collection();
x.add(something);
x.add(somethingElse);

foreach(type value; x)
{
     writeln(value);
}

Collection is a class with a private array member variable which 
actually holds the collection data entered via the add method. 
What do i need to add to the Collection class to make it iterable 
(whilst it's actually iterating through the member array) so the 
foreach loop works as expected?
Oct 16 2012
next sibling parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Wednesday, October 17, 2012 00:03:46 Gary Willoughby wrote:
 I want to do something like this (Collection is a custom type):
 
 Collection x = new Collection();
 x.add(something);
 x.add(somethingElse);
 
 foreach(type value; x)
 {
 writeln(value);
 }
 
 Collection is a class with a private array member variable which
 actually holds the collection data entered via the add method.
 What do i need to add to the Collection class to make it iterable
 (whilst it's actually iterating through the member array) so the
 foreach loop works as expected?

1. Define opApply (see section labeled "Foreach over Structs and Classes with opApply after here: http://dlang.org/statement.html#foreach_with_ranges) 2. Or make it a range (see http://dlang.org/statement.html#foreach_with_ranges and http://ddili.org/ders/d.en/ranges.html ), which would probably be a bad idea, since containers really shouldn't be ranges. 3. Or do what std.container does and overload opSlice which returns a range over the container (see http://dlang.org/operatoroverloading.html#Slice and http://dlang.org/phobos/std_container.html in addition to the links in #2). Overall, this is the best approach. But regardless of which approach you take, you really should read up on ranges if you want to be doing much with D's standard library, and http://ddili.org/ders/d.en/ranges.html is the best tutorial on them at this point. There's also this recent article by Walter Bright which explains one of the main rationales behind ranges: http://www.drdobbs.com/architecture-and-design/component-programming-in- d/240008321 - Jonathan M Davis
Oct 16 2012
parent reply Jacob Carlborg <doob me.com> writes:
On 2012-10-17 00:23, Jonathan M Davis wrote:

 2. Or make it a range (see http://dlang.org/statement.html#foreach_with_ranges
 and http://ddili.org/ders/d.en/ranges.html ), which would probably be a bad
 idea, since containers really shouldn't be ranges.

Why is that a bad idea? -- /Jacob Carlborg
Oct 16 2012
parent reply Jacob Carlborg <doob me.com> writes:
On 2012-10-17 08:17, Jonathan M Davis wrote:

 For starters, iterating over the container would empty it.

Right, but that is really weird, in my opinion. -- /Jacob Carlborg
Oct 16 2012
parent reply Jacob Carlborg <doob me.com> writes:
On 2012-10-17 17:45, Jonathan M Davis wrote:

 Well, what would you expect? Ranges are consumed when you iterate over them.
 So, if an container is a range, it will be consumed when you iterate over it.
 That's the way that it _has_ to work given how ranges work, and that's why you
 overload opSlice to return a range which is iterated over rather than making
 the container itself a range.

How does this work with built-in arrays? -- /Jacob Carlborg
Oct 17 2012
parent =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
On 10/17/2012 09:58 AM, Jacob Carlborg wrote:
 On 2012-10-17 17:45, Jonathan M Davis wrote:

 Well, what would you expect? Ranges are consumed when you iterate over
 them.
 So, if an container is a range, it will be consumed when you iterate
 over it.
 That's the way that it _has_ to work given how ranges work, and that's
 why you
 overload opSlice to return a range which is iterated over rather than
 making
 the container itself a range.

How does this work with built-in arrays?

Array is a separate concept than slice; a slice provides access to the elements of an array. Arrays are containers that are owned by the runtime, slices are ranges over their elements. Only the slices are consumed. Ali
Oct 17 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Wednesday, October 17, 2012 08:14:45 Jacob Carlborg wrote:
 On 2012-10-17 00:23, Jonathan M Davis wrote:
 2. Or make it a range (see
 http://dlang.org/statement.html#foreach_with_ranges and
 http://ddili.org/ders/d.en/ranges.html ), which would probably be a bad
 idea, since containers really shouldn't be ranges.

Why is that a bad idea?

For starters, iterating over the container would empty it. - Jonathan M Davis
Oct 16 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Wednesday, October 17, 2012 08:58:33 Jacob Carlborg wrote:
 On 2012-10-17 08:17, Jonathan M Davis wrote:
 For starters, iterating over the container would empty it.

Right, but that is really weird, in my opinion.

Well, what would you expect? Ranges are consumed when you iterate over them. So, if an container is a range, it will be consumed when you iterate over it. That's the way that it _has_ to work given how ranges work, and that's why you overload opSlice to return a range which is iterated over rather than making the container itself a range. - Jonathan M Davis
Oct 17 2012
prev sibling next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Wed, Oct 17, 2012 at 06:58:52PM +0200, Jacob Carlborg wrote:
 On 2012-10-17 17:45, Jonathan M Davis wrote:
 
Well, what would you expect? Ranges are consumed when you iterate
over them.  So, if an container is a range, it will be consumed when
you iterate over it.  That's the way that it _has_ to work given how
ranges work, and that's why you overload opSlice to return a range
which is iterated over rather than making the container itself a
range.

How does this work with built-in arrays?

If I understand it correctly, arrays work because when you pass an array to a range function, you're actually passing a slice of it to the function. That slice gets consumed, but the original array is unchanged. T -- A linguistics professor was lecturing to his class one day. "In English," he said, "A double negative forms a positive. In some languages, though, such as Russian, a double negative is still a negative. However, there is no language wherein a double positive can form a negative." A voice from the back of the room piped up, "Yeah, yeah."
Oct 17 2012
prev sibling next sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Wednesday, October 17, 2012 10:08:15 H. S. Teoh wrote:
 On Wed, Oct 17, 2012 at 06:58:52PM +0200, Jacob Carlborg wrote:
 On 2012-10-17 17:45, Jonathan M Davis wrote:
Well, what would you expect? Ranges are consumed when you iterate
over them. So, if an container is a range, it will be consumed when
you iterate over it. That's the way that it _has_ to work given how
ranges work, and that's why you overload opSlice to return a range
which is iterated over rather than making the container itself a
range.

How does this work with built-in arrays?

[...] If I understand it correctly, arrays work because when you pass an array to a range function, you're actually passing a slice of it to the function. That slice gets consumed, but the original array is unchanged.

Pretty much yeah. Thinking of arrays in D as containers in a mistake really. They're not, as weird as that may be. It's the runtime (or the block of memory in the runtime, depending on how you look at it) which is the container, and the array is just a slice into it. But even that's not really an accurate way of looking at it, because you can append to them (altering the size of the underlying container and possibly resulting in them pointing to a different block of memory). Kind of like how arrays are halfway between value types and reference types, they're sort of halfway between ranges and containers. It's quite unfortunate that arrays are by far the most commonly used type of range, because they're a horrible example of one when you get down to the details of how they work. Regardless, it's slicing that you're dealing with when doing range-based operations on arrays, so it's the slice that gets operated on and consumed rather than the original array. And just like how ranges which are structs normally get automatically saved when passed to functions, arrays automatically get saved because they get sliced. And foreach doesn't even use the range API on arrays anyway, so regardless of how they work as ranges, it wouldn't necessarily apply to foreach. - Jonathan M Davis
Oct 17 2012
prev sibling parent "Gary Willoughby" <dev kalekold.net> writes:
 1. Define opApply (see section labeled "Foreach over Structs 
 and Classes with
 opApply after here: 
 http://dlang.org/statement.html#foreach_with_ranges)

 2. Or make it a range (see 
 http://dlang.org/statement.html#foreach_with_ranges
 and http://ddili.org/ders/d.en/ranges.html ), which would 
 probably be a bad
 idea, since containers really shouldn't be ranges.

 3. Or do what std.container does and overload opSlice which 
 returns a range
 over the container (see 
 http://dlang.org/operatoroverloading.html#Slice and
 http://dlang.org/phobos/std_container.html in addition to the 
 links in #2).
 Overall, this is the best approach.

 But regardless of which approach you take, you really should 
 read up on ranges
 if you want to be doing much with D's standard library, and
 http://ddili.org/ders/d.en/ranges.html is the best tutorial on 
 them at this
 point. There's also this recent article by Walter Bright which 
 explains one of
 the main rationales behind ranges:

 http://www.drdobbs.com/architecture-and-design/component-programming-in-
 d/240008321

 - Jonathan M Davis

Awesome thanks Jonathan! I've read that guide on ranges before and they sound very interesting. I'm currently playing with recursive collections and opApply works great.
Oct 17 2012