December 12, 2013 Re: Ranges: is it ok if front is a data member? | ||||
---|---|---|---|---|
| ||||
Posted in reply to Jesse Phillips | 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: > >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. > > Being able to assign to front is a feature of an output range. Really?? I thought the defining feature of an output range is the .put method. T -- Тише едешь, дальше будешь. |
December 12, 2013 Re: Ranges: is it ok if front is a data member? | ||||
---|---|---|---|---|
| ||||
Posted in reply to H. S. Teoh | On 12/12/2013 12:06 PM, H. S. Teoh wrote: > 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: >>> 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. >> >> Being able to assign to front is a feature of an output range. > > Really?? I thought the defining feature of an output range is the .put > method. > > > T > The third condition that is checked to determine whether it is an OutputRange is indeed assignment to front. http://dlang.org/phobos/std_range.html#.put 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 } Ali |
December 12, 2013 Re: Ranges: is it ok if front is a data member? | ||||
---|---|---|---|---|
| ||||
Posted in reply to Ali Çehreli | 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?
|
December 13, 2013 Re: Ranges: is it ok if front is a data member? | ||||
---|---|---|---|---|
| ||||
Posted in reply to Adam D. Ruppe | 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
|
December 13, 2013 Re: Ranges: is it ok if front is a data member? | ||||
---|---|---|---|---|
| ||||
Posted in reply to Ali Çehreli | 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.
>
> http://dlang.org/phobos/std_range.html#.put
>
> 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?
|
December 13, 2013 Re: Ranges: is it ok if front is a data member? | ||||
---|---|---|---|---|
| ||||
Posted in reply to qznc | 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).
|
December 13, 2013 Re: Ranges: is it ok if front is a data member? | ||||
---|---|---|---|---|
| ||||
Posted in reply to H. S. Teoh | 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! > > > 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; } […] } -- Marco |
December 13, 2013 Re: Ranges: is it ok if front is a data member? | ||||
---|---|---|---|---|
| ||||
Posted in reply to Marco Leise | 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.
|
December 13, 2013 Re: Ranges: is it ok if front is a data member? | ||||
---|---|---|---|---|
| ||||
On Fri, Dec 13, 2013 at 04:20:18PM +0100, Joseph Rushton Wakeling wrote: > 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. 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 -- Жил-был король когда-то, при нём блоха жила. |
December 14, 2013 Re: Ranges: is it ok if front is a data member? | ||||
---|---|---|---|---|
| ||||
Posted in reply to Marco Leise | 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>:
>
>> 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
>
> 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.
|
Copyright © 1999-2021 by the D Language Foundation