@rockcavera this isn't specific to nim, style guides in some other languages also recommend APIs with signed types to avoid wrap-around issues.
Should they only be avoided in this loop situation or in other situations as well?
anytime you can have wraparound as an unexpected edge condition
It would not loop if an exception was raised when computing a.len - 4. Signed numbers can also wrap around and cause nasty bugs, but fortunately in Nim overflows are checked for operations on signed integers.
So, just add underflow/overflow check on unsigned numbers rather than using modular arithmetic and the dangerous behavior of unsigned numbers disappears.
So to avoid all these nasty problems with unsigned integers, it suffices to add underflow/overflow checks rather than using modular arithmetic.
No. Look at my example: With signed numbers it simply works and doesn't produce an underflow. The flaw in your reasoning is that you ignore basic numeric probabilities, 0 is a very common number and around 0 signed arithmetic does not overflow, but unsigned does.
Sorry for stupid questions, but what is the runtime overhead for subrange types such as Natural? If they panic on illegal values, there's some checking going on. Asking because if there is an overhead, how is it better than overflow-checked unsigneds?
With signed numbers it simply works and doesn't produce an underflow.
These are all genuine questions, btw. I want to be convinced, not yet.
If you add underflow check to unsigned numbers, your example raises an exception and it is what personally I would expect. The choice to use modular arithmetic is the reason why we encounter such an odd behavior.
Of course, with unsigned integers there is more chance to get an underflow than an overflow. And this is why we should avoid using modular arithmetic for unsigned integers. There is too much risk to encounter an underflow which gets unnoticed.
Ironically, in Nim we get overflow checks for signed integers which are very unlikely to occur, but not for unsigned integers where they would be really helpful.
Asking because if there is an overhead, how is it better than overflow-checked unsigneds?
This question makes little sense, I don't talk about "overhead". There is a clear subset relation, Natural <= int so you know the conversion from Natural to int cannot fail. For uint you have many values that are not representable as a signed integer.
Isn't this behaviour bad? There's clearly a bug in the logic.
No, the loop works exactly as it should work.
Isn't this example a bit questionable? We depend on a contents but instead of using bounded and enumerated item iteration we do math.
Well the example is simply about iterating over a subset if such a subset exists. For me it comes up frequently.
I don't talk about "overhead". There is a clear subset relation, Natural <= int so you know the conversion from Natural to int cannot fail.
Weren't we talking about subtracting with going down under zero? This can fail. I just want to understand how int subrange is a better solution than using checked uints (i.e. for indexing).
Well the example is simply about iterating over a subset if such a subset exists
Well, you see, you can't infer this if clause you have from reading the code. This is, as it happens, just what it does, but it's not clear if this had been the intent of the one who wrote it. I have a hunch a panic on for a in 0..-4 would be not a bad thing to do, because this expression doesn't make any sense, follows it's a bug. If it was some 0..<a.len().saturated_sub(4) then both the intent and the behaviour would be absolutely unambiguously clear. On the other hand, this is just forcing proper code documenting with runtime costs, which is also not good. :P
Well, you see, you can't infer this if clause you have from reading the code. This is, as it happens, just what it does, but it's not clear if this had been the intent of the one who wrote it.
It's always my intent and I don't enjoy writing more "if" statements for the non-mathematically inclined minds. For example I also say "for each x in S: prop(x)" is a true statement for S == {}.
There is also https://en.cppreference.com/w/cpp/ranges/ssize which exists because signed integers are really a good idea, whether you can "understand" it or not.
For example I also say "for each x in S: prop(x)" is a true statement for S == {}.
I have absolutely no problem with that, because you iterate over the things in a set. But 0..-4 isn't an empty set of natural numbers, it's just logical nonsense.
But 0..-4 isn't an empty set of natural numbers, it's just logical nonsens
It's an empty range, nothing illogical about it.
It's an empty range, there is nothing illogical about it.
Yeah, this is the source of my confusion. For the "non-mathematically inclined" minds a "range of natural numbers from 0 to -4" is a contradiction in terms, but for mathematically inclined this is just an empty set. That's what you get having more Aristotle than Cantor or Leibniz. I have no other way but to readjust. Thanks for bearing with me.