Following up on the thread at https://forum.nim-lang.org/t/3169, I took the code and replaced every instance of a..<b with a..b-1 whether in for i in a..<b or in arr[a..<b] and the code was almost 2 seconds faster for one benchmark. So not only being confusing and non-intuitive it's way slower than using explicit numerical bounds.
In Ruby a..b is used to include from a to b whereas a...b is the equivalent of a..b-1.
Maybe compile with -d:release the next time. There are no plans to deprecate ..< only to deprecate <. What's confusing about ..< anyway? That Python lacks it? But Swift has it too. ;-)
In Ruby a..b is used to include from a to b whereas a...b is the equivalent of a..b-1.
Yeah but it sucks, type one more character to remove one iteration?!
The intent of the post was to provide information from a user's perspective with real world code on how to make Nim better from the user's perspective.
Why did you make the assumption I didn't compile my code with --d:release? I did. What I hoped you appreciated was that using fixed numerical values are much more performamnt than using <b in real code. Maybe you don't care, but I hope you would.
Also, it doesn't look (and feel) good for you to speak derisively of Ruby. The .. and ... idioms in Ruby are logical, consistent, and well documented, and do not cause confusion. Whether someone types them correctly is not an issue of the idioms.
In fact, I think you could learn a lot from the Ruby community, and how Matz (Ruby's creator) has set a tone of being friendly and welcoming
Also, it doesn't look (and feel) good for you to speak derisively of Ruby. The .. and ... idioms in Ruby are logical, consistent, and well documented, and do not cause confusion.
They are ugly and illogical. And my respect for you and Matz does not stop me from being harsh on features of programming languages. I can distinguish between people and abstract non-living things. I'm sure you can do the same.
Why did you make the assumption I didn't compile my code with --d:release? I did. What I hoped you appreciated was that using fixed numerical values are much more performamnt than using <b in real code. Maybe you don't care, but I hope you would.
2 seconds faster for -1 vs ..< makes no sense, we need to look at the concrete code and at the produced assembly code to explain the difference. Or maybe it's just noise -- if it runs for several minutes a 2s difference means nothing. Either way unexplainable, likely unreproducable performance differences are a bad foundation to settle disputes in coding styles.
In Ruby a..b is used to include from a to b whereas a...b is the equivalent of a..b-1
From my user perspective, a different semantic based on the number of dots you type is just brainfuck programming..
There's absolutely no sense in badmouthing Ruby syntax. I'm not a fan of Ruby myself, I don't like its syntax, but calling it ugly and even comparing it to Brainfuck is a bit too much...
I'm afraid, the natural reaction for @jzakiya after those replies will be to become defensive and quit the Nim community spreading the word of its unfriendliness. Would be 100% understandable.
I'm not against Ruby neither against jzakiya... I'll also admit that one of the first thing I wrote in nim was the `...` iterator with that precise semantic (I also think that a better choice would have been to make the `...` iterator count until end-1 since that is what you need 99% of the times, but it's obviously too late to go back now), but I quickly noticed that it was very difficult to distinguish at first sight a double dot from a triple dot and more than once I happened to write buggy programs because of a missing or an exceeding dot, when I discovered the existence of the `..<` iterator I switched to it and never turned back.
I'm not saying that the guys of Ruby are stupid because they came up with that idea, I also did it, and I was completely unaware of the existence of the triple dot in Ruby before today! I'm just saying that it is a bad idea
That said, it would be helpful if jzakiya could provide his benchmarks of the ..< iterator vs the .. because, from a user's perspective, any significant performance difference would be quite surprising
It should be noted (for completeness) that with a Garbage Collected language, the GC can add random timings into the benchmark.
I have also found that when benchmarking, also switch around the order of two tests within the code, because that also changes things (can trigger the GC differently, and change your results).
That said, here is my little benchmark test, where I call the two tests in different orders, and for me I get results where either looping can be faster or slower than the other (ie, neither wins outright), but of course, YMMV:
import times
const
Sz = 1000
LoopCnt = 500_000
var
ss1: seq[string] = @["the","rain","in","Spain"]
si1: seq[int] = @[]
sf1: seq[float] = @[]
ss2: seq[string] = @["the","rain","in","Spain"]
si2: seq[int] = @[]
sf2: seq[float] = @[]
t0 = epochTime()
proc p1() =
for i in 0..Sz-1:
si1.add(i)
sf1.add(i.float)
for j in 0 .. LoopCnt-1:
for i in 0..Sz-1:
si1[i] += 1
sf1[i] += 1.0
if i < ss1.len:
ss1[i].add($i)
echo "Dur (..) : ", epochTime() - t0
proc p2() =
for i in 0..<Sz:
si2.add(i)
sf2.add(i.float)
for j in 0 ..< LoopCnt:
for i in 0..<Sz:
si2[i] += 1
sf2[i] += 1.0
if i < ss2.len:
ss2[i].add($i)
echo "Dur (..<) : ", epochTime() - t0
proc main() =
t0 = epochTime()
p1()
t0 = epochTime()
p2()
t0 = epochTime()
p2()
t0 = epochTime()
p1()
main()
My sample results:
tst_it.exe
Dur (..) : 2.262003898620606
Dur (..<) : 2.152803897857666
Dur (..<) : 2.230803966522217
Dur (..) : 2.293204069137573
tst_it.exe
Dur (..) : 2.262003898620606
Dur (..<) : 2.293204069137573
Dur (..<) : 2.215204000473023
Dur (..) : 2.340003967285156
tst_it2.exe
Dur (..) : 2.558404445648193
Dur (..<) : 2.995205163955689
Dur (..<) : 2.184003829956055
Dur (..) : 2.184003829956055
@jzakiya, everyone knows that Ruby has a great community and has been a highly successful language. I don't think any offense was intended.
Could you make your code and tests available? If you results can be reproduced, they would make a stronger case. If nothing else, I would code "a..(b-1)" instead of "a..<b".
When I learned Ruby (10 years ago), I found it difficult to remember the difference between .. and .... Not obvious. But everyone language has a few things that simply must be memorized.
To be honest I also find ... Ruby syntax illogical (not a Rubyist, coming from Python)
Triple dot are probably a source of silent bugs that are also harder to catch in code reviews.
Also I think ..< better convey the meaning of mathematical exclusive range [a, b[ vs inclusive [a, b]
Now regarding perf issue, iirc the <, ..<, ^, ..^, high, low that works for range/slice are all implemented via compiler magic, I use them all over the place because I suppose they are fast (as fast as doing b-1) and would be interested to understand perf diff.
(there are two things being discussed here: speed and coding style. This post has to do with speed differences)
I have simplified the benchmark I posted before.
The statement
it's way slower than using explicit numerical bounds
seems VERY difficult to prove, given the following code:
import times, sequtils
const
LoopCnt = 50_000_000
var
t0 = epochTime()
ival = newSeqWith(LoopCnt, 0)
proc p1() =
t0 = epochTime()
for j in 0..LoopCnt-1: ival[j] += 1
echo "Dur (..) : ", 1_000_000*(epochTime() - t0)
proc p2() =
t0 = epochTime()
for j in 0..<LoopCnt: ival[j] += 1
echo "Dur (..<) : ", 1_000_000*(epochTime() - t0)
proc main() =
p1(); p2(); p2(); p1()
p1(); p2(); p2(); p1()
main()
which (looking at the c code) produces loops where the difference is the following lines
if (!(res <= ((NI) 49999999))) goto LA3;
and
if (!(i < ((NI) 50000000))) goto LA3;
so the difference between testing <= and testing < can NOT produce significant differences (and certainly does not account for a 2 second difference).
(of course I am not testing array/sequences indexing, just loops)
@jzakiya Don't take me wrong but ... really seems not-so-good-idea, despite being used in Ruby. It was widely discussed by Rust users and they used the same arguments Araq and woggioni (well, sort-of-argument woggioni used ;) ) used here. Some alternatives include pairs of ..< and .. or .. and ..=, both of which have their own problems. A friend of mine always uses a .. b-1 in Nim due to these problems.
Besides, I wouldn't really call criticizing ... "badmouthing Ruby". It's a nice language but it has it's own problems, pretty much like Nim. It's not like you can only praise it infinitely to show your respect to its developers. ;) It's not unfriendly at all, actually, some languages' communities, like Rust's one, criticize their language heavily as well. What's more, it seems really healthy to me. :) I myself criticize Nim a lot, actually (hi, Araq xD).
@jzakiya I think that one of the things for which Nim should be praised is the fact that things that are missing can be very easily added to the language: this was my implementation of the `...` operator (it can also be used to iterate backwards: 10...0 starts from 10 and ends with 1), if you really like it you can use it or write your own (NIMBY)
iterator `...`*[T](a: T, b: T): T =
var res: T = T(a)
if a<b:
while res < b:
yield res
inc res
else:
while res > b:
yield res
dec res