2

I'd like to create an array in Swift that is immutable, but can be completely replaced, in the style of functional programming.

I understand that I can create them in this way:

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

I want the properties of immutability, but to still be able to change which immutable array my variable points to. For example, I would like this to fail:

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

But I would like this to succeed:

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

Similar, but not answers:

Community
  • 1
  • 1
SimplGy
  • 20,079
  • 15
  • 107
  • 144

3 Answers3

2

I don't know of a way to achieve that with the built-in Array type. What you could do is to define a custom ConstantArray type which contains an array and has only immutable accessor methods (which are all forwarded to the contained array):

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 }
}

Of course this is a different type, but by implementing the SequenceType and CollectionType it can be used quite similarly. Example:

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)
}

For the mapping, you can use the global map() function (or the map() method of Array) and convert the result back to a ConstantArray:

myArr = ConstantArray(map(myArr) { $0 * 2 })
myArr = ConstantArray(myArr.array.map { $0 * 2 })

Or you implement a map() method for ConstantArray:

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

and use it as

myArr = myArr.map { 2 * $0 }

In the same manner, other methods like filter() and sorted() can be implemented for ConstantArray.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Oh, this is clever. Must be how functional libraries are implemented in swift. – SimplGy May 31 '15 at 09:42
  • This is the cleanest Swift solution but I hate it so much. We are back at Java's `Collections.unmodifiableList` again. – Sulthan May 31 '15 at 09:45
1

In this case, let can't be used because it never changes throughout runtime.

I personally prefer to use var and declare a NSArray. You can reassign the variable to point to another immutable NSArray but you can't modify the contents of the NSArray.

The pitfall is that this produces runtime crashes if you manage to cast it to an NSMutableArray.

Schemetrical
  • 5,506
  • 2
  • 26
  • 43
  • Do futher operations on an immutable NSArray assigned to a mutable variable return immutable NSArrays? eg: `var arr = NSArray(1,2,3); arr = arr.map { $0 * 2 }`. Is arr now mutable, or immutable? – SimplGy May 31 '15 at 07:41
1

What you want doesn't make sense for Array, because it is a value type with value semantics. The value of the variable is the array -- so the ability to change the variable and the ability to change the array are in a way the same thing.

Semantically there is no difference between:

myArr.append(42)

and something like (made up):

myArr = myArr.newArrayWithAppending(42)

In a way, you can imagine that any "modification" to a value type variable could be implemented as assigning a completely new value to the variable. (It's probably not done that way because it would be inefficient, but from a semantics point of view, there is no reason why it could not be implemented that way.)


The concept of "mutability" only makes sense for reference types, where the variable is a reference that points to the actual object, and the actual object can be shared between multiple references (so that "mutation" through one reference can be seen through another one).

With reference types, you can do what you want, with for example an immutable ImmutableArray class, and then if you have a var variable of type ImmutableArray, then you can construct a whole new immutable array and change the variable to point to the new array, but any particular array object cannot be "changed" as seen from a shared reference.

newacct
  • 119,665
  • 29
  • 163
  • 224
  • This is a really good point. I hadn't considered that arrays (and dictionaries) are a value type in swift. The accomplishes my goal for this question, namely being able to trust that the collection my function is working on will not be mutated behind my back. – SimplGy Jun 02 '15 at 14:59