Swift has (from what I can tell), 5 range operators, corresponding 5 range types:
...0 produces a PartialRangeThrough<Int>
..<0 produces a PartialRangeUpTo<Int>
0..<10 produces a Range<Int>
0...10 produces a ClosedRange<Int>
10... produces a PartialRangeFrom<Int>
Switch statements can handle heterogeneous case types just fine, e.g.:
switch 123 {
case ...0: print("...0")
case ..<0: print("..<0")
case 0..<10: print("0..<10")
case 0...10: print("0...10")
case 10...: print("10...")
default: break
}
However, making collections of heterogeneous range types is really hard, because they're mutually incompatible types. They're all subtypes of RangeExpression, but that protocol has an associated type (Bound), so it can't be used in a collection with first cooking up an AnyRange type eraser, and wrapping all the values in it. Consider this example, of trying to encode Canada's federal income tax margins:
postfix operator %
postfix func % (d: Double) -> Double { return d / 100.0 }
let federalMargins: KeyValuePairs<ClosedRange<Int>, Double> = [
0 ... 47_630: 15.0% ,
47_630 ... 95_259: 20.5%,
95_259 ... 147_667: 26.0%,
147_667 ... 210_371: 29.0%,
210_371 ... Int.max: 33.0%,
]
The last line can't be 210_371..., because you get a type error:
error: cannot convert value of type 'PartialRangeFrom' to expected dictionary key type 'ClosedRange'
I understand that the current implementation has some pleasant performance upsides. The open vs. closed characteristic is encoded in the type, so each partial range instance only needs to store one T property, and each "normal" range only needs to store two T properties, without needing any extra space (and associated necessary padding) for storing flags like isOpen.
However, there are usability issues that I can't find any workarounds for. What would you guys recommend here? Am I missing something, or does this area of Swift need some TLC?