I always remembered it as "the third dot makes it bigger so it pushes the range and the last item falls off". I can't remember where that came from, perhaps related to the poignant guide:
> I suspect `0..=n` would work similarly well, but I've not seen a real language to ever do that yet
This could be extended to cover `0=..=n`, `0<..=n`, `0<..<n` and `0<..=n`.
You can actually define that syntax in Haskell:
Prelude> let a =..= b = [a..b]
Prelude> let a <..= b = [(a+1)..b]
Prelude> let a <..< b = [(a+1)..(b-1)]
Prelude> let a =..< b = [a..(b-1)]
Prelude> (1 =..= 4, 1 <..= 4, 1 <..< 4, 1 =..< 4)
([1,2,3,4],[2,3,4],[2,3],[1,2,3])
As an alternative opinion, I personally hate when ranges are exclusive by default. In addition to it feeling somewhat unintuitive to me that I need to use range(0, 6) to include 5, it also makes downward ranges quite awkward, e.g. needing range(5, -1) to count from 5 to 0.
Exclusive by default is designed to fit with 0-based indexing (0..length(v), instead 0..(length(v)-1)). That said, this thinking is less relevant in languages with iterators/ranges and doubly so ones that offer a way to explicitly, but abstractly, iterate over the indices of a vector/array.
On the point of downward ranges, you can often reverse the range explicitly, rather than swapping the endpoints, like reversed(range(0, 6)) or range(0, 6)[::-1] in Python (vs. range(5, -1, -1)). Of course, this isn't nearly as syntactically nice as range(5, 0)... but that comes with its own problems: automatically iterating backwards in ranges has been really annoying every time I've encountered it (mainly in R), requiring extra care and a pile of extra ifs and/or mins & maxs.
Right-Exclusive ranges are superior to inclusive ranges because they can represent empty ranges, which removes one rare degenerate case and not overemphasizing it.
Another benefit: creating a series of right-exclusive ranges from a sequence [a, b, c, f] is easy: [a, b) [b, c) [c, f)
When I see "0..<n" I know the range is exclusive of n.