Using Comparable with Enumerations in Swift 5.3

As expected, there was so much goodness in WWDC 2020 last week. From exciting new software changes in Swift UI, collection views, and much more to the anticipated move away from Intel to Apple Silicon, there was something exciting for just about everyone. For this post, I’m just going to focus on a tiny sliver of the new changes in Swift 5.3 related to my last post Cleaning Up Swift Enumeration Evaluation – using the Comparable protocol to evaluate enum expressions.

Comparing Enumerations before Swift 5.3

Previously we looked at some different ways to evaluate whether an expression was present in an enumeration with switch statements, computed properties, and RawRepresentable. With Swift 5.3 and specifically proposal SE-0266, enums without an associated type or with one conforming to Comparable can now conform to Comparable themselves and gain the ability to compare values.

What does this mean? Well, prior to this change, if we had an enum that was intrinsically sorted (like ContainerLevel below), we would have had to either declare a type for it or use a private backing variable with a switch or some custom logic to implement sorting and comparability. For example, the following works but requires that the enumeration use Int raw values to conform with Comparable and we have to manually code the < operator.

// Here we have to declare the enum as a Comparable type to implement the protocol
enum ContainerLevel: Int, Comparable {
    case empty = 0
    case almostEmpty
    case halfFull
    case almostFull
    case full
    
    static func < (lhs: ContainerLevel, rhs: ContainerLevel) -> Bool {
        return lhs.rawValue < rhs.rawValue
    }
}

//Are we running low?
let coffeeMugLevel: ContainerLevel = .almostFull
let needMoreCoffee = coffeeMugLevel > .halfFull
print(needMoreCoffee)  //true

What’s Changed?

With iOS 14 and Xcode 12, we can now declare Comparable with no explicit overload needed resulting in much cleaner, more succinct code.

// With iOS 14, we can just use the `Comparable` protocol and the intrinisic ordering of the enum
enum ContainerLevel: Comparable {
    case empty
    case almostEmpty
    case halfFull
    case almostFull
    case full    
}

//Are we running low?
let coffeeMugLevel: ContainerLevel = .almostFull
let needMoreCoffee = coffeeMugLevel > .halfFull
print(needMoreCoffee)   //true

Sorting By Associated Values

You can also implicitly sort enumerations with associated values as well and it works as expected. Let’s pretend we have a function to look at coffee machine water levels for example.

enum ContainerLevel: Comparable {
    case empty
    case partiallyFull(Int)
    case full
    
    static func isAlmostFull(_ level: ContainerLevel) -> Bool {
        //More than 90% full
        return level > partiallyFull(90)
    }
    
    static func isAlmostEmpty(_ level: ContainerLevel) -> Bool {
        //Less than 10% full
        return level < partiallyFull(10)
    }
}

//Running close to empty!  Yikes!
let coffeeMachine: ContainerLevel = .partiallyFull(5)

print(ContainerLevel.isAlmostFull(coffeeMachine))   //false
print(ContainerLevel.isAlmostEmpty(coffeeMachine))  //true

As we would also anticipate, sorting an array of enums works as well.

let levels: [ContainerLevel] = [.full, .empty, .partiallyFull(75), .partiallyFull(15)].sorted()

print(levels.map({"\($0)"}))  //"empty", "partiallyFull(15)", "partiallyFull(75)", "full"

Nice! We’ve got a properly sorted list from the unsorted input that we provided. Obviously this is a trivial example; but hopefully you can leverage Comparable and enum sooner rather than later! Since this change is tied to Swift as well, you should be able to use it in apps on earlier versions of iOS as well with Xcode 12 and Swift 5.3. Happy coding!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: