Until recently, I always felt like I was missing something in Swift. Something that makes working with optionals a lot easier. And just a short while ago I found out that the thing I was missing does already exist. I'm talking about the map and flatMap functions of Swift optionals (not the Array map function). Perhaps it's because they're not mentioned in the optionals sections of the Swift guide and because I haven't seen it in any other samples or tutorials. And after asking around I found out some of my fellow Swift programmers also didn't know about it. Since I find it an amazing Swift feature that makes your Swift code often a lot more elegant I'd like to share my experiences with it.

If you didn't know about the map and flatMap functions either you should keep on reading. If you did already know about it, I hope to show some good, real and useful samples of it's usage that perhaps you didn't think about yet.

What do map and flatMap do?

Let me first give you a brief example of what the functions do. If you're already familiar with this, feel free to skip ahead to the examples.

The map function transforms an optional into another type in case it's not nil, and otherwise it just returns nil. It does this by taking a closure as parameter. Here is a very basic example that you can try in a Swift Playground:

var value: Int? = 2
var newValue = value.map { $0 * 2 }
// newValue is now Optional(4)

value = nil
newValue = value.map { $0 * 2 }
// newValue is now nil

At first, this might look odd because we're calling a function on an optional. And don't we always have to unwrap it first? In this case not. That's because the map function is a function of the Optional type and not of the type that is wrapped by the Optional.

The flatMap is pretty much the same as map, except that the return value of the closure in map is not allowed to return nil, while the closure of flatMap can return nil. Let's see another basic example:

var value: Double? = 10
var newValue: Double? = value.flatMap { v in
    if v < 5.0 {
        return nil
    }
    return v / 5.0
}
// newValue is now Optional(2)

newValue = newValue.flatMap { v in
    if v < 5.0 {
        return nil
    }
    return v / 5.0
}
// now it's nil

If we would try to use map instead of flatMap in this case, it would not compile.

When to use it?

In many cases where you use a ternary operator to check if an optional is not nil, and then return some value if it's not nil and otherwise return nil, it's probably better to use one of the map functions. If you recognise the following pattern, you might want to go through your code and make some changes:

var value: Int? = 10
var newValue: Int? = value != nil ? value! + 10 : nil 
// or the other way around:
var otherValue: Int? = value == nil ? nil : value! + 10

The force unwrapping should already indicate that something is not quite right. So instead use the map function shown previously.

To avoid the force unwrapping, you might have used a simple if let or guard statement instead:

func addTen(value: Int?) -> Int? {
  if let value = value {
    return value + 10
  }
  return nil
}

func addTwenty(value: Int?) -> Int? {
  guard let value = value else {
    return nil
  }
  return value + 20
}

This still does exactly the same as the ternary operator and thus is better written with a map function.

Useful real examples of using the map functions

Now let's see some real examples of when you can use the map functions in a smart way that you might not immediately think of. You get the most out of it when you can immediately pass in an existing function that takes the type wrapped by the optional as it's only parameter. In all of the examples below I will first show it without a map function and then again rewritten with a map function.

Date formatting

Without map:

var date: NSDate? = ...
var formatted: String? = date == nil ? nil : NSDateFormatter().stringFromDate(date!)

With map:

var date: NSDate? = ...
var formatted: date.map(NSDateFormatter().stringFromDate)

Segue from cell in UITableView

Without map:

func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
  if let cell = sender as? UITableViewCell, let indexPath = tableView.indexPathForCell(cell) {
    (segue.destinationViewController as! MyViewController).item = items[indexPath.row]
  }
}

With map:

func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
  if let indexPath = (sender as? UITableViewCell).flatMap(tableView.indexPathForCell) {
    (segue.destinationViewController as! MyViewController).item = items[indexPath.row]
  }
}

Values in String literals

Without map:

func ageToString(age: Int?) -> String {
    return age == nil ? "Unknown age" : "She is (age!) years old"
}

With map:

func ageToString(age: Int?) -> String {
    return age.map { "She is ($0) years old" } ?? "Unknown age"
}

(Please note that in the above examples there need to be backslashes before (age!) and ($0) but unfortunately 🙁 that breaks the formatting of WordPress in this post)

Localized Strings

Without map:

let label = UILabel()
func updateLabel(value: String?) {
  if let value = value {
    label.text = String.localizedStringWithFormat(
      NSLocalizedString("value %@", comment: ""), value)
  } else {
    label.text = nil
  }
}

With map:

let label = UILabel()
func updateLabel(value: String?) {
  label.text = value.map { 
    String.localizedStringWithFormat(NSLocalizedString("value %@", comment: ""), $0) 
  }
}

Enum with rawValue from optional with default

Without map:

enum State: String {
    case Default = ""
    case Cancelled = "CANCELLED"

    static func parseState(state: String?) -> State {
        guard let state = state else {
            return .Default
        }
        return State(rawValue: state) ?? .Default
    }
}

With map:

enum State: String {
    case Default = ""
    case Cancelled = "CANCELLED"

    static func parseState(state: String?) -> State {
        return state.flatMap(State.init) ?? .Default
    }
}

Find item in Array

With Item like:

struct Item {
    let identifier: String
    let value: String
}

let items: [Item]

Without map:

func find(identifier: String) -> Item? {
    if let index = items.indexOf({$0.identifier == identifier}) {
        return items[index]
    }
    return nil
}

With map:

func find(identifier: String) -> Item? {
    return items.indexOf({$0.identifier == identifier}).map({items[$0]})
}

Constructing objects with json like dictionaries

With a struct (or class) like:

struct Person {
    let firstName: String
    let lastName: String

    init?(json: [String: AnyObject]) {
        if let firstName = json["firstName"] as? String, let lastName = json["lastName"] as? String {
            self.firstName = firstName
            self.lastName = lastName
            return
        }
        return nil
    }
}

Without map:

func createPerson(json: [String: AnyObject]) -> Person? {
    if let personJson = json["person"] as? [String: AnyObject] {
        return Person(json: personJson)
    }
    return nil
}

With map:

func createPerson(json: [String: AnyObject]) -> Person? {
    return (json["person"] as? [String: AnyObject]).flatMap(Person.init)
}

Conslusion

The map and flatMap functions can be incredibly powerful and make your code more elegant. Hopefully with these examples you'll be able to spot when situations where it will really benefit your code when you use them.

Please let me know in the comments if you have similar smart examples of map and flatMap usages and I will add them to the list.