Swift has a nice syntax for grabbing subarrays:

let array = [1, 2, 3, 4, 5, 6]
let subarray = array[0...2] // Yields [1, 2, 3]

However, subarray here isn’t an Array like you may expect, it is an ArraySlice. For the most part, this doesn’t matter because ArraySlice and Array have the exact same interfaces and eventually get you where you want to go. Want to take the first three items from some array and add them to the end of another array? (Types added for clarity.)

let array1: Array<Int> = [1, 2, 3, 4, 5, 6]
let slice: ArraySlice<Int> = array1[0...2]

let array2: Array<Int> = [8, 9, 10]
let combined: Array<Int> = array2 + slice // [8, 9, 10, 1, 2, 3]

There was an intermediate ArraySlice, but by the time we got done, the ArraySlice was gone and we’re left with an Array.

Want to get the nth item out of a previously sliced Array?

let a: Array<Int> = [8, 9, 10]
let b: ArraySlice<Int> = a[0...1]

let result = b[b.startIndex] // 8

In these examples, it is clear that an ArraySlice is effectively an Array. It is used as an intermediate value during some operation, where you end up with another Array or some element of that Array and then the slice is discarded. But, things get weird when you start passing Array and ArraySlices to other functions, and the Swift compiler gets a bit suspicious:

func sum(array: Array<Int>) -> Int {
    return array.reduce(0, combine: { (acc, i) in 
        return acc + i
    })
}

let array: Array<Int> = [8, 9, 10]
let slice: ArraySlice<Int> = array[0...1]

sum(array) // 27
sum(slice) // Compiler error!

ArraySlice may walk and talk like an Array, but it isn’t an Array – the Swift compiler doesn’t really give a shit that they’re effectively the same thing.

This is a really good example of a key difference between Objective-C and Swift. If we wanted to slice arrays in Objective-C, we’d probably implement the slice as part of the NSArray class cluster.

Related: How to Keep Your Style Code Clean in Swift

New call-to-action

To refresh your memory, a class cluster is a class that is really just an interface. The real implementation of that class is hidden to you and an instance may be one of several different types that implement the cluster interface. For example, when you create an NSArray, the real object you get is an instance of __NSArrayI. It’s okay though, because __NSArrayI does everything an NSArray does, so you can treat it as such.

With an __NSArraySlice in the NSArray class cluster, we could do something like this:

NSArray *array = @[@1, @2];
NSArray *slice = [array sliceWithRange:NSMakeRange(0, 1)];

Using your imagination here, slice is actually __NSArraySlice, but it’s in the NSArray class cluster, so we don’t care. We go on treating it as an NSArray and unless we go poking around in the debugger, we’ll never know. Passing a slice to a method that takes an NSArray is no big deal.

Swift, on the other hand, uses a composition of many protocols like Indexable and CollectionType to define what it is to be ‘Array-like’. In other words, being Array-like in Swift is a bottom-up approach where Objective-C takes a top-down approach.

If we want a method like sum to handle both Array and ArraySlices in Swift, we need to think in terms of this bottom-up approach. The sum function takes one argument and calls the instance method reduce on that argument. Both Array and ArraySlice implement reduce, so this is all possible, but the Swift compiler isn’t going to let you off that easy. And that’s actually a good thing.

Array and ArraySlice have a reduce method because one of those protocols they are a composition of is SequenceType. The SequenceType protocol says a type has to implement a handful of methods – like map, filter, generate, forEach, and so on – and then it gets to be a SequenceType. And when a type is a SequenceType, it gets all kinds of free goodies that are implemented as extensions to SequenceType – like the reduce method!

So, the Swift compiler isn’t cool with you treating an ArraySlice as an Array, but that’s because it wants you to be more clear about your intentions. Since sum is doing SequenceType things, it should accept a SequenceType argument.

func sum<T: SequenceType where T.Generator.Element == Int>(list: T) -> Int {
    return list.reduce(0, combine: { (acc, e) in
        return acc + e
    })
}

The sum method can now accept both Array and ArraySlice – or any other SequenceType whose elements are Int. Not only does this work (that part is good), but it is a more explicit and accurate representation of our intentions for sum. In other words, Objective-C is a relationship built on lies. Both of you are happy to call things NSArray, but deep down you both know things aren’t  NSArray. Swift doesn’t put up with that nonsense.

Joe Conway

Founder at Stable Kernel

Leave a Reply

Your email address will not be published. Required fields are marked *