www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - foreach with a default range

reply "w0rp" <devw0rp gmail.com> writes:
A thought just came to me. When I'm implementing foreach for a 
container, I'm left with the choice of using a range for a 
container or opApply. I've found often that I prefer the ranges, 
as it's easy for me to write a range that satisfies  nogc  safe 
pure nothrow, etc. This is because the ranges don't call 
delegates which are less restrictive, which opApply does.

I've been thinking about how you would implement opApply so that 
it could allow you to run  system code while the iteration itself 
is  safe, but then I had another idea. Could we allow foreach to 
look for a method (UFCS include) for producing a default range 
for an object, from a function named 'range'?

In short this...

foreach(elem; container) {}

Could be transformed into this.

foreach(elem; container.range()) {}

This is not too different from how iteration works in Python, 
Java, etc. The objects are asked for an iterator, and the 
iterator is used to iterate through the object. This would allow 
you to implement ranges with more qualifiers set on them, without 
having to type .range() everywhere.

What do others think?
Jun 11 2015
parent reply Dmitry Olshansky <dmitry.olsh gmail.com> writes:
On 11-Jun-2015 11:18, w0rp wrote:
 A thought just came to me. When I'm implementing foreach for a
 container, I'm left with the choice of using a range for a container or
 opApply. I've found often that I prefer the ranges, as it's easy for me
 to write a range that satisfies  nogc  safe pure nothrow, etc. This is
 because the ranges don't call delegates which are less restrictive,
 which opApply does.

 I've been thinking about how you would implement opApply so that it
 could allow you to run  system code while the iteration itself is  safe,
 but then I had another idea. Could we allow foreach to look for a method
 (UFCS include) for producing a default range for an object, from a
 function named 'range'?

 In short this...

 foreach(elem; container) {}

 Could be transformed into this.

 foreach(elem; container.range()) {}
Already works. Just define opSlice for container that returns a range and then: foreach(elem; container) {} is lowered to: foreach(elem; container[]) {} -- Dmitry Olshansky
Jun 11 2015
next sibling parent "w0rp" <devw0rp gmail.com> writes:
On Thursday, 11 June 2015 at 08:24:25 UTC, Dmitry Olshansky wrote:
 On 11-Jun-2015 11:18, w0rp wrote:
 A thought just came to me. When I'm implementing foreach for a
 container, I'm left with the choice of using a range for a 
 container or
 opApply. I've found often that I prefer the ranges, as it's 
 easy for me
 to write a range that satisfies  nogc  safe pure nothrow, etc. 
 This is
 because the ranges don't call delegates which are less 
 restrictive,
 which opApply does.

 I've been thinking about how you would implement opApply so 
 that it
 could allow you to run  system code while the iteration itself 
 is  safe,
 but then I had another idea. Could we allow foreach to look 
 for a method
 (UFCS include) for producing a default range for an object, 
 from a
 function named 'range'?

 In short this...

 foreach(elem; container) {}

 Could be transformed into this.

 foreach(elem; container.range()) {}
Already works. Just define opSlice for container that returns a range and then: foreach(elem; container) {} is lowered to: foreach(elem; container[]) {}
Ah! I did not know that. I shall use that in future.
Jun 11 2015
prev sibling next sibling parent "weaselcat" <weaselcat gmail.com> writes:
On Thursday, 11 June 2015 at 08:24:25 UTC, Dmitry Olshansky wrote:
 On 11-Jun-2015 11:18, w0rp wrote:
 A thought just came to me. When I'm implementing foreach for a
 container, I'm left with the choice of using a range for a 
 container or
 opApply. I've found often that I prefer the ranges, as it's 
 easy for me
 to write a range that satisfies  nogc  safe pure nothrow, etc. 
 This is
 because the ranges don't call delegates which are less 
 restrictive,
 which opApply does.

 I've been thinking about how you would implement opApply so 
 that it
 could allow you to run  system code while the iteration itself 
 is  safe,
 but then I had another idea. Could we allow foreach to look 
 for a method
 (UFCS include) for producing a default range for an object, 
 from a
 function named 'range'?

 In short this...

 foreach(elem; container) {}

 Could be transformed into this.

 foreach(elem; container.range()) {}
Already works. Just define opSlice for container that returns a range and then: foreach(elem; container) {} is lowered to: foreach(elem; container[]) {}
I had no idea this worked. Is this documented anywhere?
Jun 11 2015
prev sibling parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 6/11/15 4:24 AM, Dmitry Olshansky wrote:

 Already works. Just define opSlice for container that returns a range
 and then:

 foreach(elem; container) {}

 is lowered to:

 foreach(elem; container[]) {}
I think you want to do opIndex which takes no parameters. opSlice is no longer supposed to be used that way (though it still works for backwards compatibility). -Steve
Jun 11 2015
parent reply Dmitry Olshansky <dmitry.olsh gmail.com> writes:
On 11-Jun-2015 15:22, Steven Schveighoffer wrote:
 On 6/11/15 4:24 AM, Dmitry Olshansky wrote:

 Already works. Just define opSlice for container that returns a range
 and then:

 foreach(elem; container) {}

 is lowered to:

 foreach(elem; container[]) {}
I think you want to do opIndex which takes no parameters.
Ehm. How is being more logical or what is the reason?
 opSlice is no
 longer supposed to be used that way (though it still works for backwards
 compatibility).

 -Steve
Now that's the real news. Me thinks slice it on foreach was added speicfically for std.container back in 2012. Where the docs for the later change (well, both of them for that matter)? -- Dmitry Olshansky
Jun 11 2015
parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 6/11/15 8:54 AM, Dmitry Olshansky wrote:
 On 11-Jun-2015 15:22, Steven Schveighoffer wrote:
 On 6/11/15 4:24 AM, Dmitry Olshansky wrote:

 Already works. Just define opSlice for container that returns a range
 and then:

 foreach(elem; container) {}

 is lowered to:

 foreach(elem; container[]) {}
I think you want to do opIndex which takes no parameters.
Ehm. How is being more logical or what is the reason?
 opSlice is no
 longer supposed to be used that way (though it still works for backwards
 compatibility).
Now that's the real news. Me thinks slice it on foreach was added speicfically for std.container back in 2012. Where the docs for the later change (well, both of them for that matter)?
See this thread, was news to me too: http://forum.dlang.org/thread/luadir$t0g$1 digitalmars.com#post-mailman.669.1410325102.5783.digitalmars-d-learn:40puremagic.com The rationale was in order to support multi-dimensional slicing. -Steve
Jun 11 2015
parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Thursday, 11 June 2015 at 13:09:21 UTC, Steven Schveighoffer 
wrote:
 On 6/11/15 8:54 AM, Dmitry Olshansky wrote:
 On 11-Jun-2015 15:22, Steven Schveighoffer wrote:
 On 6/11/15 4:24 AM, Dmitry Olshansky wrote:

 Already works. Just define opSlice for container that 
 returns a range
 and then:

 foreach(elem; container) {}

 is lowered to:

 foreach(elem; container[]) {}
I think you want to do opIndex which takes no parameters.
Ehm. How is being more logical or what is the reason?
 opSlice is no
 longer supposed to be used that way (though it still works 
 for backwards
 compatibility).
Now that's the real news. Me thinks slice it on foreach was added speicfically for std.container back in 2012. Where the docs for the later change (well, both of them for that matter)?
See this thread, was news to me too: http://forum.dlang.org/thread/luadir$t0g$1 digitalmars.com#post-mailman.669.1410325102.5783.digitalmars-d-learn:40puremagic.com The rationale was in order to support multi-dimensional slicing.
Yes, but isn't it specifically for the case where you're using multi-dimensional arrays and _not_ the general case? Certainly, that's what I understood when talking with John Colvin about how the multi-dimensional array support works. It sounded like the compiler started looking for other stuff to be implemented if you defined opIndex with no parameters, whereas it doesn't if opSlice with no parameters is defined. So, I wouldn't start using opIndex that way without really understanding what's going on there in detail. Regardless, from an idiomatic perspective, it makes far more sense to be implementing opSlice with empty parameters than opIndex simply based on what the operators are for. So, if you can do both, I'd argue that you should be using opSlice with no parameters if you don't need whatever the heck is going on with multi-dimensional arrays. - Jonathan M Davis
Jun 11 2015
parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 6/11/15 4:31 PM, Jonathan M Davis wrote:
 On Thursday, 11 June 2015 at 13:09:21 UTC, Steven Schveighoffer wrote:
 On 6/11/15 8:54 AM, Dmitry Olshansky wrote:
 On 11-Jun-2015 15:22, Steven Schveighoffer wrote:
 On 6/11/15 4:24 AM, Dmitry Olshansky wrote:

 Already works. Just define opSlice for container that returns a range
 and then:

 foreach(elem; container) {}

 is lowered to:

 foreach(elem; container[]) {}
I think you want to do opIndex which takes no parameters.
Ehm. How is being more logical or what is the reason?
 opSlice is no
 longer supposed to be used that way (though it still works for
 backwards
 compatibility).
Now that's the real news. Me thinks slice it on foreach was added speicfically for std.container back in 2012. Where the docs for the later change (well, both of them for that matter)?
See this thread, was news to me too: http://forum.dlang.org/thread/luadir$t0g$1 digitalmars.com#post-mailman.669.1410325102.5783.digitalmars-d-learn:40puremagic.com The rationale was in order to support multi-dimensional slicing.
Yes, but isn't it specifically for the case where you're using multi-dimensional arrays and _not_ the general case? Certainly, that's what I understood when talking with John Colvin about how the multi-dimensional array support works. It sounded like the compiler started looking for other stuff to be implemented if you defined opIndex with no parameters, whereas it doesn't if opSlice with no parameters is defined. So, I wouldn't start using opIndex that way without really understanding what's going on there in detail.
At this point, I think opIndex() and opSlice() are identical (and the compiler I think will try opIndex() first), but I don't know what happens if you have other methods defined.
 Regardless, from an idiomatic perspective, it makes far more sense to be
 implementing opSlice with empty parameters than opIndex simply based on
 what the operators are for. So, if you can do both, I'd argue that you
 should be using opSlice with no parameters if you don't need whatever
 the heck is going on with multi-dimensional arrays.
No, opIndex with no parameters is more idiomatic. The other way is just supported for legacy reasons. It's pretty simple to understand. Let's start from a 3-arg slicing operation: o[a..b, c, d..e] => o.opIndex(o.opSlice!0(a, b), c, o.opSlice!2(d, e)); Now, let's remove parameters: o[a..b, c] => o.opIndex(o.opSlice!0(a, b), c); o[a..b] => o.opIndex(o.opSlice!0(a, b)); o[] => o.opIndex(); It makes sense, and is very extendable, and uniform. You can deal with this much easier when imlplementing some sort of wrapping than one that has to do something different depending on how many args are passed inside the []. I like the new way. -Steve
Jun 11 2015
parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Thursday, 11 June 2015 at 20:54:46 UTC, Steven Schveighoffer 
wrote:
 It makes sense, and is very extendable, and uniform. You can 
 deal with this much easier when imlplementing some sort of 
 wrapping than one that has to do something different depending 
 on how many args are passed inside the []. I like the new way.
Well, considering that we're talking about a _slicing_ operation, and not an indexing operation, I'm not about to start using opIndex for it unless I'm forced to. - Jonathan M Davis
Jun 11 2015
parent Steven Schveighoffer <schveiguy yahoo.com> writes:
On 6/11/15 5:06 PM, Jonathan M Davis wrote:
 On Thursday, 11 June 2015 at 20:54:46 UTC, Steven Schveighoffer wrote:
 It makes sense, and is very extendable, and uniform. You can deal with
 this much easier when imlplementing some sort of wrapping than one
 that has to do something different depending on how many args are
 passed inside the []. I like the new way.
Well, considering that we're talking about a _slicing_ operation, and not an indexing operation, I'm not about to start using opIndex for it unless I'm forced to.
opIndex is used for slicing operations now. It's just the index is a range of indexes, not a single index. I think opSlice probably should have been given a different name instead of commandeered for a new meaning. Like opIndexRange or something. -Steve
Jun 11 2015