Avoiding Swift's [Any] by using functional patterns
In my previous post: “Unexpected behaviour with Swift’s [Any]”, I declared a nested array of integers as:
let a : [Any] = [1,2,[3],[4,[5,6]],[[7]], 8]
Using Any
feels like a code smell; by using Any
I’m effectively saying, “ignore all the type-checking the compiler performs, instead I’ll rely on my own knowledge of the types”. Experience shows I am not as knowledgable as the compiler, especially after some time has elapsed and I’m trying to add a new feature… That said, my intent wasn’t to suppress the compiler’s type checking, I simply didn’t know how to express a heterogeneous array without resorting to Any
. However I’ve been learning Haskell where the way to express such heterogeneous arrays is by using a sum type. In Swift sum types are defined using enum
s. Let’s start by defining an enum, which will hold either Int
or Array
values:
enum IntOrArray {
case intValue(Int)
case arrayValue([IntOrArray])
}
the array:
let a : [Any] = [1,2,[3],[4,[5,6]],[[7]], 8]
can then be expressed in an equivalent type-safe way as:
let a : [IntOrArray] = [.intValue(1), .intValue(2), .arrayValue([.intValue(4), .arrayValue([.intValue(5), .intValue(6)])]), .arrayValue([.arrayValue([.intValue(7)])]), .intValue(8)]
A process
function is easy to write without resorting to the horrible hacks documented my previous post:
func process(anArray : [IntOrArray]) {
for element in anArray {
switch(element) {
case .intValue(let intValue):
print("intValue = \(intValue)")
case .arrayValue(let arrayValue):
process(arrayValue)
}
}
}
process(a)
It’s also fun to write a flatten function without any mutable variables:
func flatten(anArray : [IntOrArray]) -> [Int] {
return anArray.flatMap (flattenAnElement)
}
func flattenAnElement (element : IntOrArray) -> [Int] {
switch element {
case .intValue(let intValue):
return [intValue]
case .arrayValue(let arrayValue):
return flatten(arrayValue)
}
}
flatten(a) // [1,2,4,5,6,7,8]
Swift’s enumerations with associated values are a language feature which can often be used instead of class hierarchies, they benefit from being value types and frequently express the intent of the code much more succinctly than a class based alternative.
A playground containing the code in this post can be downloaded here