www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - getNext

reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
I think I figured out a comfortable and all-encompassing means to define 
a simplified interface for an input range.

Currently input ranges need to define empty, front, and popFront. That 
works but it's a bit heavy for simple input ranges. We've been 
discussing simplified interfaces in this group but couldn't find one 
that satisfied all use cases.

Consider this:

T* getNext(R, T)(ref R range, ref T item);

Semantics: if the range wants to expose addresses of its elements, it 
returns a pointer to the current element and also advances to the next 
element. Otherwise (i.e. the range does not have or does not want to 
expose addresses of its elements), the range fills "item" with the 
current value, again moves on to the next value, and returns &item.

In all cases, when there are no more elements in the range, getNext 
returns null.

getNext is easy to define for e.g. arrays and files. How does it sound? 
Does it bring significant simplification?


Andrei
Jul 12 2010
next sibling parent reply Jonathan M Davis <jmdavisprog gmail.com> writes:
On Monday 12 July 2010 20:48:05 Andrei Alexandrescu wrote:
 I think I figured out a comfortable and all-encompassing means to define
 a simplified interface for an input range.
 
 Currently input ranges need to define empty, front, and popFront. That
 works but it's a bit heavy for simple input ranges. We've been
 discussing simplified interfaces in this group but couldn't find one
 that satisfied all use cases.
 
 Consider this:
 
 T* getNext(R, T)(ref R range, ref T item);
 
 Semantics: if the range wants to expose addresses of its elements, it
 returns a pointer to the current element and also advances to the next
 element. Otherwise (i.e. the range does not have or does not want to
 expose addresses of its elements), the range fills "item" with the
 current value, again moves on to the next value, and returns &item.
 
 In all cases, when there are no more elements in the range, getNext
 returns null.
 
 getNext is easy to define for e.g. arrays and files. How does it sound?
 Does it bring significant simplification?
 
 
 Andrei
What happens to empty, front, and popFront then? Is this a case where something must define either empty, front, and popFront or getNext to be an input range? Or is this something else? Personally, I find empty, front, and popFront quite useful and simple for anything that I've done, and I'd find getNext to be a lot more verbose. getNext may be great if you're using empty, front, and popFront pretty much simultaneously, but if you don't want to use all of them for whatever you're doing, then getNext is overkill. So, essentially, I suppose the issue is that I don't see what you intend to do to front, popFront, and empty if you add getNext into the mix. I do not want to see front, popFront, or empty go away. Having getNext as an additional function to make it easier to iterate over a range and do something with each element as you iterate wouldn't hurt my feelings any, but I definitely don't want to lose popFront, empty, or front. As for simplification, it strikes me as more complicated in every case except where you are iterating over a range and processing each element as you iterate. Granted, that's a common use case, but there are plenty of other cases, where if you were forced to use getNext instead of having popFront, empty, and front, that would be a major problem. - Jonathan M Davis
Jul 12 2010
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 07/12/2010 11:21 PM, Jonathan M Davis wrote:
 On Monday 12 July 2010 20:48:05 Andrei Alexandrescu wrote:
 I think I figured out a comfortable and all-encompassing means to define
 a simplified interface for an input range.

 Currently input ranges need to define empty, front, and popFront. That
 works but it's a bit heavy for simple input ranges. We've been
 discussing simplified interfaces in this group but couldn't find one
 that satisfied all use cases.

 Consider this:

 T* getNext(R, T)(ref R range, ref T item);

 Semantics: if the range wants to expose addresses of its elements, it
 returns a pointer to the current element and also advances to the next
 element. Otherwise (i.e. the range does not have or does not want to
 expose addresses of its elements), the range fills "item" with the
 current value, again moves on to the next value, and returns&item.

 In all cases, when there are no more elements in the range, getNext
 returns null.

 getNext is easy to define for e.g. arrays and files. How does it sound?
 Does it bring significant simplification?


 Andrei
What happens to empty, front, and popFront then? Is this a case where something must define either empty, front, and popFront or getNext to be an input range? Or is this something else? Personally, I find empty, front, and popFront quite useful and simple for anything that I've done, and I'd find getNext to be a lot more verbose. getNext may be great if you're using empty, front, and popFront pretty much simultaneously, but if you don't want to use all of them for whatever you're doing, then getNext is overkill.
An input range could either define the troika empty/front/popFront or getNext. In the former case, getNext detects the presence of the troika and uses it transparently. That's great because client code can simply use getNext throughout.
 So, essentially, I suppose the issue is that I don't see what you intend to do
 to front, popFront, and empty if you add getNext into the mix.
There is no aggravation brought to the current definitions.
 I do not want to
 see front, popFront, or empty go away. Having getNext as an additional function
 to make it easier to iterate over a range and do something with each element as
 you iterate wouldn't hurt my feelings any, but I definitely don't want to lose
 popFront, empty, or front.
My feelings too. The troika is here to stay, and definitely necessary for any range richer than an input range.
 As for simplification, it strikes me as more complicated in every case except
 where you are iterating over a range and processing each element as you
iterate.
 Granted, that's a common use case, but there are plenty of other cases, where
if
 you were forced to use getNext instead of having popFront, empty, and front,
 that would be a major problem.
Yah, truth be told getNext won't win a prize for brevity. You need to define both a variable and a pointer to use it: T meh; T * neh; while ((neh = getNext(r, meh))) { ... process *neh ... } I've just had an idea that is so dark and devious, I was almost afraid to try it. But it works like a charm. Consider: T * getNext(R, E)(ref R range, ref E store = *(cast(E*) alloca(E.sizeof)) { ... } With this, allocating a dummy buffer on caller's stack is automated, so client code can just write: for (T * p; (p = getNext(r)); ) { ... process *p ... } I feel dirty. Andrei
Jul 12 2010
next sibling parent "Rory McGuire" <rmcguire neonova.co.za> writes:
On Tue, 13 Jul 2010 06:38:55 +0200, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:

 On 07/12/2010 11:21 PM, Jonathan M Davis wrote:
 On Monday 12 July 2010 20:48:05 Andrei Alexandrescu wrote:
 I think I figured out a comfortable and all-encompassing means to  
 define
 a simplified interface for an input range.

 Currently input ranges need to define empty, front, and popFront. That
 works but it's a bit heavy for simple input ranges. We've been
 discussing simplified interfaces in this group but couldn't find one
 that satisfied all use cases.

 Consider this:

 T* getNext(R, T)(ref R range, ref T item);

 Semantics: if the range wants to expose addresses of its elements, it
 returns a pointer to the current element and also advances to the next
 element. Otherwise (i.e. the range does not have or does not want to
 expose addresses of its elements), the range fills "item" with the
 current value, again moves on to the next value, and returns&item.

 In all cases, when there are no more elements in the range, getNext
 returns null.

 getNext is easy to define for e.g. arrays and files. How does it sound?
 Does it bring significant simplification?


 Andrei
What happens to empty, front, and popFront then? Is this a case where something must define either empty, front, and popFront or getNext to be an input range? Or is this something else? Personally, I find empty, front, and popFront quite useful and simple for anything that I've done, and I'd find getNext to be a lot more verbose. getNext may be great if you're using empty, front, and popFront pretty much simultaneously, but if you don't want to use all of them for whatever you're doing, then getNext is overkill.
An input range could either define the troika empty/front/popFront or getNext. In the former case, getNext detects the presence of the troika and uses it transparently. That's great because client code can simply use getNext throughout.
 So, essentially, I suppose the issue is that I don't see what you  
 intend to do
 to front, popFront, and empty if you add getNext into the mix.
There is no aggravation brought to the current definitions.
 I do not want to
 see front, popFront, or empty go away. Having getNext as an additional  
 function
 to make it easier to iterate over a range and do something with each  
 element as
 you iterate wouldn't hurt my feelings any, but I definitely don't want  
 to lose
 popFront, empty, or front.
My feelings too. The troika is here to stay, and definitely necessary for any range richer than an input range.
 As for simplification, it strikes me as more complicated in every case  
 except
 where you are iterating over a range and processing each element as you  
 iterate.
 Granted, that's a common use case, but there are plenty of other cases,  
 where if
 you were forced to use getNext instead of having popFront, empty, and  
 front,
 that would be a major problem.
Yah, truth be told getNext won't win a prize for brevity. You need to define both a variable and a pointer to use it: T meh; T * neh; while ((neh = getNext(r, meh))) { ... process *neh ... } I've just had an idea that is so dark and devious, I was almost afraid to try it. But it works like a charm. Consider: T * getNext(R, E)(ref R range, ref E store = *(cast(E*) alloca(E.sizeof)) { ... } With this, allocating a dummy buffer on caller's stack is automated, so client code can just write: for (T * p; (p = getNext(r)); ) { ... process *p ... } I feel dirty. Andrei
:) I like it. But then looking at the alloca man page... yikes
Jul 12 2010
prev sibling next sibling parent bearophile <bearophileHUGS lycos.com> writes:
Andrei Alexandrescu:
 T * getNext(R, E)(ref R range,
                    ref E store = *(cast(E*) alloca(E.sizeof))
 {
Time ago I have filed bug http://d.puremagic.com/issues/show_bug.cgi?id=3822 on alloca(), but I think this code is not hit by it. I have recently written some C99 code and I have appreciated its Variable Length Arrays both in syntax, safety and performance gain. Compared to alloca() the VLAs have some advantages: - Their syntax is shorter and nicer, and it's natural for a C programmer; - no imports needed; - the semantics is more clear, because they define a new variable, so the size of their scope is the same as the in all other variables, while alloca() seems to have two possible different implementations; - VLAs are more typesafe, there is no need to use a cast. While the cast needed by alloca() may forbid it in SafeD code. - VLAs don't need sizeof(T), you just specify a type. The result is that the usage of alloca() feels dirty in both C and D, but Variable Length Arrays (VLA) of C99 don't feel dirty at all. On the other hand VLAs (and alloca) can produce a stack overflow, they are not so commonly useful (as alloca), and they can become essentially a third kind of arrays for D (this is not good). In D I'd like something like alloca() that needs no casts and is able to find the size by itself, avoiding the bug prone usage of T.sizeof. A way to do it is to use the same syntax used by C99 and allow a variable in the definition of a stack array: auto foo(int n) { int[n] arr; return arr; } The main difference is that in D when you return arr it gets copied by value. Currently this code works: int foo(int n) { return 3 * n + 1; } auto bar() { immutable int n = 5; int[foo(n)] arr; return arr; } void main() {} because dmd runs foo() at compile time, so arr is allocated on the stack with a statically known size. If VLAs get introduced in D, then in this case the compiler has to do what it currently it doesn't do: to run a function at compile-time if possible (and create a fixed length array) and run it at runtime if that's impossible (and create a VLA). To avoid that in some cases you can use something like: int[StaticValue!(n)] arr; Where StaticValue is a template that makes sure n is a value always known at compile-time. But this is a little messy, and I don't like it too much. Keep in mind that currently this gives an error: int[] bar() { int[2] arr; return arr; } void main() {} You have to use the auto return type or: int[2] bar() { int[2] arr; return arr; } void main() {} With VLAs you are forced to use auto, but it can't work anyway at the return point. So I think the normal C99 syntax for VLAs is not good for D2. On the other hand alloca() semantics and syntax are bad. So D alloca() can be replaced by something better like: T* ptr = StackAlloc!T(n); Or: T* ptr = Alloca!T(n); Or: T[] arr = VLA!T(n); But this created an array that looks like a dynamic array, but its memory is on the stack so it must not escape the function. So it can be a bug-prone, similar to this that currently compiles: int[] foo() { int[1] stackArray; int[] dynArray = stackArray[0 .. 1]; return dynArray; } void main() {} See enhancement request http://d.puremagic.com/issues/show_bug.cgi?id=4451 So with the syntax T[] arr=VLA!T(n); the compiler has to disallow the return of arr and its slices. Whith those three syntaxes your signature becomes: T* getNext(R, E)(ref R range, ref E store = *StackAlloc!E(1)) { T* getNext(R, E)(ref R range, ref E store = *Alloca!E(1)) { T* getNext(R, E)(ref R range, ref E store = *(VLA!E(1).ptr)) { Bye, bearophile
Jul 13 2010
prev sibling next sibling parent reply Shin Fujishiro <rsinfu gmail.com> writes:
Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:
 I've just had an idea that is so dark and devious, I was almost afraid 
 to try it. But it works like a charm. Consider:
 
 T * getNext(R, E)(ref R range,
                    ref E store = *(cast(E*) alloca(E.sizeof))
 {
      ...
 }
 
 With this, allocating a dummy buffer on caller's stack is automated, so 
 client code can just write:
 
 for (T * p; (p = getNext(r)); )  {
     ... process *p ...
 }
 
 I feel dirty.
How about a TLS variable? template temporary(T) { static T temporary; } E* getNext(R, E)(ref R range, ref E store = temporary!E); Shin
Jul 13 2010
next sibling parent Shin Fujishiro <rsinfu gmail.com> writes:
Shin Fujishiro <rsinfu gmail.com> wrote:
 How about a TLS variable?
 
 template temporary(T)
 {
     static T temporary;
 }
 E* getNext(R, E)(ref R range, ref E store = temporary!E);
Yeah, I just missed it. TLS is not usable. I had spoken carelessly... Shin
Jul 13 2010
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 07/13/2010 05:09 AM, Shin Fujishiro wrote:
 Andrei Alexandrescu<SeeWebsiteForEmail erdani.org>  wrote:
 I've just had an idea that is so dark and devious, I was almost afraid
 to try it. But it works like a charm. Consider:

 T * getNext(R, E)(ref R range,
                     ref E store = *(cast(E*) alloca(E.sizeof))
 {
       ...
 }

 With this, allocating a dummy buffer on caller's stack is automated, so
 client code can just write:

 for (T * p; (p = getNext(r)); )  {
      ... process *p ...
 }

 I feel dirty.
How about a TLS variable? template temporary(T) { static T temporary; } E* getNext(R, E)(ref R range, ref E store = temporary!E);
There's the classic problem of reusing the same temporary. Consider: Range r1, r2; ElementType!Range * p1, p2; while ((p1 = getNext(r1)) && (p2 = getNext(r2))) { ... oops ... } You need one temporary for each static occurrence of getNext. Andrei
Jul 13 2010
parent Shin Fujishiro <rsinfu gmail.com> writes:
Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:
 There's the classic problem of reusing the same temporary. Consider:
 
 Range r1, r2;
 ElementType!Range * p1, p2;
 while ((p1 = getNext(r1)) && (p2 = getNext(r2)))
 {
     ... oops ...
 }
 
 You need one temporary for each static occurrence of getNext.
To my shame, I missed it! Anyways, I like getNext(). LockingTextReader definitely needs it. Shin
Jul 13 2010
prev sibling next sibling parent reply Michel Fortin <michel.fortin michelf.com> writes:
On 2010-07-13 00:38:55 -0400, Andrei Alexandrescu 
<SeeWebsiteForEmail erdani.org> said:

 Yah, truth be told getNext won't win a prize for brevity. You need to 
 define both a variable and a pointer to use it:
 
 T meh;
 T * neh;
 while ((neh = getNext(r, meh))) {
     ... process *neh ...
 }
At this point what you want is to use a foreach loop. In fact, if foreach could be made to work with getNext (and it should), you'd rarely need to write that boilerplate code yourself. -- Michel Fortin michel.fortin michelf.com http://michelf.com/
Jul 13 2010
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 13 Jul 2010 08:54:07 -0400, Michel Fortin  
<michel.fortin michelf.com> wrote:

 On 2010-07-13 00:38:55 -0400, Andrei Alexandrescu  
 <SeeWebsiteForEmail erdani.org> said:

 Yah, truth be told getNext won't win a prize for brevity. You need to  
 define both a variable and a pointer to use it:
  T meh;
 T * neh;
 while ((neh = getNext(r, meh))) {
     ... process *neh ...
 }
At this point what you want is to use a foreach loop. In fact, if foreach could be made to work with getNext (and it should), you'd rarely need to write that boilerplate code yourself.
struct InputForeach(R) if(isInputRangeThatUsesGetNext!R) { private R* range; this(ref R range) {this.range = &range;} int opApply(scope int delegate(ref ElementType!R) dg) { ElementType!R buf; ElementType!R *e; int result = 0; while(!result && (e = getNext(*range, buf))) { result = dg(*e); } return result; } } InputForeach!R inputForeach(R)(ref R r) if(isInputRangeThatUsesGetNext!R) { return InputForeach!R(r); } foreach(e; inputForeach(r)) { ... } opApply to the rescue :) Of course, native support would be better, unless the compiler decides to start inlining opApply. -Steve
Jul 13 2010
prev sibling next sibling parent FeepingCreature <default_357-line yahoo.de> writes:
On 13.07.2010 06:38, Andrei Alexandrescu wrote:
 Yah, truth be told getNext won't win a prize for brevity. You need to
 define both a variable and a pointer to use it:
 
 T meh;
 T * neh;
 while ((neh = getNext(r, meh))) {
    ... process *neh ...
 }
 
One way to make this slightly easier would be allowing auto in loop bodies, same as it works in if - ie. T meh; while (auto neh = getNext(r, meh)) { }
Jul 13 2010
prev sibling next sibling parent =?iso-8859-2?B?VG9tZWsgU293afFza2k=?= <just ask.me> writes:
Dnia 13-07-2010 o 06:38:55 Andrei Alexandrescu  =

<SeeWebsiteForEmail erdani.org> napisa=B3(a):

 I've just had an idea that is so dark and devious, I was almost afraid=
=
 to try it. But it works like a charm. Consider:
  T * getNext(R, E)(ref R range,
                   ref E store =3D *(cast(E*) alloca(E.sizeof))
 {
     ...
 }
  With this, allocating a dummy buffer on caller's stack is automated, =
so =
 client code can just write:
  for (T * p; (p =3D getNext(r)); )  {
    ... process *p ...
 }
  I feel dirty.
What's wrong with foreach(e; r) that you want to play dirty? The compile= r = is here to help. Tomek
Jul 23 2010
prev sibling parent reply "David Piepgrass" <qwertie256 gmail.com> writes:
 I've just had an idea that is so dark and devious, I was almost 
 afraid to try it. But it works like a charm. Consider:

 T * getNext(R, E)(ref R range,
                   ref E store = *(cast(E*) alloca(E.sizeof))
 {
     ...
 }
I don't know if this proposal went anywhere since 2010, but it occurs to me that there is a hidden danger here. alloca will allocate a sequence of separate temporaries. If the collection is large, the stack will overflow, and the client might not have a clue what happened.
Jul 09 2012
parent "David Piepgrass" <qwertie256 gmail.com> writes:
On Monday, 9 July 2012 at 07:53:41 UTC, David Piepgrass wrote:
 I don't know if this proposal went anywhere since 2010, but it 
 occurs to me that there is a hidden danger here. alloca will 
 allocate a sequence of separate temporaries. If the collection 
 is large, the stack will overflow, and the client might not 
 have a clue what happened.
Amazing. My post unleashed four pages of comments and not one of them responded to my post :O I think Mehrdad is right that an in/out range should have its own name to distinguish it from an input range, but that doesn't necessarily mean that the same interface can't be used for both. I imagine a couple of advantages of:
     T tmp;
     for(T* front = r.getNext(ref tmp))
         // do something with front
instead of:
     for(; !r.empty; r.popFront())
         // do something with r.front
- If the range uses late-binding, getNext() is faster because you're only calling one function instead of 3. When I program in interface calls to get each item. Late binding, of course, is necessary across DLL boundaries and can help avoid code bloat. - If an input-only range has to unpack its elements (e.g. bit array => bool, or anything compressed), the range doesn't need to unpack repeatedly every time 'front' is accessed, nor does it need to reserve memory inside itself for a scratch area (you don't want scratch areas in every range if your app keeps track of thousands of ranges; plus, ranges tend to get passed by value, right?). That said, it may be unreasonable for the compiler to support the necessary escape analysis (impossible in case you're importing .di files)... and maybe the existing empty/popFront/front is too well established to reconsider? (I am not familiar with the status quo).
Jul 09 2012
prev sibling next sibling parent Jesse Phillips <jessekphillips+D gmail.com> writes:
Andrei Alexandrescu Wrote:

 getNext is easy to define for e.g. arrays and files. How does it sound? 
 Does it bring significant simplification?
 
 
 Andrei
I'm with Jonathan on this. I don't really see much of a benefit. popFront, empty, front are very easy to define and simple to use. Java uses getNext for its iterators. Though it calls it 'next' and throws an exception when trying to call without any elements. This leads it to also provide a hasNext function.
Jul 12 2010
prev sibling next sibling parent Ellery Newcomer <ellery-newcomer utulsa.edu> writes:
On 07/12/2010 10:48 PM, Andrei Alexandrescu wrote:
 I think I figured out a comfortable and all-encompassing means to define
 a simplified interface for an input range.

 Currently input ranges need to define empty, front, and popFront. That
 works but it's a bit heavy for simple input ranges. We've been
 discussing simplified interfaces in this group but couldn't find one
 that satisfied all use cases.

 Consider this:

 T* getNext(R, T)(ref R range, ref T item);

 Semantics: if the range wants to expose addresses of its elements, it
 returns a pointer to the current element and also advances to the next
 element. Otherwise (i.e. the range does not have or does not want to
 expose addresses of its elements), the range fills "item" with the
 current value, again moves on to the next value, and returns &item.

 In all cases, when there are no more elements in the range, getNext
 returns null.
let's see here Range AhmAStream; ubyte[] buf = new ubyte[4]; ubyte[]* ruf = AhmAStream.getNext(buf); assert(ruf && ruf.length == 4); int i = * (cast(int*) ruf.ptr) buf = new ubyte[i]; ruf = AhmAStream.getNext(buf); assert(ruf && ruf.length == i); Something like this could work within this interface, couldn't it? Think you might be on to something
 getNext is easy to define for e.g. arrays and files. How does it sound?
 Does it bring significant simplification?


 Andrei
Jul 12 2010
prev sibling next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Mon, 12 Jul 2010 23:48:05 -0400, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:

 I think I figured out a comfortable and all-encompassing means to define  
 a simplified interface for an input range.

 Currently input ranges need to define empty, front, and popFront. That  
 works but it's a bit heavy for simple input ranges. We've been  
 discussing simplified interfaces in this group but couldn't find one  
 that satisfied all use cases.

 Consider this:

 T* getNext(R, T)(ref R range, ref T item);

 Semantics: if the range wants to expose addresses of its elements, it  
 returns a pointer to the current element and also advances to the next  
 element. Otherwise (i.e. the range does not have or does not want to  
 expose addresses of its elements), the range fills "item" with the  
 current value, again moves on to the next value, and returns &item.

 In all cases, when there are no more elements in the range, getNext  
 returns null.

 getNext is easy to define for e.g. arrays and files. How does it sound?  
 Does it bring significant simplification?
Yes, yes, yes! A question though -- whenever a pointer occurs, we always cringe, especially in safeD. will getNext be unsafe? BTW, I like the alloca thingy, that's really cool. One thing I just thought of, getNext should be split into two functions, the one you have, and: ElementType!R *getNext(R)(ref R range) To avoid having to supply the item or use alloca when the range is going to give you back a pointer to its internals anyways (tested with a template constraint). -Steve
Jul 13 2010
next sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 07/13/2010 07:39 AM, Steven Schveighoffer wrote:
 On Mon, 12 Jul 2010 23:48:05 -0400, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> wrote:

 I think I figured out a comfortable and all-encompassing means to
 define a simplified interface for an input range.

 Currently input ranges need to define empty, front, and popFront. That
 works but it's a bit heavy for simple input ranges. We've been
 discussing simplified interfaces in this group but couldn't find one
 that satisfied all use cases.

 Consider this:

 T* getNext(R, T)(ref R range, ref T item);

 Semantics: if the range wants to expose addresses of its elements, it
 returns a pointer to the current element and also advances to the next
 element. Otherwise (i.e. the range does not have or does not want to
 expose addresses of its elements), the range fills "item" with the
 current value, again moves on to the next value, and returns &item.

 In all cases, when there are no more elements in the range, getNext
 returns null.

 getNext is easy to define for e.g. arrays and files. How does it
 sound? Does it bring significant simplification?
Yes, yes, yes! A question though -- whenever a pointer occurs, we always cringe, especially in safeD. will getNext be unsafe?
I need to discuss this with Walter, he mentioned that it wouldn't be difficult to allow certain uses of pointers in SafeD.
 BTW, I like the alloca thingy, that's really cool.

 One thing I just thought of, getNext should be split into two functions,
 the one you have, and:

 ElementType!R *getNext(R)(ref R range)

 To avoid having to supply the item or use alloca when the range is going
 to give you back a pointer to its internals anyways (tested with a
 template constraint).
Yup, good point. Andrei
Jul 13 2010
prev sibling parent reply "Mehrdad" <wfunction hotmail.com> writes:
On Tuesday, 13 July 2010 at 12:39:19 UTC, Steven Schveighoffer 
wrote:
 On Mon, 12 Jul 2010 23:48:05 -0400, Andrei Alexandrescu 
 <SeeWebsiteForEmail erdani.org> wrote:
 We've been discussing simplified interfaces in this group but 
 couldn't find one that satisfied all use cases.

 Consider this:

 T* getNext(R, T)(ref R range, ref T item);

 Semantics: <snip>
Yes, yes, yes! -Steve
Oh dear. I can see this is getting ugly, pretty darn fast... If you need to /pass/ TWO parameters by _reference_ AND return a pointer to a /templated/ method _just_ to get 1 element out of a range/array, the design is horribly wrong. Imagine all the sorts of problems newcomers would have with learning this. Not to mention the troubles /everyone/ would have with having this templated method a class. (How do you override it?) Really, doing something simple should be simple. I don't have any brilliant ideas, but one guess would be nullable types... if you can return a nullable value, your interface for an interface could just be: T? next(); and that's it... it would return null if nothing is left. (The compiler can just set the null flag and ignore initializing the rest of the struct if the result is null.) Anyway, whatever we do... _please_ don't make it something like that suggestion above >_< thanks
Jul 09 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 7/9/12 5:14 AM, Mehrdad wrote:
 Really, doing something simple should be simple.

 I don't have any brilliant ideas, but one guess would be nullable
 types... if you can return a nullable value, your interface for an
 interface could just be:

 T? next();

 and that's it... it would return null if nothing is left.
What if you want to return a reference so the user can change it? Andrei
Jul 09 2012
parent reply "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 9 July 2012 at 14:03:17 UTC, Andrei Alexandrescu wrote:
 On 7/9/12 5:14 AM, Mehrdad wrote:
 Really, doing something simple should be simple.

 I don't have any brilliant ideas, but one guess would be 
 nullable
 types... if you can return a nullable value, your interface 
 for an
 interface could just be:

 T? next();

 and that's it... it would return null if nothing is left.
What if you want to return a reference so the user can change it? Andrei
Huh? You don't... it's an input iterator, not an output iterator! What would you be changing, exactly? D's return-by-reference doesn't work the way you expect anyway, so it's kinda pointless worrying about it.... property ref int foo() { } property void foo(int v) { } foo = 2; // huh?
Jul 09 2012
next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 9 July 2012 at 14:07:26 UTC, Mehrdad wrote:
 Huh? You don't... it's an input iterator, not an output 
 iterator!
er... s/iterator/range/g
Jul 09 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 9 July 2012 at 14:07:26 UTC, Mehrdad wrote:
 Huh? You don't... it's an input iterator, not an output 
 iterator!
 What would you be changing, exactly?
Not to mention, what's the use case for it?
Jul 09 2012
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 7/9/12 10:07 AM, Mehrdad wrote:
 On Monday, 9 July 2012 at 14:03:17 UTC, Andrei Alexandrescu wrote:
 On 7/9/12 5:14 AM, Mehrdad wrote:
 Really, doing something simple should be simple.

 I don't have any brilliant ideas, but one guess would be nullable
 types... if you can return a nullable value, your interface for an
 interface could just be:

 T? next();

 and that's it... it would return null if nothing is left.
What if you want to return a reference so the user can change it? Andrei
Huh? You don't... it's an input iterator, not an output iterator!
The idea of an input range is that it works seamlessly with the more capable ranges. Andrei
Jul 09 2012
parent reply "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 9 July 2012 at 14:21:33 UTC, Andrei Alexandrescu wrote:
 Huh? You don't... it's an input iterator, not an output 
 iterator!
The idea of an input range is that it works seamlessly with the more capable ranges. Andrei
You mean 'foreach'? That should be using opApply if it wants to take outputs by ref, not input range capabilities. Or is there some other constructor you're referring to? Because I don't see any overlap in the constructs between input and output ranges.
Jul 09 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 7/9/12 10:31 AM, Mehrdad wrote:
 On Monday, 9 July 2012 at 14:21:33 UTC, Andrei Alexandrescu wrote:
 Huh? You don't... it's an input iterator, not an output iterator!
The idea of an input range is that it works seamlessly with the more capable ranges. Andrei
You mean 'foreach'? That should be using opApply if it wants to take outputs by ref, not input range capabilities. Or is there some other constructor you're referring to? Because I don't see any overlap in the constructs between input and output ranges.
I think it's about the notion of "input range" that is confusing, a better name would be "single-pass range". One should be perfectly capable of assigning to elements of an input range. A built-in slice is an input range. Andrei
Jul 09 2012
parent reply "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 9 July 2012 at 14:48:05 UTC, Andrei Alexandrescu wrote:
 I think it's about the notion of "input range" that is 
 confusing, a better name would be "single-pass range". One 
 should be perfectly capable of assigning to elements of an 
 input range. A built-in slice is an input range.

 Andrei
Aren't output ranges single-pass too?
Jul 09 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 7/9/12 10:52 AM, Mehrdad wrote:
 On Monday, 9 July 2012 at 14:48:05 UTC, Andrei Alexandrescu wrote:
 I think it's about the notion of "input range" that is confusing, a
 better name would be "single-pass range". One should be perfectly
 capable of assigning to elements of an input range. A built-in slice
 is an input range.

 Andrei
Aren't output ranges single-pass too?
They don't have much of a notion of "pass" because the only primitive of output ranges is "put". Andrei
Jul 09 2012
parent reply "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 9 July 2012 at 15:16:52 UTC, Andrei Alexandrescu wrote:
 On 7/9/12 10:52 AM, Mehrdad wrote:
 On Monday, 9 July 2012 at 14:48:05 UTC, Andrei Alexandrescu 
 wrote:
 I think it's about the notion of "input range" that is 
 confusing, a
 better name would be "single-pass range". One should be 
 perfectly
 capable of assigning to elements of an input range. A 
 built-in slice
 is an input range.

 Andrei
Aren't output ranges single-pass too?
They don't have much of a notion of "pass" because the only primitive of output ranges is "put". Andrei
Sorry? I don't know what you mean, but "single-pass" makes perfect sense to me for output ranges. Think: CD burning. Anyway, what I'm saying is that if your code _only_ depends on the _input_ capabilities of a range, then it /cannot/ and _does not_ need the 'ref' capability. You only need 'ref' for two reasons, so far as I can see: - Writing - Optimization The latter point is moot here, and the former is _clearly_ not something an "input range" should have, because it's a notion of output...
Jul 09 2012
next sibling parent reply "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 9 July 2012 at 15:34:35 UTC, Mehrdad wrote:
 The latter point is moot here, and the former is _clearly_ not 
 something an "input range" should have, because it's a notion 
 of output...
Er, let me rephrase that: The former is _clearly_ not a property of "input ranges". If something is both an input range and an output range, then sure, it can have that capability. But being able to write to something is _orthogonal_ to whether you can read from it.
Jul 09 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 7/9/12 11:35 AM, Mehrdad wrote:
 If something is both an input range and an output range, then sure, it
 can have that capability. But being able to write to something is
 _orthogonal_ to whether you can read from it.
That is the case right now. The point is, with your design you need to add something extra to allow writing to elements of a single-pass range. So your design does not simplify things as much as it might seem. Andrei
Jul 09 2012
next sibling parent reply "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 9 July 2012 at 15:48:35 UTC, Andrei Alexandrescu wrote:
 On 7/9/12 11:35 AM, Mehrdad wrote:
 If something is both an input range and an output range, then 
 sure, it
 can have that capability. But being able to write to something 
 is
 _orthogonal_ to whether you can read from it.
That is the case right now. The point is, with your design you need to add something extra to allow writing to elements of a single-pass range. So your design does not simplify things as much as it might seem. Andrei
 With your design you need to add something extra to allow 
 writing to elements of a single-pass range.
If that's the case, I'd hate to tell you this, but _unless_ you're planning on removing the notion of input/output ranges (and perhaps adding single-pass/multi-pass), you're doing it wrong. :-) That capability is simply /not needed/ when your /only/ contract is that something is an input range. Asking an input range, "hey, can I write to you?" is just... insulting! That's like asking an electrical engineer if he can plumb. Sure, it might come in handy if he can, but it's just plain silly to ask himi that. If you need someone who can do both, then ask for someone who's an EE _and_ a plumber. On the other hand, the concepts of single-pass and multi-pass are most certainly NOT orthogonal, for obvious reasons. So it DOES make sense to ask an output range, "hey, can I write to you multiple times?" So if you're expecting to be able to say void foo(R)(ref R range) if (isInputRange!R) { static if (hasLValueElements!R) // or whatever it was range.front = (ElementType!R).init; } then you're mixing up two completely unrelated/orthogonal concepts, which is silly. What you SHOULD be saying instead is: void foo(R)(ref R range) if (isInputRange!R) { static if (isOutput!R) // or whatever it was range.put((ElementType!R).init); }
Jul 09 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 7/9/12 12:04 PM, Mehrdad wrote:
 On Monday, 9 July 2012 at 15:48:35 UTC, Andrei Alexandrescu wrote:
 On 7/9/12 11:35 AM, Mehrdad wrote:
 If something is both an input range and an output range, then sure, it
 can have that capability. But being able to write to something is
 _orthogonal_ to whether you can read from it.
That is the case right now. The point is, with your design you need to add something extra to allow writing to elements of a single-pass range. So your design does not simplify things as much as it might seem. Andrei
 With your design you need to add something extra to allow writing to
 elements of a single-pass range.
If that's the case, I'd hate to tell you this, but _unless_ you're planning on removing the notion of input/output ranges (and perhaps adding single-pass/multi-pass), you're doing it wrong. :-)
Given the smart-aleck nature of the comment I'd say s/hate/love/.
 That capability is simply /not needed/ when your /only/ contract is that
 something is an input range.
As I mentioned, "input range" is a misnomer. Think "one-pass range". The range can be written or not, and a multi-pass range (including random-access range) is also a one-pass range. Andrei
Jul 09 2012
parent reply "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 9 July 2012 at 18:51:40 UTC, Andrei Alexandrescu wrote:
 If that's the case, I'd hate to tell you this, but _unless_ 
 you're planning on removing the notion of input/output ranges 
 (and perhaps adding single-pass/multi-pass), you're doing it 
 wrong. :-)
Given the smart-aleck nature of the comment I'd say s/hate/love/.
lol, nice comment. +1
 That capability is simply /not needed/ when your /only/ 
 contract is that something is an input range.
As I mentioned, "input range" is a misnomer. Think "one-pass range". The range can be written or not, and a multi-pass range (including random-access range) is also a one-pass range.
Sure, we all agree that a multi-pass range is also a one-pass range. I don't have a problem with that. But if you only use the one-pass range aspect, that doesn't mean it has to use the same syntax as the multi-pass range aspect. The issue is, while you are /telling/ me that "input range" is a misnomer, that doesn't match what Phobos's is telling me. In Phobos, you've placed a very clear difference between "input ranges" and "output ranges", and /both/ of them are single-pass. It looks like it was deliberately designed that way... I don't think you made a mistake when separating input and output ranges like that (unless you do?). So unless you're planning on trashing all that and redesigning the entire thing from scratch (are you?) I don't understand how you can have two orthogonal bases for working with ranges like this. (Pardon the math lingo, but that's really what they are -- input/output vs random-access/multi-pass-sequential-access/single-pass-sequential-access are two different bases for looking at the issue. You can't really mix them up in the same module...)
Jul 09 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 7/9/12 3:03 PM, Mehrdad wrote:
 Sure, we all agree that a multi-pass range is also a one-pass range. I
 don't have a problem with that. But if you only use the one-pass range
 aspect, that doesn't mean it has to use the same syntax as the
 multi-pass range aspect.
But that works against your goal of simplifying things.
 The issue is, while you are /telling/ me that "input range" is a
 misnomer, that doesn't match what Phobos's is telling me. In Phobos,
 you've placed a very clear difference between "input ranges" and "output
 ranges", and /both/ of them are single-pass.
An output range only needs put(). In that sense it's an endless bag in which you get to put stuff without any control over e.g. navigation. A one-pass range may or may not accept assignment to .front. In particular, put() is written to detect and use that. And that's about it.
 It looks like it was deliberately designed that way... I don't think you
 made a mistake when separating input and output ranges like that (unless
 you do?).
I sense a Jedi trick is tried unto me.
 So unless you're planning on trashing all that and redesigning the
 entire thing from scratch (are you?) I don't understand how you can have
 two orthogonal bases for working with ranges like this.
I agree with your assessment that you don't have a good understanding of how D ranges work. Nevertheless, proposing an alternative design would be a great basis for discussion; isolated fragments of designs look attractive in isolation but often fail to offer the whole picture. Andrei
Jul 09 2012
parent reply "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 9 July 2012 at 19:22:39 UTC, Andrei Alexandrescu wrote:
 But that works against your goal of simplifying things.
If by "simplification" you mean "fewer methods to call", then yes. If by "simplification" you mean "easier to understand", then no -- not here. Having a different (but still simple) syntax for a more capable range is far better than the getNext() proposal here.
 I sense a Jedi trick is tried unto me.
lol wasn't intending to, but whatever.
 An output range only needs put(). In that sense it's an endless 
 bag in which you get to put stuff without any control over e.g. 
 navigation. A one-pass range may or may not accept assignment 
 to .front. In particular, put() is written to detect and use 
 that. And that's about it.
 I agree with your assessment that you don't have a good 
 understanding of how D ranges work.
I certainly don't have a good understanding of how they were _intended_ to work, is what I found out in this thread. The docs (or even the names, as you mentioned yourself) don't at all convey what you guys explained here, so if they were designed one way but intended to work another way, then /of course/ I don't have a good understanding of how they were intended to work!
 Nevertheless, proposing an alternative design would be a great 
 basis for discussion; isolated fragments of designs look 
 attractive in isolation but often fail to offer the whole 
 picture.
I agree. My alternative would be to abandon similar 'hasXYZ' stuff (which doesn't convey the picture and looks hacky), and instead formally define what those are, like I/O range. Sounds good/bad?
Jul 09 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 7/9/12 3:30 PM, Mehrdad wrote:
 I agree. My alternative would be to abandon similar 'hasXYZ' stuff
 (which doesn't convey the picture and looks hacky), and instead formally
 define what those are, like I/O range. Sounds good/bad?
You may want to just spell it clearly. So right now we have the notions: input range (well one-pass range) forward range bidirectional range random-access range output range coupled with the capability queries isInfinite hasAssignableElements hasLength ... The small set of ranges coupled with the capability queries reflect the orthogonal or near-orthogonal nature of such. As far as I understand the above, you propose the notions: input range input range with assignable elements infinite input range infinite input range with assignable elements forward range forward range with assignable elements ... That seems pretty onerous, but then I can't derive other meaning from your post. Andrei
Jul 09 2012
parent reply "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 9 July 2012 at 19:52:04 UTC, Andrei Alexandrescu wrote:
 On 7/9/12 3:30 PM, Mehrdad wrote:
 I agree. My alternative would be to abandon similar 'hasXYZ' 
 stuff
 (which doesn't convey the picture and looks hacky), and 
 instead formally
 define what those are, like I/O range. Sounds good/bad?
You may want to just spell it clearly. So right now we have the notions: input range (well one-pass range) forward range bidirectional range random-access range output range coupled with the capability queries isInfinite hasAssignableElements hasLength ... The small set of ranges coupled with the capability queries reflect the orthogonal or near-orthogonal nature of such. As far as I understand the above, you propose the notions: input range input range with assignable elements infinite input range infinite input range with assignable elements forward range forward range with assignable elements ... That seems pretty onerous, but then I can't derive other meaning from your post. Andrei
that's not what I meant, but I think another solution is better anyway: Why isn't transform taking in both an input and an output range in the first place? Of course they might be the same, but they don't have to be.
Jul 09 2012
parent reply "Roman D. Boiko" <rb d-coding.com> writes:
On Monday, 9 July 2012 at 20:21:18 UTC, Mehrdad wrote:
 that's not what I meant, but I think another solution is better 
 anyway:

 Why isn't transform taking in both an input and an output range 
 in the first place? Of course they might be the same, but they 
 don't have to be.
Could you make a detailed proposal? I still completely don't understand neither the problem you're trying to solve, nor the solution you have in mind. I was reading all your posts in this thread very carefully.
Jul 09 2012
next sibling parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Monday, July 09, 2012 22:25:57 Roman D. Boiko wrote:
 On Monday, 9 July 2012 at 20:21:18 UTC, Mehrdad wrote:
 that's not what I meant, but I think another solution is better
 anyway:
 
 Why isn't transform taking in both an input and an output range
 in the first place? Of course they might be the same, but they
 don't have to be.
Could you make a detailed proposal? I still completely don't understand neither the problem you're trying to solve, nor the solution you have in mind. I was reading all your posts in this thread very carefully.
What we really need is a solid article on ranges on the site (maybe even several, with each one going into more detail on specific stuff - e.g. issues with strings being ranges of dchar). Then it will be much easier for programmers to gain a solid understanding of ranges. The API documentation will never be good enough for that, because it's not the right place to go into that level of detail. I keep meaning to write up such an article (and even started one a while back), but I never get around to getting it done... In the meantime, there's alway's Ali's book: http://ddili.org/ders/d.en/ranges.html It may be that there are things that can and should be improved about how ranges currently work, but I think that it's clear that any proposal made to improve them needs to be made with a solid understanding of how they currently work. - Jonathan M Davis
Jul 09 2012
parent "Roman D. Boiko" <rb d-coding.com> writes:
By the way, this thread is quite old.

getNext looks similar to what I wanted when first dealt with 
ranges, but now it looks too heavyweight.

What happened to this proposal anyway? Was it deferred, 
discarded, or what?
Jul 09 2012
prev sibling parent reply "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 9 July 2012 at 20:25:58 UTC, Roman D. Boiko wrote:
 On Monday, 9 July 2012 at 20:21:18 UTC, Mehrdad wrote:
 Why isn't transform taking in both an input and an output 
 range in the first place? Of course they might be the same, 
 but they don't have to be.
Could you make a detailed proposal? I still completely don't understand neither the problem you're trying to solve, nor the solution you have in mind.
Well, I'm not even sure if the proposal is necessary yet, since it would be solved another way -- I'm not sure if transform should be taking 1 range as input or 2. I mean like, why isn't it defined this way instead? void transform(alias f, RI, RO)(RI r, RO output) if(isInputRange!RI && isOutputRange!RO) { for(; !r.empty; r.popFront()) output.put(unaryFun!f(r.front)); } If that works, then I still think we don't need assignable front()s after all.
Jul 09 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 7/9/12 4:47 PM, Mehrdad wrote:
 I mean like, why isn't it defined this way instead?

 void transform(alias f, RI, RO)(RI r, RO output)
 if(isInputRange!RI && isOutputRange!RO)
 {
 for(; !r.empty; r.popFront())
 output.put(unaryFun!f(r.front));
 }
Instead of what? There is not transform() function in std.algorithm. Andrei
Jul 09 2012
next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 07/09/2012 11:04 PM, Andrei Alexandrescu wrote:
 On 7/9/12 4:47 PM, Mehrdad wrote:
 I mean like, why isn't it defined this way instead?

 void transform(alias f, RI, RO)(RI r, RO output)
     if(isInputRange!RI && isOutputRange!RO)
 {
     for(; !r.empty; r.popFront())
     output.put(unaryFun!f(r.front));
 }
Instead of what? There is not transform() function in std.algorithm. Andrei
His post was related to this one, burried in another branch of the thread: On 07/09/2012 08:33 PM, jerro wrote:
 It is useful to be able to write an algorithm that both reads
 and writes range elements. There are plenty of use cases for
 that, but if you really need an example, here's a simple one:

 void transform(alias f, R)(R r)
      if(isInputRange!R && hasAssignableElements!R)
 {
      for(; !r.empty; r.popFront())
          r.front = unaryFun!f(r.front);
 }
Jul 09 2012
prev sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 9 July 2012 at 21:04:31 UTC, Andrei Alexandrescu wrote:
 Instead of what? There is not transform() function in 
 std.algorithm.

 Andrei
I was talking about this: http://forum.dlang.org/thread/i1gnlo$18g0$1 digitalmars.com?page=5#post-tdgxpwegpwaczardhvpx:40forum.dlang.org If you had a different use-case in mind for 'ref' front()'s, would you mind posting it?
Jul 09 2012
prev sibling next sibling parent reply "Mehrdad" <wfunction hotmail.com> writes:
 With your design you need to add something extra to allow 
 writing to elements of a single-pass range.
If that's the case, I'd hate to tell you this, but _unless_ you're planning on removing the notion of input/output ranges (and perhaps adding single-pass/multi-pass), you're doing it wrong. :-) That capability is simply /not needed/ when your /only/ contract is that something is an input range. Asking an input range, "hey, can I write to you?" is just... insulting! That's like asking an electrical engineer if he can plumb. Sure, it might come in handy if he can, but it's just plain silly to ask himi that. If you need someone who can do both, then ask for someone who's an EE _and_ a plumber. On the other hand, the concepts of single-pass and multi-pass are most certainly NOT orthogonal, for obvious reasons. So it DOES make sense to ask an output range, "hey, can I write to you multiple times?" So if you're expecting to be able to say void foo(R)(ref R range) if (isInputRange!R) { static if (hasLValueElements!R) // or whatever it was range.front = (ElementType!R).init; } then you're mixing up two completely unrelated/orthogonal concepts, which is silly. What you SHOULD be saying instead is: void foo(R)(ref R range) if (isInputRange!R) { static if (isOutputRange!R) // or whatever it was range.put((ElementType!R).init); }
Jul 09 2012
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 07/09/2012 06:05 PM, Mehrdad wrote:
 With your design you need to add something extra to allow writing to
 elements of a single-pass range.
If that's the case, I'd hate to tell you this, but _unless_ you're planning on removing the notion of input/output ranges (and perhaps adding single-pass/multi-pass), you're doing it wrong. :-) That capability is simply /not needed/ when your /only/ contract is that something is an input range. Asking an input range, "hey, can I write to you?" is just... insulting! That's like asking an electrical engineer if he can plumb.
Consider the possibility that this electrical engineer might be a plumber as well.
Jul 09 2012
parent reply "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 9 July 2012 at 16:06:57 UTC, Timon Gehr wrote:
 Consider the possibility that this electrical engineer might be 
 a plumber as well.
Yes, so if you expect him to be, then you should ask him "are you a plumber?" (i.e. output range?) But what you're asking him right now is, "hey, can you do clear this pipe for me with copper wires??" which is downright silly. Being able to write to an output range does NOT require you to have 'ref' members. The only thing 'ref' ever buys you is performance, not any new capabilities. Depending on it doesn't make sense.
Jul 09 2012
next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 07/09/2012 06:17 PM, Mehrdad wrote:
 On Monday, 9 July 2012 at 16:06:57 UTC, Timon Gehr wrote:
 Consider the possibility that this electrical engineer might be a
 plumber as well.
Yes, so if you expect him to be, then you should ask him "are you a plumber?" (i.e. output range?) But what you're asking him right now is, "hey, can you do clear this pipe for me with copper wires??" which is downright silly.
The range is an input range of references. It could just as well be an input range of setters. I do not see why the notion of 'input range' should restrict the kinds of elements that are accessed by iterating the range. It would be downright silly.
 Being able to
 write to an output range does NOT require you to have 'ref' members. The
 only thing 'ref' ever buys you is performance, not any new capabilities.
 Depending on it doesn't make sense.
sort!"a[0]<b[0]"(zip(ra1,ra2));
Jul 09 2012
parent reply "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 9 July 2012 at 16:25:06 UTC, Timon Gehr wrote:
 I do not see why the notion of 'input range' should restrict 
 the kinds of elements that are accessed by iterating the range. 
 It would be downright silly.
Yes... but when did I ever impose anything on the /elements/? I never even used that word. I imposed constraints on the range, and the range only...
 sort!"a[0]<b[0]"(zip(ra1,ra2));
sort(): "Sorts a random-access range according to predicate less." What does that have to do with input ranges?
Jul 09 2012
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 07/09/2012 06:27 PM, Mehrdad wrote:
 On Monday, 9 July 2012 at 16:25:06 UTC, Timon Gehr wrote:
 I do not see why the notion of 'input range' should restrict the kinds
 of elements that are accessed by iterating the range. It would be
 downright silly.
Yes... but when did I ever impose anything on the /elements/?
On 07/09/2012 06:04 PM, Mehrdad wrote:
 With your design you need to add something extra to allow writing to elements
of a single-pass range.
... That capability is simply /not needed/ when your /only/ contract is that something is an input range.
 I never even used that word.
Unlikely.
Jul 09 2012
parent reply "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 9 July 2012 at 16:42:52 UTC, Timon Gehr wrote:
 On 07/09/2012 06:04 PM, Mehrdad wrote:
 With your design you need to add something extra to allow 
 writing to elements of a single-pass range.
... That capability is simply /not needed/ when your /only/ contract is that something is an input range.
 I never even used that word.
Unlikely.
Huh? Isn't that Andrei's post?
Jul 09 2012
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 07/09/2012 06:47 PM, Mehrdad wrote:
 On Monday, 9 July 2012 at 16:42:52 UTC, Timon Gehr wrote:
 On 07/09/2012 06:04 PM, Mehrdad wrote:
 With your design you need to add something extra to allow writing to
 elements of a single-pass range.
... That capability is simply /not needed/ when your /only/ contract is that something is an input range.
 I never even used that word.
Unlikely.
Huh? Isn't that Andrei's post?
On 07/09/2012 11:14 AM, Mehrdad wrote:
 element
Jul 09 2012
next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 9 July 2012 at 16:50:44 UTC, Timon Gehr wrote:
 On 07/09/2012 11:14 AM, Mehrdad wrote:
 element
Link to exact post please?
Jul 09 2012
prev sibling parent "Mehrdad" <wfunction hotmail.com> writes:
(or it didn't happen :P)
Jul 09 2012
prev sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Mon, 09 Jul 2012 12:17:49 -0400, Mehrdad <wfunction hotmail.com> wrote:

 On Monday, 9 July 2012 at 16:06:57 UTC, Timon Gehr wrote:
 Consider the possibility that this electrical engineer might be a  
 plumber as well.
Yes, so if you expect him to be, then you should ask him "are you a plumber?" (i.e. output range?) But what you're asking him right now is, "hey, can you do clear this pipe for me with copper wires??" which is downright silly. Being able to write to an output range does NOT require you to have 'ref' members. The only thing 'ref' ever buys you is performance, not any new capabilities. Depending on it doesn't make sense.
import std.range; struct inputrange { int x; property ref int front() { return x;} void popFront() {} enum empty = false; } static assert(isOutputRange!(inputrange, int)); -Steve
Jul 16 2012
prev sibling parent reply "Mehrdad" <wfunction hotmail.com> writes:
Perhaps if you could show an actual example of what you expect to 
be able to do, that would make things clearer?
Jul 09 2012
parent reply "jerro" <a a.com> writes:
On Monday, 9 July 2012 at 16:22:05 UTC, Mehrdad wrote:
 Perhaps if you could show an actual example of what you expect 
 to be able to do, that would make things clearer?
It is useful to be able to write an algorithm that both reads and writes range elements. There are plenty of use cases for that, but if you really need an example, here's a simple one: void transform(alias f, R)(R r) if(isInputRange!R && hasAssignableElements!R) { for(; !r.empty; r.popFront()) r.front = unaryFun!f(r.front); }
Jul 09 2012
parent reply "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 9 July 2012 at 18:33:26 UTC, jerro wrote:
 On Monday, 9 July 2012 at 16:22:05 UTC, Mehrdad wrote:
 Perhaps if you could show an actual example of what you expect 
 to be able to do, that would make things clearer?
It is useful to be able to write an algorithm that both reads and writes range elements. There are plenty of use cases for that, but if you really need an example, here's a simple one: void transform(alias f, R)(R r) if(isInputRange!R && hasAssignableElements!R) { for(; !r.empty; r.popFront()) r.front = unaryFun!f(r.front); }
Wow, thanks a bunch. That makes it a LOT more clearer than explanations. :) The trouble here is that the use case is valid, but the design of ranges doesn't really match it. Why? Because the way it currently stands, isInputRange && hasAssignableElements is basically isOutputRange (with also input capability), which doesn't make any sense. What we really need is to define a new kind of range -- perhaps an "input/output range", if I had to name it: it's forward-only, but it can be mutated and replaced. It'd be a superset of both input and output ranges, and also a subset of bidirectional ranges. But without formally defining a new kind of range, we're just saying "I want an input range that's also kinda-sorta an output range. Ooh, I also want to be able to mutate it! But I can't seek backwards, so it's not bidi. Hmm, not sure what to call it, so I'll just put constraints for everything I need and hope it makes sense." It (obviously) /works/, but it doesn't make sense semantically, the way it's defined right now.
Jul 09 2012
parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Monday, July 09, 2012 21:19:46 Mehrdad wrote:
 On Monday, 9 July 2012 at 18:33:26 UTC, jerro wrote:
 On Monday, 9 July 2012 at 16:22:05 UTC, Mehrdad wrote:
 Perhaps if you could show an actual example of what you expect
 to be able to do, that would make things clearer?
It is useful to be able to write an algorithm that both reads and writes range elements. There are plenty of use cases for that, but if you really need an example, here's a simple one: void transform(alias f, R)(R r) if(isInputRange!R && hasAssignableElements!R) { for(; !r.empty; r.popFront()) r.front = unaryFun!f(r.front); }
Wow, thanks a bunch. That makes it a LOT more clearer than explanations. :) The trouble here is that the use case is valid, but the design of ranges doesn't really match it. Why? Because the way it currently stands, isInputRange && hasAssignableElements is basically isOutputRange (with also input capability), which doesn't make any sense.
Except that how output ranges are written to and an input range with assignable elements are fundamentally different. Output ranges use put, which _might_ write to each individual element, or it may just append to the output range (it all depends on the implementation of put). You're essentially generating a range when you use an output range. But with an input range with assignable elements, you're specifically setting each element in an existing range rather than generating a new one. You can think of an output range like an output stream, whereas an input range with assignable elements is like an array where you're assigning values to each of its elements. - Jonathan M Davis
Jul 09 2012
parent reply "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 9 July 2012 at 19:30:25 UTC, Jonathan M Davis wrote:
 Except that how output ranges are written to and an input range 
 with assignable elements are fundamentally different. Output 
 ranges use put, which _might_ write to each individual element, 
 or it may just append to the output range (it all depends on 
 the implementation of put). You're essentially generating a 
 range when you use an output range. But with an  input range 
 with assignable elements, you're specifically setting each 
 element in an existing range rather than generating a new one. 
 You can think of an output range like an output stream, whereas 
 an input range with assignable elements is like an array where 
 you're assigning values to each of its elements.
Oh!! So they're _exactly_ emulating C++ here, with insert_iterators trying to mimic regular iterators and all. I _never_ got that impression from the docs. The impression the docs give is that the only time when you "add" instead of "overwrite" is when it either doesn't make sense to overwrite (e.g. into hash set, at the end of an array, etc.). They never implied that you you might also be inserting into somewhere where overwriting is possible, so clearly I misunderstood what was intended. Btw, I just realized: With that explanation, hasAssignableElements doesn't make sense for some things that it should make sense for. How do you "assign" to e.g. a range of a BST? It _certainly_ makes sense to do a transform() on it, but assigning to any particular element doesn't make sense. So I think that confirms that what we really want IS an I/O range -- a range that supports delete-modify-add, but not (necessarily) read-modify-write (which is what hasAssignableElements implies). Makes sense?
Jul 09 2012
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 07/09/2012 09:46 PM, Mehrdad wrote:
 On Monday, 9 July 2012 at 19:30:25 UTC, Jonathan M Davis wrote:
 Except that how output ranges are written to and an input range with
 assignable elements are fundamentally different. Output ranges use
 put, which _might_ write to each individual element, or it may just
 append to the output range (it all depends on the implementation of
 put). You're essentially generating a range when you use an output
 range. But with an input range with assignable elements, you're
 specifically setting each element in an existing range rather than
 generating a new one. You can think of an output range like an output
 stream, whereas an input range with assignable elements is like an
 array where you're assigning values to each of its elements.
Oh!! So they're _exactly_ emulating C++ here, with insert_iterators trying to mimic regular iterators and all. I _never_ got that impression from the docs. The impression the docs give is that the only time when you "add" instead of "overwrite" is when it either doesn't make sense to overwrite (e.g. into hash set, at the end of an array, etc.). They never implied that you you might also be inserting into somewhere where overwriting is possible, so clearly I misunderstood what was intended. Btw, I just realized: With that explanation, hasAssignableElements doesn't make sense for some things that it should make sense for. How do you "assign" to e.g. a range of a BST? It _certainly_ makes sense to do a transform() on it, but assigning to any particular element doesn't make sense. So I think that confirms that what we really want IS an I/O range -- a range that supports delete-modify-add, but not (necessarily) read-modify-write (which is what hasAssignableElements implies). Makes sense?
struct BST(K){ struct Node{ ... } Node root; auto opSlice(){ struct Range{ ... property front(){ return ...; } property front(K k){ deleteFront(); root.add(k); } } } }
Jul 09 2012
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 07/09/2012 09:52 PM, Timon Gehr wrote:
 On 07/09/2012 09:46 PM, Mehrdad wrote:
 On Monday, 9 July 2012 at 19:30:25 UTC, Jonathan M Davis wrote:
 Except that how output ranges are written to and an input range with
 assignable elements are fundamentally different. Output ranges use
 put, which _might_ write to each individual element, or it may just
 append to the output range (it all depends on the implementation of
 put). You're essentially generating a range when you use an output
 range. But with an input range with assignable elements, you're
 specifically setting each element in an existing range rather than
 generating a new one. You can think of an output range like an output
 stream, whereas an input range with assignable elements is like an
 array where you're assigning values to each of its elements.
Oh!! So they're _exactly_ emulating C++ here, with insert_iterators trying to mimic regular iterators and all. I _never_ got that impression from the docs. The impression the docs give is that the only time when you "add" instead of "overwrite" is when it either doesn't make sense to overwrite (e.g. into hash set, at the end of an array, etc.). They never implied that you you might also be inserting into somewhere where overwriting is possible, so clearly I misunderstood what was intended. Btw, I just realized: With that explanation, hasAssignableElements doesn't make sense for some things that it should make sense for. How do you "assign" to e.g. a range of a BST? It _certainly_ makes sense to do a transform() on it, but assigning to any particular element doesn't make sense. So I think that confirms that what we really want IS an I/O range -- a range that supports delete-modify-add, but not (necessarily) read-modify-write (which is what hasAssignableElements implies). Makes sense?
struct BST(K){ struct Node{ ... } Node root; auto opSlice(){ struct Range{ ... property front(){ return ...; } property front(K k){ deleteFront(); root.add(k); } } } }
(but BSTs do not actually support a range that implements transform. Some elements might get transformed twice.)
Jul 09 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 9 July 2012 at 15:34:35 UTC, Mehrdad wrote:
 Think: CD burning.
A good comparison: Single-pass output ranges: an output range that writes to a CD, DVD, etc. Multi-pass output ranges: an output range that writes to a tape, hard disk, etc.
Jul 09 2012
prev sibling parent "Roman D. Boiko" <rb d-coding.com> writes:
On Monday, 9 July 2012 at 15:34:35 UTC, Mehrdad wrote:
 On Monday, 9 July 2012 at 15:16:52 UTC, Andrei Alexandrescu 
 wrote:
 the only primitive of output ranges is "put".

 Andrei
Sorry? I don't know what you mean
template isOutputRange(R,E) Returns true if R is an output range for elements of type E. An output range is defined functionally as a range that supports the operation void put(R, E)(ref R r, E e);
Jul 09 2012
prev sibling next sibling parent Michel Fortin <michel.fortin michelf.com> writes:
On 2010-07-12 23:48:05 -0400, Andrei Alexandrescu 
<SeeWebsiteForEmail erdani.org> said:

 T* getNext(R, T)(ref R range, ref T item);
 
 Semantics: if the range wants to expose addresses of its elements, it 
 returns a pointer to the current element and also advances to the next 
 element. Otherwise (i.e. the range does not have or does not want to 
 expose addresses of its elements), the range fills "item" with the 
 current value, again moves on to the next value, and returns &item.
 
 In all cases, when there are no more elements in the range, getNext 
 returns null.
This can't be safe. getNext would need to take a pointer out of the item reference, which isn't allowed in SafeD. -- Michel Fortin michel.fortin michelf.com http://michelf.com/
Jul 13 2010
prev sibling next sibling parent Shin Fujishiro <rsinfu gmail.com> writes:
Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:
 I think I figured out a comfortable and all-encompassing means to define 
 a simplified interface for an input range.
 
 Currently input ranges need to define empty, front, and popFront. That 
 works but it's a bit heavy for simple input ranges. We've been 
 discussing simplified interfaces in this group but couldn't find one 
 that satisfied all use cases.
 
 Consider this:
 
 T* getNext(R, T)(ref R range, ref T item);
 
 Semantics: if the range wants to expose addresses of its elements, it 
 returns a pointer to the current element and also advances to the next 
 element. Otherwise (i.e. the range does not have or does not want to 
 expose addresses of its elements), the range fills "item" with the 
 current value, again moves on to the next value, and returns &item.
 
 In all cases, when there are no more elements in the range, getNext 
 returns null.
 
 getNext is easy to define for e.g. arrays and files. How does it sound? 
 Does it bring significant simplification?
I gave it a try, and it fairly simplified range implementation. http://gist.github.com/474562 I think it's good. (1) But how does it represent an infinite range? (2) Should user code use getNext for input ranges (like put for output ranges)? For example: void doSomething(R)(R input) { // read first character if any dchar* p = getNext!dchar(input); if (p == null) return; dchar a = *p; // read subsequent characters... dchar b = *enforce( getNext!dchar(input) ); ... } Shin
Jul 14 2010
prev sibling next sibling parent reply "monarch_dodra" <monarch_dodra gmail.com> writes:
On Tuesday, 13 July 2010 at 03:48:08 UTC, Andrei Alexandrescu 
wrote:
 I think I figured out a comfortable and all-encompassing means 
 to define a simplified interface for an input range.

 Currently input ranges need to define empty, front, and 
 popFront. That works but it's a bit heavy for simple input 
 ranges. We've been discussing simplified interfaces in this 
 group but couldn't find one that satisfied all use cases.
This feels quite similar to "consumeFront"? As a matter of fact, isn't it just exchanging: "Check Not Empty" then "consumeFront" for "getNext" then "checkNotNull"
Semantics: if the range wants to expose addresses of its 
elements, it returns a pointer to the current element and also 
advances to the next element. Otherwise (i.e. the range does not 
have or does not want to expose addresses of its elements), the 
range fills "item" with the current value, again moves on to the 
next value, and returns &item.
My big worry here is that when the range does _not_ want to provide a pointer to its internals, the caller has no way of knowing it. Modifying the pointed object may or may not modify the range. For example, "std.algorithm.map" on an Array!bool simply can't work with getNext. Since the implementer has no idea what he is operating on, the conclusion is that he simply can't use getNext when mutation is possible.
Jul 09 2012
parent "monarch_dodra" <monarch_dodra gmail.com> writes:
On Monday, 9 July 2012 at 08:55:21 UTC, monarch_dodra wrote:
 For example, "std.algorithm.map" on an Array!bool simply can't 
 work with getNext. Since the implementer has no idea what he is 
 operating on, the conclusion is that he simply can't use 
 getNext when mutation is possible.
Re-reading the documentation of "map", I realize this is a bad example, but you get the point. "initializeAll" would be a better example (I think).
Jul 09 2012
prev sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Mon, 12 Jul 2010 23:48:05 -0400, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:


 Consider this:

 T* getNext(R, T)(ref R range, ref T item);
I see you still defending this concept from 2010, does it mean we may still get it? (it would make things easier for std.io for sure) If so, can we ditch the notion of forward+ ranges requiring save? -Steve
Jul 16 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 7/16/12 5:55 PM, Steven Schveighoffer wrote:
 On Mon, 12 Jul 2010 23:48:05 -0400, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> wrote:


 Consider this:

 T* getNext(R, T)(ref R range, ref T item);
I see you still defending this concept from 2010, does it mean we may still get it? (it would make things easier for std.io for sure) If so, can we ditch the notion of forward+ ranges requiring save? -Steve
I'm not defending it, I recall last time I discussed it I mentioned its issues. The main one is it makes it awkward to iterate ranges that don't actually have elements in memory. The approach would be forced to return the same pointer to a buffer, until the end when it returns null. User code would have no ability to make the distinction. On the other hand this is what e.g. byLine does, but with an array, not a pointer. If you feel strongly there are merits in the approach that overcome its issues, why not design around it? Anyhow, it looks like this would be too big a redesign of input ranges. Like it or not, .save is here to stay. Andrei
Jul 16 2012
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Mon, 16 Jul 2012 18:37:34 -0400, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:

 On 7/16/12 5:55 PM, Steven Schveighoffer wrote:
 On Mon, 12 Jul 2010 23:48:05 -0400, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> wrote:


 Consider this:

 T* getNext(R, T)(ref R range, ref T item);
I see you still defending this concept from 2010, does it mean we may still get it? (it would make things easier for std.io for sure) If so, can we ditch the notion of forward+ ranges requiring save? -Steve
I'm not defending it, I recall last time I discussed it I mentioned its issues. The main one is it makes it awkward to iterate ranges that don't actually have elements in memory. The approach would be forced to return the same pointer to a buffer, until the end when it returns null. User code would have no ability to make the distinction.
Yes, that is a true drawback. I suppose a nullable type would help here.
 On the other hand this is what e.g. byLine does, but with an array, not  
 a pointer.
True, but I was thinking in terms of unbuffered i/o. Buffered i/o fits in ok with the current range scheme, since, as you point out, a slice can be a valid range element. Come to think of it, you still need a place to put the data, so it doesn't really matter. You always have to have a buffer to point to. So maybe this isn't such a good idea...
 Like it or not, .save is here to stay.
not. ;) -Steve
Jul 16 2012