digitalmars.D - front stability
- Steven Schveighoffer (40/40) Jun 02 2016 I have always treated ranges with this expectation:
- Atila Neves (4/5) Jun 02 2016 So have I. It's troubling that's not how some of them work.
- Jack Stouffer (22/40) Jun 02 2016 Agreed. To quote Ali,
- Jonathan M Davis via Digitalmars-d (14/19) Jun 02 2016 I am in violent agreement with you on this. Copying ranges is unspecifie...
- deadalnix (5/6) Jun 02 2016 I opened some bugs about that in the past, but got shut down. It
- Walter Bright (2/5) Jun 30 2016 Please reopen them.
- ag0aep6g (7/16) Jun 02 2016 Completely agree with you here. You can only use one of the copies. When...
- Steven Schveighoffer (7/11) Jun 30 2016 Jack Stouffer has created a PR to formalize this.
- jmh530 (12/17) Jun 30 2016 It looks like this is just documentation changes. This really
- Jack Stouffer (5/14) Jun 30 2016 No, but what it will do is to make it clear that these are either
- Steven Schveighoffer (5/20) Jun 30 2016 This doesn't solve it, and it can't be solved -- halting problem. We
- Walter Bright (5/8) Jun 30 2016 I made some range drivers that brutally test the protocol:
- Mathias Lang via Digitalmars-d (8/14) Jun 30 2016 I think the case is pretty clear here, and I'm in agreement with you.
- Steven Schveighoffer (10/25) Jun 30 2016 I don't think that's a bug. The range definition says nothing about what...
- default0 (8/48) Jun 30 2016 Can we have ranges that disallow using them like rangeCache =
- H. S. Teoh via Digitalmars-d (22/36) Jun 30 2016 [...]
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (3/6) Jun 30 2016 Seems like this could be resolved by requiring all range code to
- Jesse Phillips (22/29) Jun 30 2016 I think front returning the same value every time is the
- Walter Bright (11/20) Jun 30 2016 Right. Also:
- Steven Schveighoffer (4/29) Jun 30 2016 Thank you, I will set up a PR to fix generate.
I have always treated ranges with this expectation: 1. front gets you the current element of the range. Calling front multiple times without calling popFront should get you the same value. 2. popFront moves you to the next element of the range if it exists. However, there are some ranges which can violate this. For example map: int i = 0; auto r = iota(int.max).map!((a) => ++i); auto e1 = r.front; auto e2 = r.front; assert(e1 + 1 == e2); But I consider this a programming error. It's acceptable to me that it's possible to write bugs using ranges, no programming language is immune to that. However, Joseph Wakeling pointed out[1] the generate range[2]: auto r = generate(() => ++i); // same result But unlike map, there isn't a way to make generate work as a proper range, because as Joe pointed out, popFront does nothing, front does all the work that popFront would do. There was a bit of discussion when this function was being vetted[3], and IMO, this did not result in the correct function. For example, generate.popFrontN does nothing. generate.drop does nothing. And of course, calling front multiple times does not yield the same answer, unless you provide a lambda that does the same thing every time (a useless case). The counter-argument seems to be that if you cache the front element, then then making a copy of the range via take can repeat the cached element[4]. I find this argument severely lacking -- non-forward ranges are not meant to be copied and expected to operate properly, it's why we require calling save. I'd say violating the expectations of what popFront and front do is more egregious than a particular use case, no matter how valid that case is. I'd like to fix this bug, but I see there were quite a few major D contributors in the camp that wanted this function to behave the way it is. So I'd rather first debate the merits here. -Steve [1] https://forum.dlang.org/post/ttyzjfmegkmbluumiczf forum.dlang.org [3] https://github.com/dlang/phobos/pull/2606 [4] https://github.com/dlang/phobos/pull/2606#issuecomment-58740715
Jun 02 2016
On Thursday, 2 June 2016 at 12:51:18 UTC, Steven Schveighoffer wrote:I have always treated ranges with this expectation:So have I. It's troubling that's not how some of them work. Atila
Jun 02 2016
On Thursday, 2 June 2016 at 12:51:18 UTC, Steven Schveighoffer wrote:1. front gets you the current element of the range. Calling front multiple times without calling popFront should get you the same value. 2. popFront moves you to the next element of the range if it exists.Agreed. To quote Ali, * empty: specifes whether the range is empty; it must return true when the range is considered to be empty, and false otherwise * front: provides access to the element at the beginning of the range * popFront(): shortens the range from the beginning by removing the first elementHowever, there are some ranges which can violate this. For example map: ... But I consider this a programming error.Completely agree. Comparing to the quote from Ali above, this violates the basic rules of ranges, rules which the vast majority of code expects ranges to follow. Also, these rules cannot (and shouldn't) be loosened because there is no way to check this violation at compile-time. Unless we ask people to add yet another enum to their ranges and have even more static-if's in their code. I don't see how benefit to breaking these rules is greater than the cost.However, Joseph Wakeling pointed out[1] the generate range[2]: auto r = generate(() => ++i);I never liked generate, it always seemed like a hack, now I see my opinions have been validated.But unlike map, there isn't a way to make generate work as a proper range, because as Joe pointed out, popFront does nothing, front does all the work that popFront would do. For example, generate.popFrontN does nothing. generate.drop does nothing. And of course, calling front multiple times does not yield the same answer, unless you provide a lambda that does the same thing every time (a useless case).These are some pretty bad bugs. It's clear to me than generate should not return a range, but an opApply iterable.
Jun 02 2016
On Thursday, June 02, 2016 08:51:18 Steven Schveighoffer via Digitalmars-d wrote:I'd say violating the expectations of what popFront and front do is more egregious than a particular use case, no matter how valid that case is. I'd like to fix this bug, but I see there were quite a few major D contributors in the camp that wanted this function to behave the way it is. So I'd rather first debate the merits here.I am in violent agreement with you on this. Copying ranges is unspecified behavior. If you want an actual copy, you have to call save. front must return the same element on every call until popFront is called, and while in some cases, that means returning an element that is equal every time rather than being an identical value (e.g. range.map!(a => to!string(a))()), it's not okay for front to return a different element without popFront being called. I think that for a range to do otherwise is a _very_ clear violation of the range API. If you have to do stuff like caching the value of front in order to keep it consistent, then you're stuck doing that, otherwise it's not a valid range. I really don't see how there's much of an argument otherwise. - Jonathan M Davis
Jun 02 2016
On Thursday, 2 June 2016 at 12:51:18 UTC, Steven Schveighoffer wrote:[...]I opened some bugs about that in the past, but got shut down. It is especially problematic since some ranges (like filter) are calling front several time when iterating.
Jun 02 2016
On 6/2/2016 8:21 AM, deadalnix wrote:I opened some bugs about that in the past, but got shut down. It is especially problematic since some ranges (like filter) are calling front several time when iterating.Please reopen them.
Jun 30 2016
On 06/02/2016 02:51 PM, Steven Schveighoffer wrote:For example, generate.popFrontN does nothing. generate.drop does nothing. And of course, calling front multiple times does not yield the same answer, unless you provide a lambda that does the same thing every time (a useless case).That's just broken.The counter-argument seems to be that if you cache the front element, then then making a copy of the range via take can repeat the cached element[4]. I find this argument severely lacking -- non-forward ranges are not meant to be copied and expected to operate properly, it's why we require calling save.Completely agree with you here. You can only use one of the copies. When you call popFront on one of them, that invalidates the other one. To get two valid copies, use .save. generate should not try to go beyond the contract here when it means broken behavior elsewhere.
Jun 02 2016
On 6/2/16 8:51 AM, Steven Schveighoffer wrote:I have always treated ranges with this expectation: 1. front gets you the current element of the range. Calling front multiple times without calling popFront should get you the same value. 2. popFront moves you to the next element of the range if it exists.Jack Stouffer has created a PR to formalize this. Please comment if you have objections (the group that argued for current generate behavior was absent from this post that was meant to be a debate). I think this is the right thing to do. https://github.com/dlang/phobos/pull/4511 -Steve
Jun 30 2016
On Thursday, 30 June 2016 at 14:17:22 UTC, Steven Schveighoffer wrote:Jack Stouffer has created a PR to formalize this. Please comment if you have objections (the group that argued for current generate behavior was absent from this post that was meant to be a debate). I think this is the right thing to do.It looks like this is just documentation changes. This really doesn't prevent anyone from making a Range that violates front stability. For instance, suppose isInputRange is changed so that auto h = r.front; // can get the front of the range becomes auto h = r.front; // can get the front of the range auto j = r.front; assert(h == j); //ensure front stability This doesn't seem to work for the map example above, but could it be adapted so that it does?
Jun 30 2016
On Thursday, 30 June 2016 at 14:59:11 UTC, jmh530 wrote:It looks like this is just documentation changes. This really doesn't prevent anyone from making a Range that violates front stability.No, but what it will do is to make it clear that these are either bugs or intended behavior, as this is still a point of disagreement.For instance, suppose isInputRange is changed so that auto h = r.front; // can get the front of the range becomes auto h = r.front; // can get the front of the range auto j = r.front; assert(h == j); //ensure front stabilityThis cannot be done at compile time.
Jun 30 2016
On 6/30/16 10:59 AM, jmh530 wrote:On Thursday, 30 June 2016 at 14:17:22 UTC, Steven Schveighoffer wrote:This doesn't solve it, and it can't be solved -- halting problem. We have some expectations that are assumed and some that are mechanically tested. This PR clarifies the assumptions. -SteveJack Stouffer has created a PR to formalize this. Please comment if you have objections (the group that argued for current generate behavior was absent from this post that was meant to be a debate). I think this is the right thing to do.It looks like this is just documentation changes. This really doesn't prevent anyone from making a Range that violates front stability. For instance, suppose isInputRange is changed so that auto h = r.front; // can get the front of the range becomes auto h = r.front; // can get the front of the range auto j = r.front; assert(h == j); //ensure front stability
Jun 30 2016
On 6/30/2016 8:32 AM, Steven Schveighoffer wrote:This doesn't solve it, and it can't be solved -- halting problem. We have some expectations that are assumed and some that are mechanically tested. This PR clarifies the assumptions.I made some range drivers that brutally test the protocol: http://www.digitalmars.com/sargon/asinputrange.html http://www.digitalmars.com/sargon/asforwardrange.html I think they should be part of Phobos.
Jun 30 2016
2016-06-02 14:51 GMT+02:00 Steven Schveighoffer via Digitalmars-d < digitalmars-d puremagic.com>:I have always treated ranges with this expectation:I think the case is pretty clear here, and I'm in agreement with you. I just want to add a note on the following point: 2016-06-02 14:51 GMT+02:00 Steven Schveighoffer via Digitalmars-d < digitalmars-d puremagic.com>:The counter-argument seems to be that if you cache the front element, then then making a copy of the range via take can repeat the cached element[4]. I find this argument severely lacking -- non-forward ranges are not meant to be copied and expected to operate properly, it's why we require calling save.The compiler is blatantly guilty of doing so: https://issues.dlang.org/show_bug.cgi?id=15413
Jun 30 2016
On 6/30/16 11:56 AM, Mathias Lang via Digitalmars-d wrote:2016-06-02 14:51 GMT+02:00 Steven Schveighoffer via Digitalmars-d <digitalmars-d puremagic.com <mailto:digitalmars-d puremagic.com>>: I have always treated ranges with this expectation: I think the case is pretty clear here, and I'm in agreement with you. I just want to add a note on the following point: 2016-06-02 14:51 GMT+02:00 Steven Schveighoffer via Digitalmars-d <digitalmars-d puremagic.com <mailto:digitalmars-d puremagic.com>>: The counter-argument seems to be that if you cache the front element, then then making a copy of the range via take can repeat the cached element[4]. I find this argument severely lacking -- non-forward ranges are not meant to be copied and expected to operate properly, it's why we require calling save. The compiler is blatantly guilty of doing so: https://issues.dlang.org/show_bug.cgi?id=15413I don't think that's a bug. The range definition says nothing about what happens on copying, or that non-forward ranges shouldn't be copied. What it says is that if you make a copy and use both, then there is no guarantee what happens. This is the use case in the argument I referenced. Unfortunately, for the likes of forward ranges, copying mainly always does the same thing as .save does. So you have tons and tons of code that doesn't properly use .save. Such is the way things are, and I'm not sure it will ever get better :( -Steve
Jun 30 2016
On Thursday, 30 June 2016 at 18:07:41 UTC, Steven Schveighoffer wrote:On 6/30/16 11:56 AM, Mathias Lang via Digitalmars-d wrote:Can we have ranges that disallow using them like rangeCache = range; without sacrificing usability in other cases? Going forward having ranges that simply do not compile when used that way would probably make lots of people actually honor .save and allow the compiler to point out buggy code (even if, in the end, .save really comes down to a no-op for many ranges).2016-06-02 14:51 GMT+02:00 Steven Schveighoffer via Digitalmars-d <digitalmars-d puremagic.com <mailto:digitalmars-d puremagic.com>>: I have always treated ranges with this expectation: I think the case is pretty clear here, and I'm in agreement with you. I just want to add a note on the following point: 2016-06-02 14:51 GMT+02:00 Steven Schveighoffer via Digitalmars-d <digitalmars-d puremagic.com <mailto:digitalmars-d puremagic.com>>: The counter-argument seems to be that if you cache the front element, then then making a copy of the range via take can repeat the cached element[4]. I find this argument severely lacking -- non-forward ranges are not meant to be copied and expected to operate properly, it's why we require calling save. The compiler is blatantly guilty of doing so: https://issues.dlang.org/show_bug.cgi?id=15413I don't think that's a bug. The range definition says nothing about what happens on copying, or that non-forward ranges shouldn't be copied. What it says is that if you make a copy and use both, then there is no guarantee what happens. This is the use case in the argument I referenced. Unfortunately, for the likes of forward ranges, copying mainly always does the same thing as .save does. So you have tons and tons of code that doesn't properly use .save. Such is the way things are, and I'm not sure it will ever get better :( -Steve
Jun 30 2016
On Thu, Jun 30, 2016 at 07:23:24PM +0000, default0 via Digitalmars-d wrote:On Thursday, 30 June 2016 at 18:07:41 UTC, Steven Schveighoffer wrote:[...] [...]Andrei has mentioned that, in retrospect, we should have defined forward ranges such that copying the range object *is* saving. (Though it is unclear to me what is to be done with by-reference ranges like range classes, since you'd still need a separate method to be able to copy the class object. Unless you wrap them in a struct that calls the copy method in its postblit.) That aside, though, I'm not sure how you'd enforce not copying a range. The range API is defined by constraints rather than a language-level Concept, so the actual range object could be any arbitrary type. It doesn't seem to make sense that the assignment operator should suddenly be prohibited just because it happens to have members named .front, .empty, .popFront. Besides, outside of generic code, what if the user-defined range is actually such that assignment does the Right Thing(tm)? The cases that we want to prohibit are restricted only to generic code, since code that already "knows" the concrete type shouldn't be arbitrarily restricted like this. T -- I think Debian's doing something wrong, `apt-get install pesticide', doesn't seem to remove the bugs on my system! -- Mike DresserUnfortunately, for the likes of forward ranges, copying mainly always does the same thing as .save does. So you have tons and tons of code that doesn't properly use .save. Such is the way things are, and I'm not sure it will ever get better :( -SteveCan we have ranges that disallow using them like rangeCache = range; without sacrificing usability in other cases? Going forward having ranges that simply do not compile when used that way would probably make lots of people actually honor .save and allow the compiler to point out buggy code (even if, in the end, .save really comes down to a no-op for many ranges).
Jun 30 2016
On Thursday, 30 June 2016 at 19:34:47 UTC, H. S. Teoh wrote:arbitrary type. It doesn't seem to make sense that the assignment operator should suddenly be prohibited just because it happens to have members named .front, .empty, .popFront.Seems like this could be resolved by requiring all range code to use an enclosure type that wraps the range functionality.
Jun 30 2016
On Thursday, 2 June 2016 at 12:51:18 UTC, Steven Schveighoffer wrote:The counter-argument seems to be that if you cache the front element, then then making a copy of the range via take can repeat the cached element[4]. I find this argument severely lacking -- non-forward ranges are not meant to be copied and expected to operate properly, it's why we require calling save. [4] https://github.com/dlang/phobos/pull/2606#issuecomment-58740715I think front returning the same value every time is the appropriate behavior for every range (I've had many times I wish this weren't true so I wouldn't have to implement caching). Map is no different, yes the function could technically change each time front is called, but that is an issue in the user code and not the implementation of map (map should only accept pure functions :)) I think the example of take() is a valid criticism. It is the issue of value ranges vs reference ranges all over again. Due to these distinct range types, providing a range to a function invalidates that range within the scope. It is not possible to know if the range will start at the same spot prior to calling function or if it will start after. In the case generator, we see a hybrid which isn't the only range that will do front() caching. Practically we don't run into issues with undefined behavior in our code, probably because we don't change out the type of range being used very often. I don't have a solution for this problem, but think having generator() add a new uncertainty (does calling front() a second time advance my range?) is a very bad precedence to set in Phobos.
Jun 30 2016
On 6/2/2016 5:51 AM, Steven Schveighoffer wrote:I have always treated ranges with this expectation: 1. front gets you the current element of the range. Calling front multiple times without calling popFront should get you the same value. 2. popFront moves you to the next element of the range if it exists.Right. Also: 3. empty can be called multiple times in a row, and must always return the same result. 4. it is not legal to call front() on an empty range, but it is not required to call empty() first 5. it is not legal to call popFront() on an empty range, but it is not required to call empty() or front() first.I'd say violating the expectations of what popFront and front do is more egregious than a particular use case, no matter how valid that case is. I'd like to fix this bug, but I see there were quite a few major D contributors in the camp that wanted this function to behave the way it is. So I'd rather first debate the merits here.This was debated at length in the past. The result was the rules above. Ranges that do not follow those rules need to be fixed. I don't think there's a need to debate it again.
Jun 30 2016
On 6/30/16 7:50 PM, Walter Bright wrote:On 6/2/2016 5:51 AM, Steven Schveighoffer wrote:Jack, can you update the rules to reflect this?I have always treated ranges with this expectation: 1. front gets you the current element of the range. Calling front multiple times without calling popFront should get you the same value. 2. popFront moves you to the next element of the range if it exists.Right. Also: 3. empty can be called multiple times in a row, and must always return the same result. 4. it is not legal to call front() on an empty range, but it is not required to call empty() first 5. it is not legal to call popFront() on an empty range, but it is not required to call empty() or front() first.Thank you, I will set up a PR to fix generate. -SteveI'd say violating the expectations of what popFront and front do is more egregious than a particular use case, no matter how valid that case is. I'd like to fix this bug, but I see there were quite a few major D contributors in the camp that wanted this function to behave the way it is. So I'd rather first debate the merits here.This was debated at length in the past. The result was the rules above. Ranges that do not follow those rules need to be fixed. I don't think there's a need to debate it again.
Jun 30 2016