debugswiftMinor
Fixed-length Sequences in Swift 2.2
Viewed 0 times
fixedlengthsequencesswift
Problem
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?
At this point we can do very little:
To express larger natural numbers, however, we must combine
... which allows us to count in types (try it in the playground):
The point is that we can now make use of
```
final class Box {
var value: T
init(_ value: T) { self.value = value }
}
struct Vector : SequenceType {
private let array: [Box]
var elements: [Element] { return ar
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 // 2To 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 __ : CountType { // these are two underscores `_`x2 (then, below, three, four...)
static var count: Int {
return T.value(place: 1) + U.value(place: 0)
}
}
enum ___ : CountType {
static var count: Int {
return H.value(place: 2) + T.value(place: 1) + U.value(place: 0)
}
}
enum ____ : 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):
____.count // 2016The point is that we can now make use of
CountTypes to define sequence length:```
final class Box {
var value: T
init(_ value: T) { self.value = value }
}
struct Vector : SequenceType {
private let array: [Box]
var elements: [Element] { return ar
Solution
The
by
then it suffices to import
But I would get rid of the floating point conversion and implement it as (taking the hundreds as an example):
This is simple and straightforward, and easy to read. This makes the
the math library call to the
As a consequence, the entire code compiles with a pure
without additional imports.
(This is my major point.) The boxing of the array elements into
a
First,
mutable even for a constant:
Second, copying a vector copies references to the elements,
which is not to be expected for a value type:
Here, modifying the elements of
I would simply omit the boxing and store the elements just as an
array. Modifying the elements is then done via a subscript setter:
Using a
computed
Now
By adopting the
printing a vector produces nicer output (the angle brackets are just
an example to disambiguate the output from an array):
To create (constant) vectors, an
element mapping" might be useful:
Example:
This would also come in handy when you start to define operations
on the vector type, e.g. an addition of integer vectors:
Initialization from an array or array literal might also be
convenient:
Example:
The disadvantage here is that the compiler cannot check the
dimension.
Vector type is not related to graphics, so importingCoreGraphics seems "unnatural" here. If you replace CGFloatby
Float or Double extension DigitType {
static func value(place place: Int) -> Int {
return count * Int(pow(10, Double(place)))
}
}then it suffices to import
Foundation (or Swift + Darwin).But I would get rid of the floating point conversion and implement it as (taking the hundreds as an example):
enum ___ : CountType {
static var count: Int {
return H.count * 100 + T.count * 10 + U.count
}
}This is simple and straightforward, and easy to read. This makes the
value(place place:) with its floating point conversion, and thethe math library call to the
pow() function obsolete.As a consequence, the entire code compiles with a pure
import Swiftwithout additional imports.
(This is my major point.) The boxing of the array elements into
a
class seems problematic to me.First,
struct Vector is a value type, but its elements aremutable even for a constant:
let vecA = Vector(0)
vecA[0].value = 111 // This should not compile!Second, copying a vector copies references to the elements,
which is not to be expected for a value type:
let vecA = Vector(0)
vecA[0].value = 111
let vecB = vecA
vecB[0].value = 222
print(vecA[0].value) // Output: 222. Expected output: 111.Here, modifying the elements of
vecB modifies the elements ofvecA as well.I would simply omit the boxing and store the elements just as an
array. Modifying the elements is then done via a subscript setter:
struct Vector {
private var array: [Element]
init(_ repeatedValue: Element) {
array = [Element](count: Dimension.count, repeatedValue: repeatedValue)
}
var elements: [Element] {
return array
}
subscript(index: Int) -> Element {
get {
return array[index]
}
set(newValue) {
array[index] = newValue
}
}
func generate() -> Array.Generator {
return array.generate()
}
}Using a
private var array for the element storage and a computed
elements property still makes sense for encapsulation.Now
Vector behaves like a proper value type:var vecA = Vector(0)
vecA[0] = 111
var vecB = vecA
vecB[0] = 222
print(vecA[0]) // Output: 111By adopting the
CustomStringConvertible protocol, extension Vector : CustomStringConvertible {
var description: String {
return ""
}
}printing a vector produces nicer output (the angle brackets are just
an example to disambiguate the output from an array):
print(vecA)
// Output:
// instead of Vector(array: [111, 0, 0])To create (constant) vectors, an
init method taking a "index toelement mapping" might be useful:
extension Vector {
init(elementForIndex: Int -> Element) {
array = (0 ..< Dimension.count).map(elementForIndex)
}
}Example:
let vecC = Vector(elementForIndex: { 2 * $0 })
print(vecC) // This would also come in handy when you start to define operations
on the vector type, e.g. an addition of integer vectors:
func +
(lhs: Vector, rhs: Vector)
-> Vector {
return Vector(elementForIndex: { lhs[$0] + rhs[$0]})
}Initialization from an array or array literal might also be
convenient:
extension Vector {
init(elements: [Element]) {
precondition(elements.count == Dimension.count)
array = elements
}
}
extension Vector: ArrayLiteralConvertible {
init(arrayLiteral elements: Element...) {
self.init(elements: elements)
}
}Example:
let vecD: Vector = [ 1.0, 2.0, 3.0, 4.0, 5.0 ]
print(vecD) // The disadvantage here is that the compiler cannot check the
dimension.
Code Snippets
extension DigitType {
static func value(place place: Int) -> Int {
return count * Int(pow(10, Double(place)))
}
}enum ___<H: DigitType, T: DigitType, U: DigitType> : CountType {
static var count: Int {
return H.count * 100 + T.count * 10 + U.count
}
}import Swiftlet vecA = Vector<_3, Int>(0)
vecA[0].value = 111 // This should not compile!let vecA = Vector<_3, Int>(0)
vecA[0].value = 111
let vecB = vecA
vecB[0].value = 222
print(vecA[0].value) // Output: 222. Expected output: 111.Context
StackExchange Code Review Q#124797, answer score: 2
Revisions (0)
No revisions yet.