Holocron

Holocron

Playing with FlatMap in Swift

Map

If you have any experience with functional programming, you are probably familiar with the map function.

Let's say you have an array of integers and you want another array that contains the elements from the first array but doubled. You can use Array's map function to do this:

let array = [1, 2, 3, 4, 5] func dbl(element: Int) -> Int { return element * 2 } array.map(dbl) // [2, 4, 6, 8, 10]

FlatMap

I thought I knew how flatmap would work based on that. I assumed it would just flatten a nested array and then act the same as map.

let nestedArray = [[1, 2, 3], [], [4, 5]] let doubleFlattened = nestedArray.flatMap(dbl) // Cannot convert value of type '(Int) -> Int' to // expected argument type '([Int]) -> _'

So as it turns out, the function gets applied to the elements before any flattening occurs. So in nestedArray the elements would be [1, 2, 3], [], and [4, 5] which are arrays of integers not the integers themselves.

To accomplish this, we can just flatten it first and then use map.

let doubleFlattened = nestedArray.flatten().map(dbl) // [2, 4, 6, 8, 10]

But if we wanted, we could muck with the container elements before the flattening using flatMap. For example, we might want to prepend the number of elements in each.

nestedArray.flatMap({[$0.count] + $0}) // [3, 1, 2, 3, 0, 2, 4, 5]

Optional FlatMap Magic

I just referred to the elements of a nestedArray as container elements. It turns out that Optionals count as containers of zero or one items for the purpose of being able to use flatMap with them. Doing so will unwrap the values and leave out empty ones.

let optArray: [Int?] = [1, nil, 2, 3, nil, 4, 5] optArray.flatMap({$0}) // [1, 2, 3, 4, 5]

This means we can do things like try to cast and get only elements of that type

let messyArray: [Any] = [1, "2", 2, false, 3, "four", 4, 5, 6.0] messyArray.flatMap({$0 as? Int}).map(dbl) // [2, 4, 6, 8, 10]

Or only get successfully converted things

let stringArray = ["1", "2", "NaN", "3", "4", "5"] stringArray.flatMap({Int($0)}).map(dbl) // [2, 4, 6, 8, 10]

Neat.