不可变的 `var` 数组

Immutable `var` array

我想在 Swift 中创建一个数组,它是不可变的,但可以完全替换,采用函数式编程的风格。

我知道我可以通过这种方式创建它们:

var mutable   = [1,2,3] // can change reference, add, remove, etc
let immutable = [4,5,6] // cannot replace reference or add, remove, etc

我想要不变性的属性,但仍然能够更改我的变量指向的不可变数组。例如,我希望这个失败:

myArr.append(42) // fails. immutable array

但我希望这能成功:

// Desired behavior:
// Multiply each element in the immutable array by 2
// Return a new immutable array, assigned to the old var
immutable = immutable.map { [=12=] * 2 } // ERROR: can't replace immutable ref

类似,但不是答案:

在这种情况下,let 不能使用,因为它在整个运行时都不会改变。

我个人更喜欢使用 var 并声明一个 NSArray。您可以重新分配变量以指向另一个不可变 NSArray,但您不能修改 NSArray 的内容。

陷阱是,如果您设法将其转换为 NSMutableArray

,则会导致运行时崩溃

我不知道有什么方法可以使用内置的 Array 类型来实现。 你可以做的是定义一个自定义 ConstantArray 类型 其中 包含 一个数组并且只有不可变的访问器方法 (全部转发到包含的数组):

struct ConstantArray<T> {
    private let elements : [T]

    // Create with an empty array.
    init() {
        elements = []
    }

    // Create with some sequence, e.g.
    //     let constArray = ConstantArray([1, 2, 3])
    init<S : SequenceType where S.Generator.Element == T>(_ s: S) {
        elements = Array(s)
    }

    // Read-only computed property to get the elements.
    var array : [T]  {
        return elements
    }

    // Some properties you might want to implement:
    var count: Int { return elements.count }
    var isEmpty: Bool { return elements.isEmpty }
    var first: T? { return elements.first }
    var last: T? { return elements.last }

}

extension ConstantArray : ArrayLiteralConvertible {
    // Create with an array literal, e.g.
    //     let constArray : ConstantArray = [1, 2, 3]
    init(arrayLiteral array: T...) {
        elements = array
    }
}

// Implement sequence prototol, e.g. for enumeration:
//     for elem in myConstantArray { ... }
extension ConstantArray : SequenceType {
    func generate() -> IndexingGenerator<[T]> {
        return elements.generate()
    }
}

// Implement read-only subscripting, e.g.
//     let elem = myConstantArray[1]
extension ConstantArray: CollectionType {
    var startIndex : Int { return elements.startIndex }
    var endIndex : Int { return elements.endIndex }

    subscript(index: Int) -> T {
        return elements[index]
    }
}

extension ConstantArray : Printable {
    var description: String { return elements.description }
}

当然这是不同的类型,但是通过实施 SequenceTypeCollectionType 的用法非常相似。 示例:

var myArr : ConstantArray = [1, 2, 3]

myArr.append(42)   // error: 'ConstantArray<Int>' does not have a member named 'append'
myArr[2] = 3       // error: cannot assign to the result of this expression
myArr.array[2] = 3 // error: cannot assign to the result of this expression

myArr = [4, 5, 6] // succeeds
println(myArr) // [4, 5, 6]
for elem in myArr {
    println(elem)
}

对于映射,可以使用全局map()函数 (或 Arraymap() 方法)并将结果转换回 ConstantArray:

myArr = ConstantArray(map(myArr) { [=12=] * 2 })
myArr = ConstantArray(myArr.array.map { [=12=] * 2 })

或者您为 ConstantArray 实现一个 map() 方法:

extension ConstantArray {
    func map<U>(transform: (T) -> U) -> ConstantArray<U> {
        return ConstantArray<U>(elements.map(transform))
    }
}

并将其用作

myArr = myArr.map { 2 * [=14=] }

以同样的方式,其他方法如filter()sorted() 可以为 ConstantArray.

实施

你想要的对Array没有意义,因为它是一个具有值语义的值类型。变量的值是数组——所以改变变量的能力和改变数组的能力在某种程度上是一样的。

语义上没有区别:

myArr.append(42)

和类似的东西(编造的):

myArr = myArr.newArrayWithAppending(42)

在某种程度上,您可以想象 any "modification" 到值类型变量可以实现为为变量分配一个全新的值。 (可能不是那样做,因为效率低下,但是从语义的角度来看,没有理由不能那样实现。)


"mutability"的概念只对引用类型有意义,其中变量是指向实际对象的引用,实际对象可以在多个引用之间共享(因此"mutation" 通过一个引用可以通过另一个看到)。

使用引用类型,你可以做你想做的事,例如一个不可变的 ImmutableArray class,然后如果你有一个 var 类型的变量 ImmutableArray,那么你可以构建一个全新的不可变数组并将变量更改为指向新数组,但是任何特定的数组对象都不能是 "changed" 从共享引用中看到的。