Hi all,
I suggest adding a special case ..< operator into the language so that a..<b will be equivalent to a.. <b, rather than failing with Error: undeclared identifier: '..<'.
[I know this has been discussed in the forum before, but searching for ..< doesn't work in either the forum search function or Google, so I can't point to the posts.]
By default, a..<b doesn't compile in either of the statements for i in 0..<5: echo($i) or var a: array[0..<3, int], but a.. <b compiles fine in both cases.
I just updated my Nim compiler (in case this problem had recently been resolved) and I still encounter this problem. I realise that it's the most trivial code fix (ie, insert a space between the .. and < tokens), but I'm a recovering C++ victim and this reminds me of the C++ template > > fiasco.
There is already a special case for the common idiom *:.
I was originally going to suggest that ..< simply be added as a template in the system module, but I can't seem to create a template that works for the var a: array[0..<3, int] case.
All three of these work for for i in 0..<5: echo($i):
template `..<`(left, right: expr): expr = left .. < right
template `..<`(left, right: expr): expr {.immediate.} = left .. < right
macro `..<`(left, right: expr): expr =
result = infix(left, "..", prefix(right, "<"))
But for all of them, var a: array[0..<3, int] now fails with the error Error: ordinal type expected (despite the fact that the dumped AST of array[0.. <3, int] looks identical to the result I assembled in the macro).
Ah, I just found the previous forum thread on this topic: search term "iterator syntax".
FWIW, @Araq's suggestion of template `..<`(a, b): expr = a .. < b doesn't appear to work with array either.
See also: Negative indices with inclusive intervals are dangerous
The relevant part is that .. < does not act as "from a until b" if b is zero. That behavior would definitely be expected from a ..< operator, but right now it is impossible to achieve with a simple implementation, so such a operator would add to confusion and cover up potential bugs even more.
Hi @gmpreussner, I don't think the < in ..< is arbitrary at all.
Anyone who has ever learned C/C++/C#/Java/JavaScript (so, >95% of the world's programmers?) will recognise the standard C for-loop idiom to iterate over an array of length N when indices begin at 0. Even newer languages like Python that offer "foreach"-style loops, also explicitly support a [0, N) closed-open interval with range and enumerate.
I use the equivalent of 0..(n-1) very frequently. For example, in my main Nim project (4919 non-whitespace lines of Nim), I use ..<N 28 times (0.57% of my code lines!) and ..N only 13 times. In my years of programming, I've almost never used 0..(n-k) unless I was implementing some tricky sorting/searching algorithm.
I can understand the intention & benefit of the .. syntax (as seen in Fortran, Ada, Pascal, Basic, Smalltalk, Haskell, OCaml and Ruby). In particular, you don't need an end value that's one beyond the end of your range, which makes possible to iterate over the whole of an ordinal space.
But I think that an additional ..< is a very sensible & reasonable compromise for all the C-family programmers.
(One of my eyebrows did go up slightly when I saw the stand-alone unary '<' that is equivalent to 'pred', but hey, as it says right there in the documentation, it's "for nice looking excluding ranges".)
I don't understand the problem here.
.. is used in two different cases (slice + range) and it is normally just a slice.
< is an unary operator which gives pred of a value and was "just made so that ranges / slices look nice.
Some use later one because it does look better to them.
Having ..< would be something new. A slice / range which does handle its right operand in a new way. I think the proposal is to make ..< syntactic sugar for .. < which is probably fine. But to me it feels strange because my guts say: The range/slice (..) uses something decreased by one (<) like the (i-1) and not ..< "counts" one less. I hope I make sense :)
BTW: I would prefer to have the for accept a slice as range which would be this:
let r = 1.. <5
for i in r.a .. r.b:
echo i
P.S.: That would make it work to create a personal ..< operator which creates a slice and it would work in the for construct.
I just tried it and got this. Isn't this the solution?
template `..<`(left, right: expr): expr = left .. < right
iterator items*(a: Slice): int {.inline.} =
var i = a.a
while i <= a.b:
yield i
inc i
let r = 1..<5
echo "slice"
echo r
echo "case 1"
for i in r:
echo i
echo "case 2"
for i in 1..<5:
echo i
echo "case 3"
let s = "ABCDEFGHIJKLMNO"
echo s[1..<5]
echo "case 4"
echo s[r]
Hi @OderWat, your suggestion is the very first code example that I posted of what doesn't work. It doesn't solve the original array example that I described.
If I add my original array example to the end of your example code:
var a: array[0..<3, int]
then I get the same compilation error as I reported in my original post: Error: ordinal type expected.
If instead, I substitute r (the slice that you defined in your example code):
var a: array[r, int]
then I get a different compilation error: Error: cannot evaluate at compile time: r.
Hi @BlaXpirit, I did read your post and bug report. Your bug report is about an issue that is independent & parallel to the issue that is causing me problems right now. :)
I already encounter the issue that you described in your bug report, but in a different way: If I try to declare an array with an index range of either 0.. <0 or 0.. -1, then compilation fails with the error Error: range is empty. Arrays can't be declared with empty ranges (and that seems sensible to me, because otherwise you wade into addr issues... unless an array is always allocated for N+1 elements).
Anyway, @Jehan has already explained the underlying cause of this problem... which supports my reasoning that a special case for this is needed in the language.
@jboy Well, my focus was suddenly on having for/in working with the slice (line 17) by defining this iterator.
I overlooked the array type problem, which looked not very important to me as this is compile time only anyway. I would also not use "0..<10" instead of "0..9".
You only get one relevant compilation error imho, as the case with the slice just makes sense if you define the r as const. Then you will get the same error as for using the template itself. Which is what @Jehan explained.
P.S.: I also wonder why you (@jboy) experiment with allocating empty arrays (at compile time).
@jboy: "In particular, you don't need an end value that's one beyond the end of your range, which makes possible to iterate over the whole of an ordinal space."
Except that was never permited by Nim's implementation. It results in an infinite loop: https://github.com/Araq/Nim/issues/148
There I suggested too an ..< iterator, but for a different reason.