digitalmars.D - Problem with C++ ranges also exhibited by D
- Atila Neves (30/30) Apr 16 2019 This blog post shows off some problems with the ranges v3 library
- Dukc (15/18) Apr 16 2019 I belive the current design is superior, because of the ease of
- Atila Neves (3/21) Apr 17 2019 I have to confess my ignorance on the existence of `cache`. Huh.
- H. S. Teoh (23/60) Apr 16 2019 Actually, I've used libraries in the past that combine advancing the
- =?UTF-8?Q?Ali_=c3=87ehreli?= (10/13) Apr 16 2019 I've been under the impression that such separation is for strong
- Atila Neves (3/17) Apr 17 2019 That's a good point. I doubt the concern is historical, maybe the
- aliak (23/54) Apr 16 2019 You can implement map to not do that as well:
This blog post shows off some problems with the ranges v3 library that is going to be included in the C++20 standard library: https://www.fluentcpp.com/2019/04/16/an-alternative-design-to-iterators-and-ranges-using-stdoptional/ Because the problem is basically (from the blog) "Is it really necessary to have separate operations for advancing the iterator and evaluating its element?". The answer is of course no, and this has been brought up in the forums before. Like it or not, D has the same issue: ----------------------------- import std.algorithm; import std.range; import std.stdio; void main() { iota(1, 6) .map!((n) { writeln("transform ", n); return n * 2; }) .filter!(n => n % 4 == 0) .writeln; } ----------------------------- Produces the output: [transform 1 transform 2 transform 2 4transform 3 transform 4 , transform 4 8transform 5 ] Showing that the mapped function is called more times than strictly necessary, just as in C++.
Apr 16 2019
On Tuesday, 16 April 2019 at 12:07:03 UTC, Atila Neves wrote:This blog post shows off some problems with the ranges v3 library that is going to be included in the C++20 standard library.I belive the current design is superior, because of the ease of doing this: import std.algorithm; import std.range; import std.stdio; void main() { iota(1, 6) .map!((n) { writeln("transform ", n); return n * 2; }) .cache .filter!(n => n % 4 == 0) .writeln; } After all, sometimes you might want front() to be lazy. With the current design, you have the choice.
Apr 16 2019
On Tuesday, 16 April 2019 at 12:47:51 UTC, Dukc wrote:On Tuesday, 16 April 2019 at 12:07:03 UTC, Atila Neves wrote:I have to confess my ignorance on the existence of `cache`. Huh. Thanks!This blog post shows off some problems with the ranges v3 library that is going to be included in the C++20 standard library.I belive the current design is superior, because of the ease of doing this: import std.algorithm; import std.range; import std.stdio; void main() { iota(1, 6) .map!((n) { writeln("transform ", n); return n * 2; }) .cache .filter!(n => n % 4 == 0) .writeln; } After all, sometimes you might want front() to be lazy. With the current design, you have the choice.
Apr 17 2019
On Tue, Apr 16, 2019 at 12:07:03PM +0000, Atila Neves via Digitalmars-d wrote:This blog post shows off some problems with the ranges v3 library that is going to be included in the C++20 standard library: https://www.fluentcpp.com/2019/04/16/an-alternative-design-to-iterators-and-ranges-using-stdoptional/ Because the problem is basically (from the blog) "Is it really necessary to have separate operations for advancing the iterator and evaluating its element?". The answer is of course no, and this has been brought up in the forums before.Actually, I've used libraries in the past that combine advancing the range with reading the next element. There's certainly a benefit when you're doing simple iteration and don't want to bother to have to manually bump the range everywhere. However, I found that for non-trivial tasks I end up sprinkling ad hoc caching code everywhere, just because reading the front of a range is conflated with advancing it. One of the most annoying examples of this sort is the Posix file API, where .eof is uncheckable until you perform a read operation, and read may arbitrarily block, resulting in a percolation of corner cases, workarounds, and other band-aid just to make it work nicely with code that needs to check EOF without blocking / consuming the front of the data. For this reason, I found D's range API much cleaner to use, despite being more verbose. D's range API puts the onus on the range author to write correct code to retain the value of .front; the other API forces the client code to cache values when needed, violating DRY, and client code doesn't always do it right.Like it or not, D has the same issue: ----------------------------- import std.algorithm; import std.range; import std.stdio; void main() { iota(1, 6) .map!((n) { writeln("transform ", n); return n * 2; }) .filter!(n => n % 4 == 0) .writeln; } ----------------------------- Produces the output: [transform 1 transform 2 transform 2 4transform 3 transform 4 , transform 4 8transform 5 ] Showing that the mapped function is called more times than strictly necessary, just as in C++.[...] Just use .cache and move on already! ;-) T -- In theory, software is implemented according to the design that has been carefully worked out beforehand. In practice, design documents are written after the fact to describe the sorry mess that has gone on before.
Apr 16 2019
On 04/16/2019 05:07 AM, Atila Neves wrote:"Is it really necessary to have separate operations for advancing the iterator and evaluating its element?".I've been under the impression that such separation is for strong exception guarantee. We can't popFront (and C++ cannot operator++) before knowing that the copy of the returned value has not thrown an exception. The article you've linked does not even mention exceptions so perhaps this concern is now historical? Or perhaps there are no types that throw during copy anymore, meaning that they are either trivial bits or reference types? Ali
Apr 16 2019
On Tuesday, 16 April 2019 at 17:14:17 UTC, Ali Çehreli wrote:On 04/16/2019 05:07 AM, Atila Neves wrote:That's a good point. I doubt the concern is historical, maybe the author just didn't know about it."Is it really necessary to have separate operations for advancing the iterator andevaluatingits element?".I've been under the impression that such separation is for strong exception guarantee. We can't popFront (and C++ cannot operator++) before knowing that the copy of the returned value has not thrown an exception. The article you've linked does not even mention exceptions so perhaps this concern is now historical? Or perhaps there are no types that throw during copy anymore, meaning that they are either trivial bits or reference types? Ali
Apr 17 2019
On Tuesday, 16 April 2019 at 12:07:03 UTC, Atila Neves wrote:This blog post shows off some problems with the ranges v3 library that is going to be included in the C++20 standard library: https://www.fluentcpp.com/2019/04/16/an-alternative-design-to-iterators-and-ranges-using-stdoptional/ Because the problem is basically (from the blog) "Is it really necessary to have separate operations for advancing the iterator and evaluating its element?". The answer is of course no, and this has been brought up in the forums before. Like it or not, D has the same issue: ----------------------------- import std.algorithm; import std.range; import std.stdio; void main() { iota(1, 6) .map!((n) { writeln("transform ", n); return n * 2; }) .filter!(n => n % 4 == 0) .writeln; } ----------------------------- Produces the output: [transform 1 transform 2 transform 2 4transform 3 transform 4 , transform 4 8transform 5 ] Showing that the mapped function is called more times than strictly necessary, just as in C++.You can implement map to not do that as well: https://run.dlang.io/is/9a2ba4 Take that with a grain of salt though, no idea what kind of corner cases the actual map from phobos deals with. Also, Ali pointed out strong exception guarantees. There's an article form Hurb Sutter on implementing a generic stack container [0] that explains this scenario well. It basically boils down to, if you have advance and evaluate in one function, it makes it impossible/awkward for the caller to write exception correct code. This pattern in particular (code to match the article link you posted) try { auto element = range.next; // Do something with element } catch (Exception ex) { // meh, ignore } if copying/assigning can throw, your top element is lost. Cheers, - Ali [0] http://ptgmedia.pearsoncmg.com/imprint_downloads/informit/aw/meyerscddemo/DEMO/MAGAZINE/SU_DIR.HTM#dingp39
Apr 16 2019