Utilizing functional programming to filter nil values in Swift


No matter what realm of programming you choose, you’ll have to be comfortable handling information that is there, as well as the information that is not there. By that I mean, nil, null, nullptr, etc. All of these have one thing in common and that’s nothing…well, at least what they represent is a state defining that there is nothing there!

In this weekend’s Swifty Saturdays, I’m going to be showing you a nice way to use functional programming (yes the classic buzzword that every coding guru tosses around these days) to filter out optional values in an array in swift. Arrays are the most common data structure that you will use when fetching data from APIs and storing the results; so, it’s nice to have tricks up your sleeve when optional values can arise. Let’s take a look at the higher-order function that we’ll be using to filter out these nil values!

Compact Map

The higher-order function that we’ll be using to achieve this awesome nil filtering functionality is Compact Map. Compact Map is essentially a specialty version of the classic higher-order function map

Map simply takes some iterable data structures values and iterates over each value, x, and performs some transformation f(x) to produce y. This is just like any function that you’ve used in math. We’re simply applying some transformation to the input and receiving a mapped output to a different domain of values. Map, however, does nothing for nil values. This is where compactMap comes in

compactMap, by definition, performs some transformation on the values within the iterable data structure and then filters out the results values that are nil after the transformation. All we need to do is give compactMap a closure that takes an element in the iterable sequence and returns an optional value! So, in the example above, if y <- nil after applying f to x, then y would not be included in the resulting array after the compact map.  Which is exactly what we want since we don’t want to deal with nil values!

What if I don’t want to perform a transformation?

If you’re a very thorough reader, you would’ve seen that for compactMap to work, we have to apply some transformation f to x since we’re in fact, using a specialty version of the map function. 

What happens if I don’t want to transform the data, Brady?

To that, I say, no worries! We can simply map x back to itself! For instance, you could have, f(x) = x which in English is simply returning x. So, when we call the compactMap function, we will simply just return the value using $0. compactMap will then take care of removing the nil values from the resulting array for us!

Enough talk, let’s see it in action

For this example, we’re going to replicate a scenario where we just fetched data from a baseball website. Each player is represented by the following struct:

struct BaseballPlayer: Codable {
    var name: String
    var number: String
    var battingAverage: Double?

You can see that each baseball player has a name, number, and an optional batting average (because some players might have not batted in a game before). Our goal is to take the players returned from the API and only display the players that have batted in a game (i.e. the players that have a non-nil batting average).

Let’s think through this before getting too deep into the code. Since we want to get the players that have a batting average, we want to map all players to nil that don’t have a batting average and keep all the players with a batting average as the same struct. This logic is then translated to the following map function, f(x)= x.battingAverage != nil ? x : nil By using this transformation, we will get rid of all players without a batting average, because all of the nil values will get filtered out during the compacting of the resulting array.

let playersFetchedFromApi: [BaseballPlayer] = [
    .init(name: "Bryce Harper", number: "1", battingAverage: 0.1),
    .init(name: "Mike Trout", number: "2", battingAverage: 0.3),
    .init(name: "Brady Murphy", number: "3"),
    .init(name: "Barry Bonds", number: "4", battingAverage: 0.31),
    .init(name: "Chipper Jacobs", number: "5a"),

let playersWithBattingAverage = playersFetchedFromApi.compactMap { $0.battingAverage != nil ? $0 : nil }

If you print out playersWithBattingAverage, you will see that the players that are left in the array are Bryce Harper, Mike Trout, and Barry Bonds! Brady Murphy and Chipper Jacobs are nowhere to be found which is exactly what we want because we all know if I stepped up to the plate it would be ugly!

Protecting Against Casting

Let’s take one more look at the power of compactMap. Let’s say we want to get all of the numbers from the players in the form of an Int. To do this, we could simply add a map { Int($0.number) } to the array that we parsed from the API and we’ll be golden pony boy! Right? WRONG!

You need to be defensive in your code. Who knows what that API is really giving to us. The players’ number string could be formatted wrong or an extra character could’ve slipped in during the data entry. So, if we tried to cast that player’s number to an Int, it would return nil!

We need to protect against a potential mapping creating a nil value. To do this, we can utilize our trusty compactMap function to filter out the typecasts that go wrong! Let’s see it in an example.

let playersNumbers = playersFetchedFromApi.compactMap { Int($0.number)}

We can then print out to check to make sure that the only numbers that are in the array are 1, 2, 3, and 4. Chipper will not be included since “5a” can’t map to a valid int.

Thanks for reading!

Thanks for taking the time to read this weeks’ Swifty Saturdays! I hope you learned a little bit about how to use compactMap in your code to protect against nil values in a clean functional programming paradigm. No more clunky for-loops within your code to filter out nil values.

If you like my posts, subscribe to the Unwrapped Bytes email list. I send out updates on new articles to keep you informed with the latest bytes 💽

Hope you have a great weekend 🥂

p.s. Shoutout Tyler Nix for the awesome photo. Check him out here

Success! You're on the list!

Leave A Comment