Reversed Ranges

I find the concept of

for i in 1..n by -1 do {....}

ugly and unclear. I would much prefer to see

for i in n..1 do {...}
``1
but that is simply incorrect. But
```chapel
for i in -(1..n) do

is quite clear. But my crude routine

  inline operator -(r: range(?))
  {
    return r.low .. r.high by -r.stride;
   }

does not use new like the routines in ChapelRange.chpl.

Am I saling close to the wind or is that feasible?

I don't know the answer to your immediate question but another possibility is to add a method or function for ranges that reverses them, e.g. for i in reverse(1..n). Such a thing might be more findable in the documentation.

Thanks. That sounds smarter and more readable. What is the idea behind creating a new range and what do the arguments mean please?

Hello Damian,

Your operator - looks fine to me. What are your concerns about it or why is it not sufficient for you?

The new range() operator is not intended to be user-facing. However it surely comes handy for creating a range given all its properties, such as idxType, stridability, boundedness, bounds, stride, and alignment. Let me know your use cases and we can figure it out.

If you you want your operator - or proc reverse to be robust, let us know and we can work through a couple of considerations such as unbounded ranges (to avoid compilation errors) and the strides parameter of the result (for performance).

Vass

I'm not a fan of -(1..10), but didn't say so earlier since Michael's proposal seemed to cause Damian to back away from it. The main reason is that I don't think it's self-evident what it does, and relies on a very different interpretation of - than we normally have. A secondary reason is that today it results in promoting - across the range, so would be a breaking change post-2.0. For me, that combination of things suggests doing something different / clearer like Michael's suggestion.

-Brad

i am more than happy with reverse. I will scribble down what I think its code should be and people can tell me where it is suspect. I would assume that for my needs, an open range should be treated as a compilation error

Is this optimal?

  inline proc reverse(r: range(?))
  {
    return r.low .. r.high by -r.stride;
  }

Thanks

Re optimal: the strides parameter of the resulting range seems any instead of, ideally, negOne. Let me look into it.

Also keep in mind that r.low and r.high involve some computation when r.strides != strideKind.one. For example, reverse(1..5 by 2 align 2) gives 2..4 by -2.

Separately, I suggest you add an explicit check for clearer compiler error:

if r.bounds != boundKind.both then
  compilerError("only bounded ranges are allowed");

Hi Damian,

I mentioned this to Vass offline, but he's on vacation at the moment so
might not be checking his messages.

The improvements he's suggested here are definitely good things to
consider, and would be necessary to consider the feature stable. But
don't feel as though you have to add them in order to submit the feature
if you wanted to, even in your original form it could be useful to other
users. We would be happy to have that version in our code base as an
unstable feature, with some mention of the considerations outlined here,
e.g.,

/* This function is unstable because it needs explicit handling for 
strides other than <link to strideKind one>, <list of other things Vass 
> */
@unstable("reverse is unstable due to being new")
inline proc reverse(r: range(?))
   {
     return r.low .. r.high by -r.stride;
   }

Of course, if you need to or are interested in diving more into those
details yourself, please don't let me stop you from doing so! I just
don't want you to feel as though you have to go through the more
extensive robustness examination that we'd want to follow ourselves if
you're just trying to get something that works for a more limited case.

Thanks,
Lydia

Right! Sorry to revive this topic one year later... A programmer used to Fortran's DO statement might be slightly surprised by the example below

inline proc reverse(r: range(?)) {
   return r.low .. r.high by -r.stride;
}

inline proc reverse2(r: range(?)) {
   return r.lowBound .. r.highBound by -r.stride;
}

for i in reverse(1..10 by 2) do {
   writeln("i = ",i);
}
writeln("-"*20);
for j in reverse2(1..10 by 2) do {
   writeln("j = ",j);
}
writeln("-"*20);
for k in 1..10 by -2 do {
   writeln("k = ",k);
}

which prints

i = 9
i = 7
i = 5
i = 3
i = 1
--------------------
j = 10
j = 8
j = 6
j = 4
j = 2
--------------------
k = 10
k = 8
k = 6
k = 4
k = 2

Cheers

Hi Nelson —

Can you say more about what a Fortran programmer would expect here / What could be done to reduce that surprise?

Thanks,
-Brad

Hi Brad: I haven't programmed in Fortran for many years on a regular basis (I use Chapel now), but something similar to the Chapel loops that I posted is

program downtoby2
   implicit none
   integer :: i
   do i = 10,1,-2
      write(6,*) 'i = ', i
   end do
end program downtoby2

This results in an ouptut similar to those from for j ... and for k ... above.
A Fortran programmer therefore might be surprised by the reverse(...) behavior; reverse2(...) would (perhaps) produce a more "familiar" result. I stumbled into this while trying reverse (which I remembered from a year ago) --- and was surprised. I do not think that there is any action needed about it --- just awareness.

Regards

Nelson

Great, thanks for clarifying Nelson! I agree that for someone coming from a (start, stop, negative stride) perspective, your definition of reverse2 seems more intuitive than reverse does. That said, it also makes me think that maybe a different name or different set of arguments would help clarify things even further.

For example, if I say reverse(myRange) using a named range rather than a literal one like 1..10 by 2, it makes sense to me that it would reverse the indices represented by the range (whatever they are) rather than extracting the original bounds and generating new indices that may not be represented by the range. For example if I did:

const myRange = …; // whatever
var A: [myRange] real;
for i in reverse(myRange) do
  …A[i]…

it would be surprising that I could potentially get OOB array accesses when it looks as though I've asked to iterate over the array's indices, simply in reverse order.

However, if I wrote a distinct countDown() or fortranDo() (or …) iterator that took distinct start, stop, stride integer arguments, it seems very reasonable to define it to behave like reverse2().

@vass or others may have additional thoughts here.

-Brad

When I wrote that cut of reverse() I did not know that lowbound and highbound existed.

Yes. Awareness is the order of the day. At Vass's suggestion, I messed around with compilerError so I only allow unit-stride ranges in reverse() above. Anything else gets a custom routine along the lines of what Brad is suggesting which looks like step first,final,stride which is what CPL called a range. Or, I fall back to using a while loop. Programming languages have too many concepts with too many subtle differences and not enough English words to be different.

A do (or for) range of Fortran (and other languages) and the array slice of Fortran (and many other languages) are not the same as a Chapel range. Chapel's is more powerful and in spite of the name, not quite the same, even though it gets used for the same thing most of the time in Chapel programs. Also, and it only took me 13 years to learn this and only after Brad told me, by is an operator and (in my opinion) is more of a navigation rule for how to step through the range to which it is applied. I was deluded that by in Chapel was the same thing as the word which is spelt the same way in other languages land used in constructs like

for i := 1 to 10 by 2 do
for i from 1 to 10 by 2 do
for i from 10 downto 1 by 2 do // love this - really clear

My encounters with the concept of a range was first:final (from Algol68 and Ada and mates) or first:final:stride (or originally first:final;stride) which is used in Fortran to define a dimension range in a declaration or an array slice in an expression (most people I know and myself refer to a slice which is not a subset of the dimension range as range which is very naughty). Those old concepts are very hard to remove from ones brain especially when they appear in text books to do with linear algebra.

Should one use lowbound (highbound) or low (high) when referring to ranges.

What is the precise difference? Thanks

Hi Damian —

Both are useful, and which to use depends on what behavior you want. In the range 1..10 by 2 both low and lowBound are 1, but high would be 9 (the highest index represented by the range) and highBound would be 10 (the high bound as defined by the range). [ATO]

In a range strided by 1 or -1, both calls will always return the same value.

-Brad

This seems like it might be a good candidate for a user's guide
example. Damian, would you be up for writing an issue requesting it?

Lydia

Yes. Next week.I will put together something.

My Markdown skills are awful. I will send my best attempt and somebody else can refine it.

Sounds good, and no worries if you don't get to it. I'll try to
remember, too, so hopefully it doesn't get forgotten.

Lydia