Function that takes any enum type

Hi, I'm trying to write a function that looks like this:

func foo(symbols: [E, String], constraints: [E]) -> [String]

The idea is that E is an enumeration. The constraints parameter is a list of values from that enumeration, and symbols map from enumeration values to strings.

For each enumeration value in constraints, if that enumeration value is a key in symbols, the associated string value should be in the returned list of strings.

The thing I can't figure out is how to define the type E. E should be valid iff it is an enumeration type. Is there any way to do this?

Thanks,
Lane

We don't have AnyEnum or anything similar. Can't you make E conform to Hashable, and use Dictionary?

Sure. But then I wouldn't get the type-checking safety that I'm looking for.

My goal is to have a type-safe mechanism for specifying a subset of keys from a dictionary as a separate parameter.

What safety are you looking for?

  • That the constraints is a restricted to some set of values? You already restricted it to be all valid/instantiable values of E.

  • That the symbols have all possible combinations of E? You'd want to have a custom method on E instead:

    extension E {
      var symbol: String { ... }
    }
    
1 Like

The check you're asking for isn't type safety, since you would allow any enum to be used. That doesn't prevent the caller from using an inappropriate type.

If you don't care which enum it is, why would you care if an arbitrary Hashable was used for E? Is there something inside foo that it makes a difference to? Wouldn't things just work fine inside foo if E is Int, for example?

If you want actual type safety, the thing to do is declare a protocol, say SymbolConstraint (which can be empty, for this purpose) and constrain E to require SymbolConstraint conformance.

That doesn't prevent a caller from intentionally using foo with something other than an enum that's force-conformed to SymbolConstraint (again: why does foo care?), but it does prevent them from unintentionally passing in a random Hashable.

Also, FWIW, for an enum without associated values on any of its cases, you can kinda make an "Is it an enum?" test by declaring it CaseIterable and requiring E to conform to CaseIterable. The compiler will synthesize the enum's conformance for you, and I don't think it's possible to make anything except an enum conform.

You can make other things conform, you just don't get automatic implementation.

struct Bar: CaseIterable {
    static var allCases: [Bar] = [Bar()]
}
3 Likes

This helps. I wasn't aware of CaseIterable. This comes pretty close to what I was looking for:

class Foo<T: CaseIterable & Hashable> {

    static func foo(symbols: [T: String], constraints: [T]) -> [String] {
        var result = [String]()
        for (symbol, string) in symbols {
            if constraints.contains(symbol) {
                result.append(string)
            }
        }
        return result
    }

}

enum Weather: CaseIterable {
    case clear
    case fog
    case wind
}


let constraints = [Weather.fog, Weather.wind]
let symbols = [ Weather.clear : "Let's go!",
                Weather.fog:    "We must be in London",
                Weather.wind:   "Batten down the hatches!"]


let result: [String] = Foo.foo(symbols: symbols, constraints: constraints)
print(result)

foo is very curious, that it uses symbols ordering (which has no meaning of order), should the order of the result matches constraints instead (well, this still throws out nil value)?

static func foo(symbols: [T: String], constraints: [T]) -> [String] {
  constraints.compactMap { symbols[$0] }
}