Enum's all implement protocol Foo: best way to get allCases as [Foo] If Foo is Hashable?

protocol Foo {
    var name: String { get }
}

enum A: Foo, CaseIterable {
    case a, b, c

    static var allFoos: [Foo] {
        Self.allCases as [Foo]
    }

    var name: String {
        String(describing: self)
    }
}

enum B: Foo, CaseIterable {
    case one, two, three

    static var allFoos: [Foo] {
        Self.allCases as [Foo]
    }

    var name: String {
        String(describing: self)
    }
}

// want to just operate on Foo's
//(A.allCases + B.allCases).forEach {     // error: Binary operator '+' cannot be applied to operands of type '[A]' and '[B]'
(A.allFoos + B.allFoos).forEach {   // Q: is this the best way?
    print($0.name)
}
1 Like

If all instances of Foo are known, I would personally just write it as an extension on Foo. e.g.

extension Foo {
  static let allCases: [Foo] = [A.allCases, B.allCases].flatMap { $0 }
}

It just dinged on me I might have misunderstood your question. If you want each Foo type to have an allFoos, you could do this:

extension Foo where Self: CaseIterable {
  static var allFoos: [Foo] {
    allCases as! [Foo]
  }
}
1 Like

I need Foo to be either Hashable or Identifiable for use with SwiftUI.ForEach, this cause problem:

protocol Foo: Hashable {
    var name: String { get }
}

extension Foo where Self: CaseIterable {
    static var allFoos: [Foo] {     // Error: Protocol 'Foo' can only be used as a generic constraint because it has Self or associated type requirements
        allCases as! [Foo]          // Error: Protocol 'Foo' can only be used as a generic constraint because it has Self or associated type requirements
    }
}

Is there anyway to overcome this?

This (Error: Protocol 'Foo' can only be used as a generic constraint because it has Self or associated type requirements) seems to be a classic problem in Swift. Unfortunately, I don't think the solution here is very straightforward without a lot of boilerplate :frowning_with_open_mouth:. So it's probably best to wait for someone more experienced to chime in.

Need a Bad Explanation?

BTW If you are unaware, basically what it is saying is that you can't use Foo as a type (i.e. as a existential) because somewhere in its requirements it has an associatedtype. In this case, any type that conforms to Foo requires an associated type ID via its inheritance of Identifiable.

This means that it fails when you try to declare a value of type [Foo], because Foo, due to its associatedtype requirement, is not a valid type.

For example, you should still get the same error if you tried to instantiate a simple var (let foo: Foo = A.a). Though I believe the restriction in this simple case is being removed in a future version of Swift (unless it has already)? [Foo] from my knowledge will remain invalid however.

Usually this is solved using generic type, or some Foo. But in my case here, I'm trying to mix different enum types by using Foo as an abstract type. Unfortunately, Swift doesn't allow this it seems.

I think as of now, the solution is to create type erase by making a AnyFoo. Until generalized existential when we no longer need to hand code type erase.

Please correctly if I'm wrong.

1 Like

I think as of now, the solution is to create type erase by making a AnyFoo .

This was my immediate inclination as well as someone who is new(er) to the language btw. I just wanted to be sure someone smarter than me agreed before I said anything :wink:

I believe 5.7 will allow any protocol with associatedType be used as type.

1 Like

Maybe your example is oversimplified, in this case make it more realistic.

The following works for me given the current restrictions you'd specified.
enum A: String, CaseIterable {
    case a, b, c
}

enum B: String, CaseIterable {
    case one, two, three
}

func test() {
    let cases = (A.allCases.map { $0.rawValue } + B.allCases.map { $0.rawValue })
    cases.forEach {
        print($0)
    }
}

struct ContentView: View {
    var body: some View {
        let cases = (A.allCases.map { $0.rawValue } + B.allCases.map { $0.rawValue })
        
        List {
            ForEach(cases, id: \.self) { v in
                Text(v)
            }
        }
    }
}

The cases in your example is [String], that why it works. My enum case is this:

protocol ColorCase: Hashable {
    var name: String { get }
    var color: Color { get }
}

and each of my enum's implements it and I want to do ForEach over [ColorCase]. Can you get this to work with SwiftUI.ForEach?

Make it a struct?
struct ColorCase: Hashable {
    var name: String
    var color: Color
}

enum A: String, CaseIterable {
    case a, b, c
    var colorCase: ColorCase {
        switch self {
        case .a: return ColorCase(name: rawValue, color: .red)
        case .b: return ColorCase(name: rawValue, color: .green)
        case .c: return ColorCase(name: rawValue, color: .blue)
        }
    }
}

enum B: String, CaseIterable {
    case one, two, three
    var colorCase: ColorCase {
        switch self {
        case .one: return ColorCase(name: rawValue, color: .red)
        case .two: return ColorCase(name: rawValue, color: .green)
        case .three: return ColorCase(name: rawValue, color: .blue)
        }
    }
}

func test() {
    let cases = (A.allCases.map { $0.colorCase } + B.allCases.map { $0.colorCase })
    cases.forEach {
        print($0)
    }
}

struct ContentView: View {
    var body: some View {
        let cases = (A.allCases.map { $0.colorCase } + B.allCases.map { $0.colorCase })
        
        List {
            ForEach(cases, id: \.self) { v in
                Text(v.name)
                    .foregroundColor(v.color)
            }
        }
    }
}
2 Likes

Alright. This indirection/conversion works. A lot of copying going on every time. Not ideal but ok for now.

I wonder when protocol with associatedType can be used as type in 5.7, is there any copying/conversion/boxing going on behind the scene?

Thanks!

By copying are you referring to "....map { $0.rawValue }" or what?

Here's yet another alternative for you for the time being.
protocol ColorCaseable: Hashable {
    var name: String { get }
    var color: Color { get }
}

enum A: String, CaseIterable, ColorCaseable {
    case a, b, c
    var name: String { rawValue }
    var color: Color { .red }
}

enum B: String, CaseIterable, ColorCaseable {
    case one, two, three
    var name: String { rawValue }
    var color: Color { .green }
}

struct ContentView: View {
    var body: some View {
        List {
            ForEach(A.allCases, id: \.self) { v in
                CommonView(v: v)
            }
            ForEach(B.allCases, id: \.self) { v in
                CommonView(v: v)
            }
        }
    }
}

struct CommonView<T: ColorCaseable>: View {
    let v: T
    var body: some View {
        Text(v.name)
            .foregroundColor(v.color)
    }
}

Would be glad to know this. I guess we'll know the answer in a week's time one way or another.


I remember sidestepping the whole "Self or associated type requirement" issue this creative way in one of my projects.
protocol MyEquatable {
    func eq(_ v: MyEquatable) -> Bool
}

struct S: MyEquatable {
    var x: Int

    func eq(_ v: MyEquatable) -> Bool {
        guard let v = v as? S else { return false }
        return x == v.x
    }
}

struct D: MyEquatable {
    var y: String
    
    func eq(_ v: MyEquatable) -> Bool {
        guard let v = v as? D else { return false }
        return y == v.y
    }
}

func prototest() {
    let v: [MyEquatable] = [S(x: 1), S(x: 1), S(x: 2), D(y: "2")]
    
    print(v[0])
    print(v[1])
    print(v[2])
    print(v[3])

    assert(v[0].eq(v[1]))
    assert(!v[0].eq(v[2]))
    assert(!v[0].eq(v[3]))
    
    // a bit strange you can compare S and D directly, but that's fine with me so long as they compare not equal:
    let s = S(x: 0)
    let d = D(y: "0")
    assert(!s.eq(d))
    
    print("done")
}

prototest()
1 Like

Note that such an existential type will be usable as a type in upcoming versions of Swift, but they will not conform to any protocols, not themselves and not the protocols they refine—this is not an implementation shortcoming: it is not (ever) possible in the general case.

The confusion around this concept was the reason these protocols were not permitted to be used as types in the first place, but it has been decided better to allow you to use the type in some way than to ban them altogether. However, it does not mean that you will be able to have Hashable existential types.

5 Likes

I mean this:

a ColorCase is created on every access.

So hand coded type erase is still needed?

Still awaits "Generalized Existential" to eliminate the need to hand code type erase?

It is a small value type, normally allocated on stack, and should as fast as memcpy-ing 24 bytes worth of data. I doubt you'll even see it in profiler unless you do a microbenchmark.

Plus, you can "create" it just once as shown here.
struct ColorCase: Hashable {
    var name: String
    var color: Color
}

struct A {
    static let allCases = [
        ColorCase(name: Kind.a.rawValue, color: .red),
        ColorCase(name: Kind.b.rawValue, color: .green),
        ColorCase(name: Kind.c.rawValue, color: .blue)
    ]
    
    enum Kind: String { case a, b, c }
    let kind: Kind
    let colorCase: ColorCase
}

struct B {
    static let allCases = [
        ColorCase(name: Kind.one.rawValue, color: .red),
        ColorCase(name: Kind.two.rawValue, color: .green),
        ColorCase(name: Kind.three.rawValue, color: .blue)
    ]
    
    enum Kind: String { case one, two, three }
    let kind: Kind
    let colorCase: ColorCase
}

struct ContentView: View {
    var body: some View {
        let cases = A.allCases + B.allCases
        
        List {
            ForEach(cases, id: \.self) { v in
                Text(v.name)
                    .foregroundColor(v.color)
            }
        }
    }
}

(Here the boilerplate is moved from one place into another - manual allCases implementation.)

1 Like

:+1::pray:

I decided to keep my enum's, so I did the precreate as static var inside my enum's by mapping allCases to allColors. I use enum as a way to define my colors. When I add or delete color, the compiler will make sure update everything.

The most generalized existential will not eliminate the need for custom code.

1 Like

Have a look. This sidesteps Hashable requirement:

protocol ColorCase: CaseIterable {
    var name: String { get }
    var color: Color { get }
}

enum A: String, ColorCase {
    case a, b, c
    var name: String { rawValue }
    var color: Color { .red }
}

enum B: String, ColorCase {
    case one, two, three
    var name: String { rawValue }
    var color: Color { .green }
}

struct ContentView: View {
    var body: some View {
        let a: [ColorCase] = A.allCases
        let b: [ColorCase] = B.allCases
        let cases = a + b
        
        List {
            // the range is constant here, 0 ..< A.allCases.count + B.allCases.count
            ForEach(0 ..< cases.count) { i in
                Text(cases[i].name)
                    .foregroundColor(cases[i].color)
            }
        }
    }
}
1 Like