Here is another reason why an exclusive upper bound is a saner approach:
Suppose you wished to write a function that applies some transform to a subsequence of items in a list. If intervals were to use an inclusive upper bound as you suggest, you might naively try writing it as:
def apply_range_bad(lst, transform, start, end):
"""Applies a transform on the elements of a list in the range [start, end]"""
left = lst[0 : start-1]
middle = lst[start : end]
right = lst[end+1 :]
return left + [transform(i) for i in middle] + right
At first glance, this seems straightforward and correct, but unfortunately it is subtly wrong.
What would happen if:
start == 0end == 0end < 0
? In general, there might be even more boundary cases that you should consider. Who wants to waste time thinking about all of that? (These problems arise because by using inclusive lower and upper bounds, there no inherent way to express an empty interval.)
Instead, by using a model where upper bounds are exclusive, dividing a list into separate slices is simpler, more elegant, and thus less error-prone:
def apply_range_good(lst, transform, start, end):
"""Applies a transform on the elements of a list in the range [start, end)"""
left = lst[0:start]
middle = lst[start:end]
right = lst[end:]
return left + [transform(i) for i in middle] + right
(Note that apply_range_good does not transform lst[end]; it too treats end as an exclusive upper-bound. Trying to make it use an inclusive upper-bound would still have some of the problems I mentioned earlier. The moral is that inclusive upper-bounds are usually troublesome.)
(Mostly adapted from an old post of mine about inclusive upper-bounds in another scripting language.)