Swift - Leveraging the power of first-class functions

Functions, in Swift, are first-class citizens that can be passed as arguments, stored in properties or returned from functions.

When dealing with Swift built-in functions such as map and forEach, one often ends up using the trailing closure syntax to provide with the required behavior.

let stackView = UIStackView()
let views = [UIView(), UIView(), UIView()]

views.forEach { view in
     stackView.addArrangedSubview(view)
 }
 

Or, using the $ syntax:

views.forEach { 
     stackView.addArrangedSubview($0)
 }

While the above implementation is quite clear, let’s see how we can leverage the power of first-class functions and improve code readability.

— Functions

First, let’s take a closer look at the forEach expected function argument when looping over our views.

(body: (UIView) throws -> Void)

It expects a function that takes a UIView as as argument and returns Void.

Now, let’s look at the addArrangedSubview function from the Swift documentation.

func addArrangedSubview(_ view: UIView) -> Void

Interestingly enough, the addArrangedSubview method gives us exactly what we need to meet the forEach argument expectation giving us the ability to directly pass the addArrangedSubview into the forEach function.

views.forEach(stackView.addArrangedSubview(_:))

The wildcard function argument is not necessary since the compiler can figure on its own what method it refers to.

views.forEach(stackView.addArrangedSubview)

It provides with a shorter syntax while enhancing readability on a code that could almost be read as plain English text.

Let’s wrap it up by removing our subviews from our UIStackView.

views.forEach(stackView.removeArrangedSubview)

— Initializers

Since initializers are also functions that takes a certain amount of arguments and returns the object they’re defined in, it becomes quite convenient to use such declarative syntax to write more legible code.

let imageStrNames = ["flower", "car", "boat"]
        
imageStrNames
    .map(UIImage.init(named:))
    .map(UIImageView.init)
    .forEach(stackView.addArrangedSubview)

Notice how in the first map, we explicitly specify the argument label to give the compiler a hint on which init method we want to use (since a UIImage has several initializers, it could otherwise lead to misbehaviors).

struct Student {
    let name: String
}

let studentNames = ["John", "Bob", "Peter"]
let students = studentNames.map(Student.init(name:))
 

Again, the compiler is smart enough to use the proper initializer so the explicit init argument name is not required.

let students = studentNames.map(Student.init)