HiveBrain v1.2.0
Get Started
← Back to all entries
debugswiftMinor

Fixed-length Sequences in Swift 2.2

Submitted by: @import:stackexchange-codereview··
0
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?

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 __ : 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 // 2016


The 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 Vector type is not related to graphics, so importing
CoreGraphics seems "unnatural" here. If you replace CGFloat
by 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 the
the math library call to the pow() function obsolete.

As a consequence, the entire code compiles with a pure

import Swift


without 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 are
mutable 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 of
vecA 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: 111


By 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 to
element 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 Swift
let 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.