Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I sort an enum's cases by order of declaration?

Tags:

swift

In Swift, when an enum conforms to CaseIterable, "The synthesized allCases collection provides the cases in order of their declaration."

I would like to sort an array of CaseIterable enum cases, which means conforming to Comparable. Can I access this same order of declaration to determine how such objects should be sorted?

If not, is my implementation of < below reasonable?

enum MyEnum: CaseIterable, Comparable {
    case one, two, three
}

static func < (lhs: MyEnum, rhs: MyEnum) -> Bool {
    guard let lhsIndex = allCases.firstIndex(of: lhs), let rhsIndex = allCases.firstIndex(of: rhs) else {
        fatalError("`MyEnum`'s implementation of `Comparable.<` found a case that isn't present in `allCases`.")
    }
    return lhsIndex < rhsIndex
}

Finally, is it possible to go one step further and make CaseIterable itself conform to Comparable this way? Any reason this would be a bad idea?

like image 896
Ben Packard Avatar asked May 13 '20 17:05

Ben Packard


3 Answers

In Swift 5.3 and later you can use the synthesized conformance to Comparable for any enum that either has no associated value or has an associated value that conforms to Comparable.

For example:

enum Size: Comparable {
    case small
    case medium
    case large
    case extraLarge
}

let shirtSize = Size.small
let personSize = Size.large

if shirtSize < personSize {
    print("That shirt is too small")
}

I have an article providing information on this change, along with other features introduced in Swift 5.3: What’s new in Swift 5.3?

like image 87
TwoStraws Avatar answered Oct 18 '22 19:10

TwoStraws


If you don't want to make your enum conform to RawRepresentable with an Int RawValue type, like the other answers here have posted, then your approach seems reasonable to me.

As for this question:

Finally, is it possible to go one step further and make CaseIterable itself conform to Comparable this way?

Yes, you could make an extension on Comparable that contains a default implementation of the < function and constrain the extension to only apply when Self also conforms to CaseIterable, as shown below:

extension Comparable where Self: CaseIterable {
    static func < (lhs: Self, rhs: Self) -> Bool {
        guard let lhsIndex = allCases.firstIndex(of: lhs), let rhsIndex = allCases.firstIndex(of: rhs) else {
            fatalError("`\(Self.self)`'s implementation of `Comparable.<` found a case that isn't present in `allCases`.")
        }
        return lhsIndex < rhsIndex
    }
}

This will give this < function to any enum you declare as conforming to both Comparable and CaseIterable.

like image 25
TylerP Avatar answered Oct 18 '22 19:10

TylerP


You could make the enum accessed in the same order as the declaration by providing an Int raw type to your enum:

enum MyEnum: Int, CaseIterable {
like image 2
Frankenstein Avatar answered Oct 18 '22 21:10

Frankenstein