Bogdan Farca
Bogdan Farca

Reputation: 4106

Array of enum types conforming to a protocol with an associated type

Let's consider a simplified case, with two enum types conforming to CaseIterable.

enum A: String, CaseIterable {
    case a1, a2, a3
}

enum B: String, CaseIterable {
    case b1, b2, b3
}

What I want to do is create some sort of hash table associating a key (a String in my example) and the type of the enums:

let dict: [String : CaseIterable.Type] = [ // Error here: Protocol 'CaseIterable' can only be used as a generic constraint because it has Self or associated type requirements
    "my key A" : A.self,
    "my key B" : B.self
]

What I need to achieve in the end is to "query" my hash table for a specific key and apply a protocol method on the result (say allCases).

let cases = dict["my key A"]!.allCases

Any suggestions ?

Upvotes: 0

Views: 626

Answers (1)

Rob Napier
Rob Napier

Reputation: 299355

I want to display a menu listing all the possible options.

So you want Strings, not the cases themselves. That's absolutely doable. First, start by saying what you really want the type to do in the form of a protocol:

protocol CaseNamed {
    static var caseNames: [String]
}

If you had that, you could build what you want:

var enums: [CaseNamed.Type] = [A.self, B.self]
enums.flatMap { $0.caseNames }

(I call this "wish driven development." I wish I had a type that could....)

Now you just need to conform types to CaseNamed by implementing caseNames. Luckily that's easy if the type also happens to conform to CaseIterable:

extension CaseNamed where Self: CaseIterable {
    static var caseNames: [String] {
        self.allCases.map { "\($0)" }
    }
}

But you can have CaseNamed types that don't conform to CaseIterable. CaseIterable isn't a requirement. It's just nice if you have it. Here's the full code:

protocol CaseNamed {
    static var caseNames: [String] { get }
}

enum A: String, CaseIterable, CaseNamed {
    case a1, a2, a3
}

enum B: String, CaseIterable, CaseNamed {
    case b1, b2, b3
}

extension CaseNamed where Self: CaseIterable {
    static var caseNames: [String] {
        self.allCases.map { "\($0)" }
    }
}

var enums: [CaseNamed.Type] = [A.self, B.self]

enums.flatMap { $0.caseNames }

Now of course you might also want this CaseNamed protocol to do other things, so you can add those other things. But you need to think in terms of the calling code and what it fundamentally needs to do its job.

Upvotes: 2

Related Questions