Swift has (from what I can tell), 5 range operators, corresponding 5 range types:
...0
produces aPartialRangeThrough<Int>
..<0
produces aPartialRangeUpTo<Int>
0..<10
produces aRange<Int>
0...10
produces aClosedRange<Int>
10...
produces aPartialRangeFrom<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?