May 20, 2009
On Wed, 20 May 2009 13:04:42 -0400, Andrei Alexandrescu <SeeWebsiteForEmail@erdani.org> wrote:
> Bill Baxter wrote:
>> On Wed, May 20, 2009 at 9:19 AM, Andrei Alexandrescu
>> <SeeWebsiteForEmail@erdani.org> wrote:
>>
>>> I'm thinking a better design is to require any range that's forward or
>>> better to define a function save(). Ranges that don't implement it are input
>>> ranges; those that do, will guarantee a brand new range is returned from
>>> save(). So then adjacentFind would look like this:
>>>
>>> R adjacentFind(R)(R r)
>>> {
>>>    if (r,empty) return r;
>>>    R last = r.save;
>>>    r.popFront;
>>>    for (; !r.empty && last.front != r.front; last.popFront, r.popFront)
>>>    {
>>>    }
>>>    return r;
>>> }
>>>
>>> Obviously, when you pass a range that doesn't support save, adjacentFind
>>> will not compile, which is what we want.
>>  The only other alternative that comes to mind would be forcing input
>> ranges to hide their copy constructor, or whatever the D equivalent
>> is, making R last = r; fail.  But that would make input ranges very
>> difficult to use.
>
> Exactly. I thought of that design, and it was difficult to even pass a range to a function.
>
>> So, of those two options at least, requiring a .save sounds like the
>> better choice.
>>  The down side is you will get no error if you write the code the first
>> way, without a .save.   I see this as turning into tip #5 in
>> "Effective D" -- "Know when to use .save"   It would be nice if that
>> potential mistake could be eliminated somehow.  You could perhaps
>> require input ranges to implement transfer semantics, and have them
>> implement a .clone for cases when you really do want to make an
>> aliasing copy.
>
> Good point. I don't have a solution for that. Giving ranges move semantics would probably make for another Effective D tip (or perhaps more... move semantics are pretty brutal).
>
> Another partial solution is to define a different interface for input ranges, one that combines front() and popFront(). Something like popNext. That way, people who use only the primitives empty() and popNext() know they are using a forward range and with hope they'll remember they can't really save copies of it and expect them to "remember" where they are in the input.
>
>
> Andrei

Bicycle shed: Well, since output ranges use 'put', how about 'get' for input ranges?
May 20, 2009
Robert Jacques wrote:
> Bicycle shed: Well, since output ranges use 'put', how about 'get' for input ranges?

Nice color :o). In fact, "put" is a poor choice because it doesn't reflect advancement. Probably putNext and getNext are better.

Andrei
May 20, 2009
== Quote from Andrei Alexandrescu (SeeWebsiteForEmail@erdani.org)'s article
> In wake of a few discussion I've witnessed, I'm thinking of a last
> change for ranges. (In fact there's one more, but that's minor.)
> The problem is that input ranges and forward ranges have the same
> syntactic interface, but different semantic interfaces. Consider the
> problem of finding the first two identical adjacent items in a range:
> R adjacentFind(R)(R r)
> {
>      if (r,empty) return r;
>      R last = r;
>      r.popFront;
>      for (; !r.empty && last.front != r.front; last.popFront, r.popFront)
>      {
>      }
>      return r;
> }
> This will work properly on lists and vectors, but horrendously on files
> and sockets. This is because input ranges can't be saved for later use:
> incrementing r also increments popFront and essentially forces both to
> look at the same current value.
> I'm thinking a better design is to require any range that's forward or
> better to define a function save(). Ranges that don't implement it are
> input ranges; those that do, will guarantee a brand new range is
> returned from save(). So then adjacentFind would look like this:
> R adjacentFind(R)(R r)
> {
>      if (r,empty) return r;
>      R last = r.save;
>      r.popFront;
>      for (; !r.empty && last.front != r.front; last.popFront, r.popFront)
>      {
>      }
>      return r;
> }
> Obviously, when you pass a range that doesn't support save, adjacentFind
> will not compile, which is what we want.
> Andrei
> P.S. There is a way to implement adjacentFind for forward ranges by
> saving data instead of ranges. I've used a limited version above for
> illustration purposes.

Sounds like at least a reasonable solution.  The thing I like about it is that, in addition to safety, it allows for the range to do fancy and arbitrary stuff under the hood if necessary to allow for saving.  Also, while we're fine tuning input ranges vs. forward ranges, I think the concept of iterables as a catch-all for ranges, opApply, builtins, etc. needs to be introduced and fine tuned, too.  We've shown on this NG previously that, while ranges are usually preferable for the flexibility they offer, opApply does have its legitimate use cases.
May 20, 2009
dsimcha wrote:
> Also, while we're fine tuning input
> ranges vs. forward ranges, I think the concept of iterables as a catch-all for
> ranges, opApply, builtins, etc. needs to be introduced and fine tuned, too.  We've
> shown on this NG previously that, while ranges are usually preferable for the
> flexibility they offer, opApply does have its legitimate use cases.

An input/forward range is basically just another name/syntax for an iterable. Perhaps algorithms that work on input ranges should be written using foreach instead of front/popFront?
May 20, 2009
Robert Fraser wrote:
> dsimcha wrote:
>> Also, while we're fine tuning input
>> ranges vs. forward ranges, I think the concept of iterables as a catch-all for
>> ranges, opApply, builtins, etc. needs to be introduced and fine tuned, too.  We've
>> shown on this NG previously that, while ranges are usually preferable for the
>> flexibility they offer, opApply does have its legitimate use cases.
> 
> An input/forward range is basically just another name/syntax for an iterable. Perhaps algorithms that work on input ranges should be written using foreach instead of front/popFront?

For most, foreach is not sufficient.

Andrei
May 20, 2009
Andrei Alexandrescu Wrote:

> Jason House wrote:
> > I feel like there are too many differences between input and forward ranges for such a minor difference. Many range functions are written assuming no side effects on the caller. This can restrict the use of helper functions. It may be best to make their usage different...
> 
> So how do you think we should go about it? Also don't forget that any ranges should be seamlessly and efficiently treated as input ranges.
> 
> Andrei

You won't like my answer!

Like you've already said, the semantics of forward ranges and input ranges are different. I would argue that forward ranges have value semantics but input ranges do not. Any function that implicitly assumes value semantics is wrong. Sadly, overlapping API's makes that all too easy for someone to write bad code that passes simplistic tests with forward ranges and then fail with input ranges.

My initial thoughts is that input ranges should have two changes:
1. A different API from forward ranges
2. Be a reference type (AKA class instead of struct)

In short, I disagree with your basic premise of treating the two ranges similarly.
May 20, 2009
Jason House wrote:
> Andrei Alexandrescu Wrote:
> 
>> Jason House wrote:
>>> I feel like there are too many differences between input and forward
>>> ranges for such a minor difference. Many range functions are written
>>> assuming no side effects on the caller. This can restrict the use of
>>> helper functions. It may be best to make their usage different...
>> So how do you think we should go about it? Also don't forget that any ranges should be seamlessly and efficiently treated as input ranges.
>>
>> Andrei
> 
> You won't like my answer!
> 
> Like you've already said, the semantics of forward ranges and input ranges are different. I would argue that forward ranges have value semantics but input ranges do not. Any function that implicitly assumes value semantics is wrong. Sadly, overlapping API's makes that all too easy for someone to write bad code that passes simplistic tests with forward ranges and then fail with input ranges.
> 
> My initial thoughts is that input ranges should have two changes:
> 1. A different API from forward ranges
> 2. Be a reference type (AKA class instead of struct)
> 
> In short, I disagree with your basic premise of treating the two ranges similarly.

I don't want to treat them similarly, but we should be able to treat forward ranges as input ranges. Otherwise, all algorithms that work for input ranges would have to be written twice.

Andrei
May 20, 2009
== Quote from Jason House (jason.james.house@gmail.com)'s article
> Andrei Alexandrescu Wrote:
> > Jason House wrote:
> > > I feel like there are too many differences between input and forward ranges for such a minor difference. Many range functions are written assuming no side effects on the caller. This can restrict the use of helper functions. It may be best to make their usage different...
> >
> > So how do you think we should go about it? Also don't forget that any ranges should be seamlessly and efficiently treated as input ranges.
> >
> > Andrei
> You won't like my answer!
> Like you've already said, the semantics of forward ranges and input ranges are
different. I would argue that forward ranges have value semantics but input ranges do not. Any function that implicitly assumes value semantics is wrong. Sadly, overlapping API's makes that all too easy for someone to write bad code that passes simplistic tests with forward ranges and then fail with input ranges.
> My initial thoughts is that input ranges should have two changes:
> 1. A different API from forward ranges
> 2. Be a reference type (AKA class instead of struct)
> In short, I disagree with your basic premise of treating the two ranges similarly.

Then how would you handle functions that only require lowest common denominator functionality?  This is true for *a lot* of cases, including some important ones like finding the mean and standard deviation some object.  (Incidentally, this is also where iterable comes in, since such a function doesn't even need to care if the iteration is with ranges, opApply, or the fairy #$()@* god mother, as long as foreach somehow works.)  You mean to tell me you'd require explicit handling of both the input range and forward range case separately in these cases?  The day this happens, I switch to Java because D will have become just as much of an insanely verbose bondage and discipline language, but with less libraries.
May 20, 2009
On Wed, May 20, 2009 at 11:44 AM, Andrei Alexandrescu <SeeWebsiteForEmail@erdani.org> wrote:
> Jason House wrote:
>>
>> Andrei Alexandrescu Wrote:
>>
>>> Jason House wrote:
>>>>
>>>> I feel like there are too many differences between input and forward ranges for such a minor difference. Many range functions are written assuming no side effects on the caller. This can restrict the use of helper functions. It may be best to make their usage different...
>>>
>>> So how do you think we should go about it? Also don't forget that any ranges should be seamlessly and efficiently treated as input ranges.
>>>
>>> Andrei
>>
>> You won't like my answer!
>>
>> Like you've already said, the semantics of forward ranges and input ranges are different. I would argue that forward ranges have value semantics but input ranges do not. Any function that implicitly assumes value semantics is wrong. Sadly, overlapping API's makes that all too easy for someone to write bad code that passes simplistic tests with forward ranges and then fail with input ranges.
>>
>> My initial thoughts is that input ranges should have two changes:
>> 1. A different API from forward ranges
>> 2. Be a reference type (AKA class instead of struct)
>>
>> In short, I disagree with your basic premise of treating the two ranges similarly.
>
> I don't want to treat them similarly, but we should be able to treat forward ranges as input ranges. Otherwise, all algorithms that work for input ranges would have to be written twice.

auto inp = std.typecons.inputRangeFromForwardRange(fwd);


No need to write the algos twice now, but you do have to add a line or two of code to every input range algo.  Or force the the user to call the converter function.

--bb
May 20, 2009
== Quote from Bill Baxter (wbaxter@gmail.com)'s article
> On Wed, May 20, 2009 at 11:44 AM, Andrei Alexandrescu <SeeWebsiteForEmail@erdani.org> wrote:
> > Jason House wrote:
> >>
> >> Andrei Alexandrescu Wrote:
> >>
> >>> Jason House wrote:
> >>>>
> >>>> I feel like there are too many differences between input and forward ranges for such a minor difference. Many range functions are written assuming no side effects on the caller. This can restrict the use of helper functions. It may be best to make their usage different...
> >>>
> >>> So how do you think we should go about it? Also don't forget that any ranges should be seamlessly and efficiently treated as input ranges.
> >>>
> >>> Andrei
> >>
> >> You won't like my answer!
> >>
> >> Like you've already said, the semantics of forward ranges and input ranges are different. I would argue that forward ranges have value semantics but input ranges do not. Any function that implicitly assumes value semantics is wrong. Sadly, overlapping API's makes that all too easy for someone to write bad code that passes simplistic tests with forward ranges and then fail with input ranges.
> >>
> >> My initial thoughts is that input ranges should have two changes:
> >> 1. A different API from forward ranges
> >> 2. Be a reference type (AKA class instead of struct)
> >>
> >> In short, I disagree with your basic premise of treating the two ranges similarly.
> >
> > I don't want to treat them similarly, but we should be able to treat forward ranges as input ranges. Otherwise, all algorithms that work for input ranges would have to be written twice.
> auto inp = std.typecons.inputRangeFromForwardRange(fwd);
> No need to write the algos twice now, but you do have to add a line or
> two of code to every input range algo.  Or force the the user to call
> the converter function.
> --bb

But if you make the input range a class as Jason proposed, then:

1.  Unless it's final, its methods will be virtual (slow).
2.  You trigger a heap allocation every time you want to make this conversion.  (slow)