6 ms·
Or we could just accept and implement the "pernicious three dots". when you see `-1` in code it is often a failure of the expressiveness of 0-indexed lists. Fo
by protonfish 10y ago
Or we could just accept and implement the "pernicious three dots".
when you see `-1` in code it is often a failure of the expressiveness of 0-indexed lists. For example, the last item in an array:
arr[arr.length - 1]
Or a found item by index number:
if (arr.indexOf("thing") > -1) {
This is hardly an elegant convention.
- jacobolus 10y agoThe last item of an array should be simply arr[-1]. This is very easy to understand once you have played with it for a few days. Your second example isn’t ultimately about array indices, but is caused by confusion between using 0 as both an ordinal number and as a representation of boolean false.
- int_19h 10y agoThe problem with this is that it breaks the conceptual model of zero-based indexing - your indices start from 0 when counting from the left, but from (-)1 when counting from the right! It always irked me in Python. This is especially painful if you need to do further arithmetic on the indices in a single slice operation, because you have to remember the different rules at play here. With 1-based indexing, you can just skip zero, and be consistent - a[1] is the first element from the left, a[-1] is the first element from the right.
- jacobolus 10y agoThe “conceptual model of zero-based indexing” is: use the left fencepost as the index. This is consistent and easy to reason about. With one-based indexing, you can’t have arr[-1] as the last element (that would cause huge problems!). Languages with one-based indexing write something like arr[end] or arr[arr.length] to refer to the last element, with arr[end-1] or arr[arr.length-1] referring to the second-to-last element.
- int_19h 10y ago> With one-based indexing, you can’t have arr[-1] as the last element (that would cause huge problems!) Can you give some examples?
- benlorenzetti 10y agoI agree with you but wouldn't the first element from the right be a[-0], sort of an abuse-of-notation for a[N-0]?
- int_19h 10y agoIt could, if you could distinguish positive and negative zero for integers.
- Normal_gaussian 10y agoThe index a[b] in languages with safe bounding can be thought of as a[(a.length + b) % a.length]. Modulo and exponents are, to me, the primary reason for using a 0 based numbering system
- junke 10y agoarr.indexOf("thing") > -1 ... this one is hardly related to zero-indexing. If indexOf returned 0, this would be equally inelegant.
- pwdisswordfish 10y agoAnd if indexOf returned null or undefined when the value is not found, this could not even be expressed with an ordering relation.
- junke 10y agoWhich is ... great? what is your point?
- pwdisswordfish 10y agoThat whether an element was found in a list or not has nothing to do with the order relation on the type of indices. Conceptually, it's a completely distinct case; in a more solid type system it would be captured by an option type. This becomes especially evident when you attempt to generalise this searching operation to arbitrary maps.
- qwertyuiop924 10y ago>when you see `-1` in code it is often a failure of the expressiveness of 0-indexed lists. ...And when you see '+1' or '(x%ArrayLen)+1' or 'x<=arrayLen', in lua or FORTRAN it is often a failure in the expressiveness of one-indexed lists.
- int_19h 10y agoThe most common case where +1 arises in 1-based indexing code is when you have to index from the end, but the language doesn't support a special syntax for it. So, for example, if you need to slice an array, starting from the Nth, and up to and including the Nth from the end, and your slice syntax is end-inclusive (as is typical for 1-based approach), you get something like: a[N : length(a) - N + 1] However, if you do have such special support - e.g. if negative indices are treated as from-the-end (like Python does), then it's just: a[N : -N] Compare this to 0-based N with end-exclusive slices (again, as is typical). If you don't have reverse indexing, it's slightly simpler: a[N : length(a)] but with reverse indexing (where -1 is the last element), it's actually worse: a[N : -N - 1] FWIW, most of it has more to do with the fact that 1) negative numbers are probably not the best way to represent reverse indices, and 2) depending on the situation, either end-inclusive or end-exclusive can be convenient. In fact, merely having a special syntax for reverse indexing solves it for 0-based languages. For example, suppose that we extend the slice syntax such that either index can be prefixed with ~ to indicate indexing from the end. Then ~0 is the last element, and it becomes: a[N : ~N] This has a side benefit of avoiding errors when the computed index is accidentally negative, and ends up being treated as reverse, even though that was not actually the intent. Encoding the direction in the slice syntax itself means no runtime magic. Alternatively, you can provide syntactic sugar for the length of the array within the indexer - say, `$`. Then, in 0-based end-exclusive approach, you just write: a[N : $ - N] This is one case that 1-based approach doesn't handle nearly as well, because for the same simple syntax to work there, you'd need to make $ be length + 1, which is kinda ugly. However, this only works if your ranges aren't first-class (i.e. if they only appear inside []). These days, the trend seems to be to have ranges as first-class values in the language, and have the indexer accept a range as an operand. In that case, since the range can be constructed separately, it needs to be able to encode "index from start" and "index from end" inside the object itself, and special range syntax for that makes more sense. If the language uses iterators or equivalent, then the iterator itself can encode the direction, and the range is just a pair of iterators. But you would still want some special short syntax, or at least a short convenience function, to easily create reverse iterators. C++ does just that - a pair of iterators expressing the same thing for random-access container `a` would be: make_pair(a.begin() + N, a.rbegin() + N) This is very generic, but the annoyance here is that you have to repeat `a` twice, and it could be a lengthy expression, or even have side effects. Some form of let..in syntax to introduce local bindings in expressions, like ML does, would help a lot here - you could do e.g.: let a = <some very long expression> in make_pair(a.begin() + N, a.rbegin() + N)
- robotresearcher 10y ago> This is hardly an elegant convention. C++ has arr.back() which is much nicer to read, but has the downside that behaviour is undefined when the vector is empty. Seems like interfaces always have pain points.
- Avshalom 10y agoto be fair in many languages we've managed to squeeze it down to two pernicious dots
- JadeNB 10y ago> For example, the last item in an array: arr[arr.length - 1] Of course, one could argue that this just means that the length of an array is a less meaningful property than its last populated index (what Perl calls `$#array`); one can imagine arr[arr.lastIndex] or, which is probably better, just arr.last > Or a found item by index number: if (arr.indexOf("thing") > -1) { This comes from overloading return values to communicate errors. It's not clear to me that if (arr.indexOf("thing") > 0) { (in a hypothetical analogue with 1-indexed lists) demonstrates any clear improvement; something like if (arr.member?("thing")) { seems preferable in terms of clarity (ignoring the efficiency objection that one would probably immediately after look for the index).