Blog

Swift Tip: First Class Functions

Since the first public release of Swift (4 years ago in June!), there’s been much debate about whether Swift is a functional language. Rather than engaging in that discussion, we believe it’s more productive to explore useful techniques from functional programming that you can use today.

In Episode 19 of Swift Talk, we looked into replacing NSSortDescriptor with a function:

typealias SortDescriptor<A> = (A, A) -> Bool

With the type alias above we can use SortDescriptor just like a regular type: for parameters, in a property, or as a return type. For example, here’s a function that returns a sort descriptor:

func sortDescriptor<Value>(property: @escaping (Value) -> String) -> SortDescriptor<Value> {
    return { value1, value2 in
        property(value1).localizedCaseInsensitiveCompare(property(value2)) == .orderedAscending
    }
}

Or a function that reverses a sort descriptor:

func reversed<A>(_ sortDescriptor: @escaping SortDescriptor<A>) -> SortDescriptor<A> {
    return { value1, value2 in
        return !sortDescriptor(value1, value2)
    }
}

Note that in the code above, we work with SortDescriptor in almost the same way as we would do with any other type (except for the @escaping annotation). Because it’s a function, though, we can also directly pass it into any of the standard library’s sorting methods.

There is one caveat: it’s not possible to write extensions on function types. So while we would like to write sortDescriptor(property:) as an initializer, we can’t. Likewise, reversed is a top-level function, rather than a method on SortDescriptor.

We could work around that restriction by putting the function into a struct, like so:

struct SortDescriptorAlt<A> {
    let isAscending: (A, A) -> Bool
}

extension SortDescriptorAlt {
    init(property: @escaping (A) -> String) {
        isAscending = { value1, value2 in
            property(value1).localizedCaseInsensitiveCompare(property(value2)) == .orderedAscending
        }
    }
}

The separate type gives us a namespace for methods, properties and initializers, but unlike the SortDescriptor type, we can’t pass it directly to the standard library’s sorting methods. Instead, we have to unwrap it:

let sample = SortDescriptorAlt<Int>(isAscending: >)
[3,1,17,2].sorted(by: sample.isAscending)

In our code, we have used both approaches: sometimes a plain function is simpler, and other times wrapping it up into a struct is simpler.

In our book Functional Swift we show many more examples of how to leverage functional programming in your Swift code base.


Stay up-to-date with our newsletter or follow us on Twitter.

Back to the Blog

recent posts