Is there a way to generate a type that’s all the cases of two (or more) enums?

I don’t know what it would be called (composition?), but is there a way to take two (or more) enum types and make a type that’s automatically all the combined cases of them?

For example:

enum Vertical {
    case up
    case down
}

enum Horizontal {
    case left
    case right
}
//How do I define SomeType so this could be .up, .down, .left, or .right?
let twoDimensional: SomeType
2 Likes

The best I can think of is a macro that takes enum subtypes and automatically builds cases for the “parent” type that’s all those cases combined.

That definitely seems doable, but it makes the subtypes more awkward to use alone by effectively namespacing them, and you can’t combine enums you aren’t defining yourself.

It would be good to maintain the compositionality of the parent type by building it literally out of the two component types:

enum Direction {
  case vertical(Vertical)
  case horizontal(Horizontal)
}

so that you can directly construct and pattern-match out the child types. To put sugar over it from that point, maybe you could use dynamic member lookup and ~= overloads to let the Horizontal and Vertical members be accessed on Direction directly, or to let Direction be switched againt Horizontal and Vertical's cases directly, without having to explicitly write the .horizontal()/.vertical() wrapping.

2 Likes

I suppose this is still conceivably done with macros, too. I don’t fully understand the macro system, but perhaps some kind of macro for Direction that takes some kind of marker (a protocol, I presume) on the enums that comprise its “shape” (for lack of a better word) and makes cases and writes the pattern matching overloads is reasonable. I wonder now then if the macro system could get the cases out of an enum you don’t define yourself if you extend it to confirm to the protocol the macro is looking for to compose the cases…

this doesn’t really compose well with raw values.

if i have

enum Vertical:UInt8
{
    case up = 0
    case down = 1
}

enum Horizontal:UInt8
{
    case left = 2
    case right = 3
}

it takes a bit of thinking to get Direction to inherit those raw value encodings.

this would be more encouraging if macros were currently supported on linux, but they are not…

In that case at least, it seems like a straightforward case of forwarding the outer enum's RawRepresentable conformance to those of its payloads, so would benefit from a generalized language feature for forwarding conformances to enum cases.

2 Likes

Conflicting names could happen, also some issues with colliding raw values or incompatible raw value types, but that's ok (e.g. we could prohibit those combinations with an appropriate compilation error). If we did have this at the language level, we could probably do the same for structs / classes & tuples, right?

For those specific values I see no problem. If left was 1, same as down that would prohibit the resulting combination type to have raw value, ditto if the types were different, or, say, in this case:

enum A: UInt8 {
    case a1, a2, .... a200
}
enum B: UInt8 {
    case b1, b2, ... b200
}

union_type AB = A | B : UInt8 // 🛑 Error, too many values

One thing that comes to mind - and maybe it’s solved trivially - but what happens for the cases where, both enum are marked as Int and, therefore, their cases correspond to 0…n - what would happen if we joining two distinct enum each with their own with separate ordering?

Ex

enum MyFirstEnum: Int { case a, case b}
enum MySecondEnum: Int { case x, case y, case z}

Now let’s say I’m doing something with an XPC connection, let’s say I have a launch agent written entirely in objective c which is forwarding an object over to swift, and, I know on the other side of my connection I have some swift code which I plan to use the contents of the object to represent an enum case - how would I be able to hit the right case if I had some swift code receiving the object? In such a scenario, maybe it’s solved simply - I’m not sure.. Maybe I would have to handle the cases on the components of the composition before I start making use of the composed enum?

Ex from some objc code sending an object to swift where it knows the swift code is translating the contents to enum cases


NSDictionary *blah = @{@“myKey”: 0}; // where I believe  0 corresponds to `MyFirstEnum case a` 
[self->delegate sendObj(object: blah)];

On the swift side let’s say I receive that dictionary - where would I put it in the composed Enum on the swift side? Is “0” MyFirstEnum.a or MySecondEnum.x
If someone was trying to represent the raw value of the hypothetical joined set, what would be the Int value for each case? Is it based on the order they were built?

class: SwiftSide: DelegateProtocol { 
     func sendObj(object: [String: Any]) {
          var myComposedEnum: MyComposedEnum?
          myComposedEnum = MyComposedEnum.init(rawValue: object[“myKey”]) // which case belongs to who?
     }
}

What would be the best way to tackle such a scenario? Is 0 corresponding to case a of MyFirstEnum or perhaps corresponding to case x of MySecondEnum ? I think that it’s something that needs an concrete answer because MyComposedEnum is representing case a, b, x, y, z
Maybe it’s a non-issue or it’s something that can be solved elegantly

In any case I like the idea and I can see how it would be useful to be able to represent multiple enums through one. I tend to avoid enum if possible but they are quite useful nonetheless.

It does seem fragile for those situations where (and I know this is an arcane corner case) someone has something like an XPC connection, it’s taking in some runtime selectors, it’s passing an object that is supposed to map over to a swift enum. I think if someone comes in on swift side and plops down a conformance that essentially becomes “MyComposedEnum”, the cases preservation is going to be challenging

Perhaps if MySecondEnum adopts the conformance and points to MyFirstEnum, the cases are concatenated so it becomes like 0…n starting from 0 with MyFirstEnum ?