www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Ranges: is it ok if front is a data member?

reply "Adam D. Ruppe" <destructionator gmail.com> writes:
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
next sibling parent =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
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
prev sibling next sibling parent "Dicebot" <public dicebot.lv> writes:

to be "standard" answer to this question (== yes, it is ok).
Dec 12 2013
prev sibling next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
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
prev sibling next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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
next sibling parent "Adam D. Ruppe" <destructionator gmail.com> writes:
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
prev sibling parent reply Marco Leise <Marco.Leise gmx.de> writes:
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
 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 =3D false; T current; property T front() { if (!popFrontHasBeenCalledOnce) { popFront(); // initializes `current' } return current; } [=E2=80=A6] } --=20 Marco
Dec 13 2013
next sibling parent Joseph Rushton Wakeling <joseph.wakeling webdrake.net> writes:
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
prev sibling next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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 -- Жил-был король когда-то, при нём блоха жила.
Dec 13 2013
prev sibling parent reply "Jakob Ovrum" <jakobovrum gmail.com> writes:
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.
Dec 14 2013
parent reply "Joseph Rushton Wakeling" <joseph.wakeling webdrake.net> writes:
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:
 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.
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().
Dec 14 2013
parent Marco Leise <Marco.Leise gmx.de> writes:
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:
 On Friday, 13 December 2013 at 14:52:32 UTC, 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:
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 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().
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. -- Marco
Dec 14 2013
prev sibling next sibling parent reply Joseph Rushton Wakeling <joseph.wakeling webdrake.net> writes:
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
next sibling parent "Adam D. Ruppe" <destructionator gmail.com> writes:
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
prev sibling parent reply "Jesse Phillips" <Jesse.K.Phillips+D gmail.com> writes:
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.
Dec 12 2013
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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 -- Тише едешь, дальше будешь.
Dec 12 2013
parent reply =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
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. 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
Dec 12 2013
next sibling parent Joseph Rushton Wakeling <joseph.wakeling webdrake.net> writes:
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
prev sibling parent reply "qznc" <qznc web.de> writes:
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
parent "Chris Cain" <clcain uncg.edu> writes:
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
prev sibling next sibling parent Joseph Rushton Wakeling <joseph.wakeling webdrake.net> writes:
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
prev sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
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