There's one syntax for half-open ranges and another syntax for closed ranges.
You can index an array any number of different ways: "get me the 5th element". "Get me the element three back from the end". "Get me elements 5 through 8 (inclusive)". "Get me 0 through 6 (exclusive)". "Get me the 3-length subarray starting at index 2".
A hash table returns nil by default when you access a key that isn't stored. But you can change the default value as a property of the table itself. Users now don't need to remember to supply their own default value when they're looking in the hash table. Even more, you can specify a function that gets called when a nonexistent key is accessed, to do whatever is appropriate.
All of those are great things. It's easy to translate your algorithm that says "take a stretch of three elements starting from i" into an access that looks like array[i..i+2]. But the semantics of your algorithm start slipping away as you make adjustments like that. If what you're doing is taking substretches of 3 elements each, there's a gain from being able to express it as array[i, 3], as semantically distinct from taking a substretch between two independent bounds that happen to be i and i+2.
But the flip side of this is that if you're not regularly immersed in the language, it's really easy to lose track of precisely which bit of syntax does what.