digitalmars.D.learn - Ranges: is it ok if front is a data member?
- Adam D. Ruppe (15/15) Dec 12 2013 Consider the following:
- =?UTF-8?B?QWxpIMOHZWhyZWxp?= (7/14) Dec 12 2013 Yes it is perfectly fine. And if it works for empty it should work for
- Dicebot (2/2) Dec 12 2013 I'd expect http://dlang.org/phobos/std_range.html#.isInputRange
- bearophile (6/13) Dec 12 2013 It seems similar to 0.repeat
- H. S. Teoh (9/28) Dec 12 2013 I do this with my own ranges sometimes. Sometimes, it's more performant
- Adam D. Ruppe (5/8) Dec 12 2013 Yeah, that's exactly what I was doing here.
- Marco Leise (21/29) Dec 13 2013 Most non-trivial ranges do the actual work in `popFront()' and
- Joseph Rushton Wakeling (7/24) Dec 13 2013 For example in much of std.random. With classes you can get round it by...
- H. S. Teoh (30/60) Dec 13 2013 Hmm.
- Jakob Ovrum (5/22) Dec 14 2013 Really? I've never seen that particular pattern. I always just
- Joseph Rushton Wakeling (6/14) Dec 14 2013 I don't know about "most non-trivial ranges" but it is a pattern
- Marco Leise (9/25) Dec 14 2013 Am Sat, 14 Dec 2013 16:38:20 +0100
- Joseph Rushton Wakeling (6/13) Dec 12 2013 Isn't the issue here not whether or not it will work in terms of your ty...
- Adam D. Ruppe (6/8) Dec 12 2013 Yeah, that's what I meant by the lvalue thing, though most the
- Jesse Phillips (3/23) Dec 12 2013 Being able to assign to front is a feature of an output range.
- H. S. Teoh (6/30) Dec 12 2013 Really?? I thought the defining feature of an output range is the .put
- =?UTF-8?B?QWxpIMOHZWhyZWxp?= (14/42) Dec 12 2013 The third condition that is checked to determine whether it is an
- Joseph Rushton Wakeling (5/14) Dec 12 2013 Ouch!!
- qznc (3/16) Dec 12 2013 Ouch. That surely is confusing. Why don't arrays provide .put
- Chris Cain (11/13) Dec 12 2013 Basically, when you're using an array as an output range, you're
- Joseph Rushton Wakeling (4/6) Dec 12 2013 ... if OTOH the idea is that front never changes, then I'd suggest an en...
- Jonathan M Davis (19/38) Dec 12 2013 It's perfectly fine as far as isInputRange goes.
Consider the following: struct JustZeroes { int front = 0; enum bool = false; void popFront() {} } Is that guaranteed to work as an input range? I ask because I've so often written: T current; property T front() { return current; } that it just seems silly to me to write the extra lines when current == front. I realize there is a small difference there, in that front is not an lvalue here, but is when it is a direct member, but other than that, is this an acceptable form? Or does the lvalue thing mean it is strongly discouraged?
Dec 12 2013
On 12/12/2013 08:19 AM, Adam D. Ruppe wrote:Consider the following: struct JustZeroes { int front = 0; enum bool = false;You meant empty = false;void popFront() {} } Is that guaranteed to work as an input range?Yes it is perfectly fine. And if it works for empty it should work for front. ;) The presence of std.range.hasLvalueElements is another indicator that it fine. Finally, if std.range.isInputRange returns true, it is an InputRange. Ali
Dec 12 2013
to be "standard" answer to this question (== yes, it is ok).
Dec 12 2013
Adam D. Ruppe:Consider the following: struct JustZeroes { int front = 0; enum bool = false; void popFront() {} } Is that guaranteed to work as an input range?It seems similar to 0.repeat When you are not sure add a static assert below the range, to verify it is the kind of range you want. Bye, bearophile
Dec 12 2013
On Thu, Dec 12, 2013 at 05:19:28PM +0100, Adam D. Ruppe wrote:Consider the following: struct JustZeroes { int front = 0; enum bool = false; void popFront() {} } Is that guaranteed to work as an input range? I ask because I've so often written: T current; property T front() { return current; } that it just seems silly to me to write the extra lines when current == front. I realize there is a small difference there, in that front is not an lvalue here, but is when it is a direct member, but other than that, is this an acceptable form? Or does the lvalue thing mean it is strongly discouraged?I do this with my own ranges sometimes. Sometimes, it's more performant to precompute the value of .front and store it (as .front), and have .popFront compute the next value, than to have .front compute the value every time. AFAICT, this is perfectly fine and should work with Phobos seamlessly. The power of ducktyping! T -- Quack!
Dec 12 2013
On Thursday, 12 December 2013 at 16:45:05 UTC, H. S. Teoh wrote:I do this with my own ranges sometimes. Sometimes, it's more performant to precompute the value of .front and store it (as .front)Yeah, that's exactly what I was doing here. My question was mostly on if there's the chance that someone will call front() but looks like that isn't supposed to happen and is wrong if it does, so cool.
Dec 12 2013
Am Thu, 12 Dec 2013 08:43:35 -0800 schrieb "H. S. Teoh" <hsteoh quickfur.ath.cx>:I do this with my own ranges sometimes. Sometimes, it's more performant to precompute the value of .front and store it (as .front), and have .popFront compute the next value, than to have .front compute the value every time. AFAICT, this is perfectly fine and should work with Phobos seamlessly. The power of ducktyping! =20 =20 TMost non-trivial ranges do the actual work in `popFront()' and return a cached value from `front'. It has been argued as a design quirk, that this in general leads to: struct Range { bool popFrontHasBeenCalledOnce =3D false; T current; property T front() { if (!popFrontHasBeenCalledOnce) { popFront(); // initializes `current' } return current; } [=E2=80=A6] } --=20 Marco
Dec 13 2013
On 13/12/13 16:52, Marco Leise wrote:Most non-trivial ranges do the actual work in `popFront()' and return a cached value from `front'. It has been argued as a design quirk, that this in general leads to: struct Range { bool popFrontHasBeenCalledOnce = false; T current; property T front() { if (!popFrontHasBeenCalledOnce) { popFront(); // initializes `current' } return current; } […] }For example in much of std.random. With classes you can get round it by defining a default constructor, but with structs it can create some tricky situations. I have wondered about the feasibility of a method called something like .first() which would basically be called the very first time one calls _any_ method of the struct/class in question, and would perform the appropriate initialization.
Dec 13 2013
On Fri, Dec 13, 2013 at 04:20:18PM +0100, Joseph Rushton Wakeling wrote:On 13/12/13 16:52, Marco Leise wrote:Hmm. struct First(T) /* bad name, I know */ if (is(T.init.first())) { T impl; bool doneFirst; auto opDispatch(string funcName, A...)(A args) { if (!doneFirst) { impl.first(); doneFirst = true; } alias func = mixin("impl." ~ func); // does this work? return func(args); } } struct MyStructImpl { void first() { ... } void method() { ... } } alias MyStruct = First!MyStructImpl; MyStruct s; s.method(); // calls s.first() first. s.method(); // only calls method(). T -- Жил-был король когда-то, при нём блоха жила.Most non-trivial ranges do the actual work in `popFront()' and return a cached value from `front'. It has been argued as a design quirk, that this in general leads to: struct Range { bool popFrontHasBeenCalledOnce = false; T current; property T front() { if (!popFrontHasBeenCalledOnce) { popFront(); // initializes `current' } return current; } […] }For example in much of std.random. With classes you can get round it by defining a default constructor, but with structs it can create some tricky situations. I have wondered about the feasibility of a method called something like .first() which would basically be called the very first time one calls _any_ method of the struct/class in question, and would perform the appropriate initialization.
Dec 13 2013
On Friday, 13 December 2013 at 14:52:32 UTC, Marco Leise wrote:Am Thu, 12 Dec 2013 08:43:35 -0800 schrieb "H. S. Teoh" <hsteoh quickfur.ath.cx>:Really? I've never seen that particular pattern. I always just see the initial element being computed when the range is initialized, e.g. in a constructor or a constructor helper function.I do this with my own ranges sometimes. Sometimes, it's more performant to precompute the value of .front and store it (as .front), and have .popFront compute the next value, than to have .front compute the value every time. AFAICT, this is perfectly fine and should work with Phobos seamlessly. The power of ducktyping! TMost non-trivial ranges do the actual work in `popFront()' and return a cached value from `front'. It has been argued as a design quirk, that this in general leads to:
Dec 14 2013
On Saturday, 14 December 2013 at 15:26:36 UTC, Jakob Ovrum wrote:On Friday, 13 December 2013 at 14:52:32 UTC, Marco Leise wrote:I don't know about "most non-trivial ranges" but it is a pattern that shows up when you have a struct that cannot be guaranteed to be properly initialized upon creation -- which is a common situation because structs don't allow a default constructor this().Most non-trivial ranges do the actual work in `popFront()' and return a cached value from `front'. It has been argued as a design quirk, that this in general leads to:Really? I've never seen that particular pattern. I always just see the initial element being computed when the range is initialized, e.g. in a constructor or a constructor helper function.
Dec 14 2013
Am Sat, 14 Dec 2013 16:38:20 +0100 schrieb "Joseph Rushton Wakeling" <joseph.wakeling webdrake.net>:On Saturday, 14 December 2013 at 15:26:36 UTC, Jakob Ovrum wrote:I probably over-dramatized this from my own experience and a recent thread in digitalmars.D. Looking at some old code of mine I actually call .popFront() in a ctor where I thought I used a boolean flag. It's not a big deal in any case. -- MarcoOn Friday, 13 December 2013 at 14:52:32 UTC, Marco Leise wrote:I don't know about "most non-trivial ranges" but it is a pattern that shows up when you have a struct that cannot be guaranteed to be properly initialized upon creation -- which is a common situation because structs don't allow a default constructor this().Most non-trivial ranges do the actual work in `popFront()' and return a cached value from `front'. It has been argued as a design quirk, that this in general leads to:Really? I've never seen that particular pattern. I always just see the initial element being computed when the range is initialized, e.g. in a constructor or a constructor helper function.
Dec 14 2013
On 12/12/13 17:19, Adam D. Ruppe wrote:Is that guaranteed to work as an input range? I ask because I've so often written: T current; property T front() { return current; } that it just seems silly to me to write the extra lines when current == front. I realize there is a small difference there, in that front is not an lvalue here, but is when it is a direct member, but other than that, is this an acceptable form? Or does the lvalue thing mean it is strongly discouraged?Isn't the issue here not whether or not it will work in terms of your type being a range, and more that it means that users can overwrite the value of front? It seems to me that it would be OK for personal projects where you control 100% of the code, but it wouldn't be acceptable for stuff that's intended to be used by other people.
Dec 12 2013
On Thursday, 12 December 2013 at 16:52:12 UTC, Joseph Rushton Wakeling wrote:and more that it means that users can overwrite the value of front?Yeah, that's what I meant by the lvalue thing, though most the time I don't think it is even that big of a deal if it gets overwritten since the main use of ranges for me is foreach loops anyway. But yeah, that is a good point.
Dec 12 2013
On Thursday, 12 December 2013 at 16:52:12 UTC, Joseph Rushton Wakeling wrote:On 12/12/13 17:19, Adam D. Ruppe wrote:Being able to assign to front is a feature of an output range.Is that guaranteed to work as an input range? I ask because I've so often written: T current; property T front() { return current; } that it just seems silly to me to write the extra lines when current == front. I realize there is a small difference there, in that front is not an lvalue here, but is when it is a direct member, but other than that, is this an acceptable form? Or does the lvalue thing mean it is strongly discouraged?Isn't the issue here not whether or not it will work in terms of your type being a range, and more that it means that users can overwrite the value of front? It seems to me that it would be OK for personal projects where you control 100% of the code, but it wouldn't be acceptable for stuff that's intended to be used by other people.
Dec 12 2013
On Thu, Dec 12, 2013 at 08:12:50PM +0100, Jesse Phillips wrote:On Thursday, 12 December 2013 at 16:52:12 UTC, Joseph Rushton Wakeling wrote:Really?? I thought the defining feature of an output range is the .put method. T -- Тише едешь, дальше будешь.On 12/12/13 17:19, Adam D. Ruppe wrote:Being able to assign to front is a feature of an output range.Is that guaranteed to work as an input range? I ask because I've so often written: T current; property T front() { return current; } that it just seems silly to me to write the extra lines when current == front. I realize there is a small difference there, in that front is not an lvalue here, but is when it is a direct member, but other than that, is this an acceptable form? Or does the lvalue thing mean it is strongly discouraged?Isn't the issue here not whether or not it will work in terms of your type being a range, and more that it means that users can overwrite the value of front? It seems to me that it would be OK for personal projects where you control 100% of the code, but it wouldn't be acceptable for stuff that's intended to be used by other people.
Dec 12 2013
On 12/12/2013 12:06 PM, H. S. Teoh wrote:On Thu, Dec 12, 2013 at 08:12:50PM +0100, Jesse Phillips wrote:The third condition that is checked to determine whether it is an OutputRange is indeed assignment to front. That condition is what makes a slice an OutputRange, which causes the super confusing state of "output range losing elements after put'ting": :) import std.range; void main() { auto s = [ 1, 2, 3 ]; s.put(10); assert(s.length == 2); // PASSES! :p } AliOn Thursday, 12 December 2013 at 16:52:12 UTC, Joseph Rushton Wakeling wrote:Really?? I thought the defining feature of an output range is the .put method. TOn 12/12/13 17:19, Adam D. Ruppe wrote:Being able to assign to front is a feature of an output range.Is that guaranteed to work as an input range? I ask because I've so often written: T current; property T front() { return current; } that it just seems silly to me to write the extra lines when current == front. I realize there is a small difference there, in that front is not an lvalue here, but is when it is a direct member, but other than that, is this an acceptable form? Or does the lvalue thing mean it is strongly discouraged?Isn't the issue here not whether or not it will work in terms of your type being a range, and more that it means that users can overwrite the value of front? It seems to me that it would be OK for personal projects where you control 100% of the code, but it wouldn't be acceptable for stuff that's intended to be used by other people.
Dec 12 2013
On 12/12/13 22:55, Ali Çehreli wrote:That condition is what makes a slice an OutputRange, which causes the super confusing state of "output range losing elements after put'ting": :) import std.range; void main() { auto s = [ 1, 2, 3 ]; s.put(10); assert(s.length == 2); // PASSES! :p }Ouch!! I see why it happens, but I really, really don't like that. Isn't there a case here for an override to put specifically for arrays? Or are there some benefits to it working like this?
Dec 12 2013
On Thursday, 12 December 2013 at 21:55:20 UTC, Ali Çehreli wrote:The third condition that is checked to determine whether it is an OutputRange is indeed assignment to front. That condition is what makes a slice an OutputRange, which causes the super confusing state of "output range losing elements after put'ting": :) import std.range; void main() { auto s = [ 1, 2, 3 ]; s.put(10); assert(s.length == 2); // PASSES! :p }Ouch. That surely is confusing. Why don't arrays provide .put which appends the element?
Dec 12 2013
On Friday, 13 December 2013 at 07:28:57 UTC, qznc wrote:Ouch. That surely is confusing. Why don't arrays provide .put which appends the element?Basically, when you're using an array as an output range, you're saying "Hey, here's a slice of memory to put the results in". You're _not_ saying "hey, here's a bit of memory that I have already, so put it on the end of it." The behavior is technically correct, but output ranges probably aren't being described as well as they could be. That said, the concept is a bit unusual and strange to me as well. I love the concept of InputRanges but I'd really like to see more coverage on OutputRanges and how to work effectively with them (and, especially, arrays as OutputRanges).
Dec 12 2013
On 12/12/13 17:51, Joseph Rushton Wakeling wrote:Isn't the issue here not whether or not it will work in terms of your type being a range, and more that it means that users can overwrite the value of front?... if OTOH the idea is that front never changes, then I'd suggest an enum, as is already common for empty (e.g. in RNGs, you typically get "enum bool empty = false").
Dec 12 2013
On Thursday, December 12, 2013 17:19:28 Adam D. Ruppe wrote:Consider the following: struct JustZeroes { int front = 0; enum bool = false; void popFront() {} } Is that guaranteed to work as an input range? I ask because I've so often written: T current; property T front() { return current; } that it just seems silly to me to write the extra lines when current == front. I realize there is a small difference there, in that front is not an lvalue here, but is when it is a direct member, but other than that, is this an acceptable form? Or does the lvalue thing mean it is strongly discouraged?It's perfectly fine as far as isInputRange goes. template isInputRange(R) { enum bool isInputRange = is(typeof( (inout int = 0) { R r = void; // can define a range object if (r.empty) {} // can test for empty r.popFront(); // can invoke popFront() auto h = r.front; // can get the front of the range })); } All that's required with regards to front is auto h = r.front; That works perfectly fine with a variable. The primary reason to avoid it is encapsulation, but if you aren't worried about that, then it should work just fine to have front as a public member variable. - Jonathan M Davis
Dec 12 2013