Expressiveness and extensions
Published: 2021-02-21 23:32:11

# Introduction
Swift is a beautiful and modern language. It's said to be **expressive**. As I understand expressiveness, it's the ability to express an idea using a medium. Swift code is expressive, because you can write code in Swift and once you get to it, you can express your ideas with minimal boilerplate in most situations.

There are many features in Swift that helps us in doing this. Today, we'll discuss a bit about extensions.

Considering we have a struct `Person`:

```swift
struct Person {
    let name: String
    let age: Int
}
```

You could write an extension 

```swift
extension Person {
    var greeting: String { 
        return "Hi, I'm \(name) and am \(age) years old" 
    }
}
```

Pretty simple. Extensions come handy in many situations, but it's a good idea not to overuse them. Code too expressive is not expressive code. It becomes confusing.

# Example: UIBarButtonItem
Suppose we're configuring some `UIBarButtonItem` so we can add them to `navigationItem`.

A good idea when you're trying to make this code expressive is by extending `UIBarButtonItem`:

```swift
extension UIBarButtonItem {
    static func done(target: Any?, action: Selector?) -> UIBarButtonItem {
        return UIBarButtonItem(
            image: UIImage(named: "Done"),
            style: .done,
            target: target,
            action: action
        )
    }
    
    static func favorite(target: Any?, action: Selector?) -> UIBarButtonItem {
        return UIBarButtonItem(
            image: UIImage(named: "Favorite"),
            style: .done,
            target: target,
            action: action
        )
    }
}
```

Take this example. We're extending `UIBarButtonItem`, so that when we need to configure buttons in the navigation bar, we can do this:

```swift
navigationItem.leftBarButtonItems = [
    .done(target: self, action: #selector(doneButtonPressed)),
    .favorite(target: self, action: #selector(doneButtonPressed)),
]
```

# Example: Array
Now, this has worked and provides code that is more understandable and more expressive. But we can do better. Imagine we need to add more buttons to the navigation bar, in both sides of it, BUT only if the device is in landscape. A first attempt to do that would be:

```swift
func configureNavigationBar() {
    navigationItem.leftBarButtonItems = [
        .done(target: self, action: #selector(doneButtonPressed)),
        .favorite(target: self, action: #selector(doneButtonPressed)),
    ]
    
    if UIDevice.current.orientation == .landscapeLeft
        || UIDevice.current.orientation == .landscapeRight {
        
        navigationItem.leftBarButtonItems?.append(contentsOf: [
            .profile(target: self, action: #selector(doneButtonPressed)),
            .posts(target: self, action: #selector(doneButtonPressed)),
        ])
    }
}
```

This would work fine. If the device is in landscape, we append more items to the `leftBarButtonItems` property.
We can make the orientation checking a bit cleaner by extending `UIDevice`:

```swift
extension UIDevice {
    var isLandscape: Bool {
        return orientation == .landscapeLeft
            || orientation == .landscapeRight
    }
}

//...

func configureNavigationBar() {
    navigationItem.leftBarButtonItems = [
        .done(target: self, action: #selector(doneButtonPressed)),
        .favorite(target: self, action: #selector(doneButtonPressed)),
    ]
    
    if UIDevice.current.isLandscape {
        
        navigationItem.leftBarButtonItems?.append(contentsOf: [
            .profile(target: self, action: #selector(doneButtonPressed)),
            .posts(target: self, action: #selector(doneButtonPressed)),
        ])
    }
}
```

However, there is an array extension that could make this much more cleaner:

```swift
extension Array {
    func appending(_ element: @autoclosure () -> Element, if condition: Bool = true) -> [Element] {
        if condition {
            var copy = self
            copy.append(element())
            return copy
        } else {
            return self
        }
    }
}
```

There are some interesting things in this code fragment. That `@autoclosure` thing means that the `Element` will be put in memory and evaluated only if needed (when its auto-generated closure is executed). The `condition` argument is `true`, by default, meaning that it's completely optional.

This code will allow us to make the previous `configureNavigationBar` function much cleaner. See:

```swift
func configureNavigationBar() {
    navigationItem.leftBarButtonItems = []
        .appending(.done(target: self, action: #selector(doneButtonPressed)))
        .appending(.favorite(target: self, action: #selector(doneButtonPressed)))
        .appending(.profile(target: self, action: #selector(doneButtonPressed)), if: UIDevice.current.isLandscape)
        .appending(.posts(target: self, action: #selector(doneButtonPressed)), if: UIDevice.current.isLandscape)
}
```

The only problem that I see is that we're making unnecessary copies of the array. We're improving the code expressiveness at expenses of performance. Of course, in this example, the performance is minimal and I don't think it's something to consider, but anyway, it's good to keep that in mind. 

The last improvement I'll add to it is this:

```swift
extension Array {
    func appending(_ elements: @autoclosure () -> [Element], if condition: Bool = true) -> [Element] {
        if condition {
            var copy = self
            copy.append(contentsOf: elements())
            return copy
        } else {
            return self
        }
    }
}
```

If we put all the appending to the same condition, the result would be that the array will be cloned many less times:

```swift
func configureNavigationBar() {
    navigationItem.leftBarButtonItems = [
            .done(target: self, action: #selector(doneButtonPressed)),
            .favorite(target: self, action: #selector(doneButtonPressed))
        ]
        .appending([
            .profile(target: self, action: #selector(doneButtonPressed)),
            .posts(target: self, action: #selector(doneButtonPressed))
        ], if: UIDevice.current.isLandscape)
}
```

# To sum up

We've seen a couple of examples where creating extensions could help us to create code that is more understandable at a first glance. It's ok and it's good to add extensions for this, but as always, this is a discipline of balancing tradeoffs. Too many extensions will result in a code that is not expressive at all, or a code that doesn't look like traditional Swift/iOS code. This is not something we want. We want our code to be understandable and minimize the time somebody will need to fully grasp it.