The following is an implementation of fixed-length sequences that makes very exotic (and hopefully fun) use of Swift 2.2 types. The question is what exactly is the cost of having the type checker guaranteeing sequence length in this way on safety? performance? readability?
protocol CountType {
static var count: Int { get }
}
protocol DigitType : CountType {}
enum _0 : DigitType { static let count = 0 }
enum _1 : DigitType { static let count = 1 }
enum _2 : DigitType { static let count = 2 }
enum _3 : DigitType { static let count = 3 }
enum _4 : DigitType { static let count = 4 }
enum _5 : DigitType { static let count = 5 }
enum _6 : DigitType { static let count = 6 }
enum _7 : DigitType { static let count = 7 }
enum _8 : DigitType { static let count = 8 }
enum _9 : DigitType { static let count = 9 }
At this point we can do very little:
_4.count // 4
_2.count // 2
To express larger natural numbers, however, we must combine DigitTypes
:
import CoreGraphics // TODO: wean off (implement `pow`)
extension DigitType {
static func value(place place: Int) -> Int {
return count * Int(pow(10, CGFloat(place)))
}
}
enum __<T:DigitType, U:DigitType> : CountType { // these are two underscores `_`x2 (then, below, three, four...)
static var count: Int {
return T.value(place: 1) + U.value(place: 0)
}
}
enum ___<H:DigitType, T:DigitType, U:DigitType> : CountType {
static var count: Int {
return H.value(place: 2) + T.value(place: 1) + U.value(place: 0)
}
}
enum ____<Th:DigitType, H:DigitType, T:DigitType, U:DigitType> : CountType {
static var count: Int {
return Th.value(place: 3) + H.value(place: 2) + T.value(place: 1) + U.value(place: 0)
}
}
... which allows us to count in types (try it in the playground):
____<_2,_0,_1,_6>.count // 2016
The point is that we can now make use of CountType
s to define sequence length:
final class Box<T> {
var value: T
init(_ value: T) { self.value = value }
}
struct Vector<Dimension: CountType, Element> : SequenceType {
private let array: [Box<Element>]
var elements: [Element] { return array.map{ $0.value } }
subscript(index: Int) -> Box<Element> { return array[index] }
init(_ repeatedValue: Element) {
array = (0..<Dimension.count).map{ _ in Box(repeatedValue) }
}
func generate() -> Array<Element>.Generator {
return elements.generate()
}
}
// TODO: Matrix<Rows: CountType, Cols: CountType, Element>
We may now work with such Vector
s and Matrix
es with confidence that their dimension can never change:
let v = Vector<__<_4,_2>,Int>(0)
v.elements.count // 42
let w = Vector<_3,Int>(0)
w.elements // [0, 0, 0]
w[2].value = 5
for x in w {
x // 0, 0, 5
}
(hm... the underscore syntax looks prettier in my playground...)
I was considering various alternative naming schemes for the combining enums. So, instead of underscores, we could perhaps emulate the exponent syntax for number literals (like 0.42e2
meaning 0.42 * pow(10, 2)
). This would work out as following:
e4<_2,_0,_1,_6>.count // 2016
// and
let v = Vector<e2<_4,_2>,Int>(0)
The reason I decided against this and similar approaches is that there is now a rather irrelevant number 2
appearing alongside _4,_2
...