Enums is a great tool when you want to avoid existentials and store them as homogeneous collection at the same time. They won’t increase compilation time by default a lot. Large switch statements can be expensive to type-check eventually, but you can try avoid them if possible or help the compiler to infer types.
I'm not sure Swift Macros are the best option here anyway. Maybe something like Sourcery, or roll your own little code generator. It Copilot even. They act complex, just repetitive.
Sourcery might be an option. I got curious and tried to write a template for it. As far as I can see its a feasible option, but at least writing it as stencil template might become cumbersome, because you need to write a lot of boilerplate to recreate the interface from the "sealed" protocol and also repeating the nested for loops repeatedly does not feel right. (Huge Disclaimer. I'm no expert in Sourcery and there might be better APIs to make this more efficient to write)
The approach is to look for protocols that implement the "Sealed" marker protocol and once found looking for types that implement these sealed protocols. For each sealed protocol an Any... enum will be generated and each implementation of that sealed protocol will be a case in there.
Templates /Sealed.stencil
{% for protocol in types.protocols %}
{% if protocol.implements.Sealed %}
enum Any{{ protocol.name }}: {{ protocol.name }} {
{% for type in types.all %}{% for implemented in type.implements %}{% if implemented == protocol.name %}
case {{ type.name|lowerFirstLetter }}({{ type.name }})
{% endif %}{% endfor %}{% endfor %}
{% for variable in protocol.rawVariables %}
var {{ variable.name }}: {{ variable.typeName }} {
switch self {
{% for type in types.all %}{% for implemented in type.implements %}{% if implemented == protocol.name %}
case .{{ type.name|lowerFirstLetter }}(let value):
return value
{% endif %}{% endfor %}{% endfor %}
}
}
{% endfor %}
{% for type in types.all %}{% for implemented in type.implements %}{% if implemented == protocol.name %}
init(_ value: {{ type.name }}) {
self = .{{ type.name|lowerFirstLetter }}(value)
}
{% endif %}{% endfor %}{% endfor %}
}
{% endif %}
{% endfor %}
Support/Sealed.swift
protocol Sealed { }
Game/Example.swift
protocol Entity: Sealed {
var x: Int { get }
var y: Int { get }
}
struct Player: Entity {
let x: Int
let y: Int
}
struct Item: Entity {
let x: Int
let y: Int
}
Generated/Sealed.generated.swift
// Generated using Sourcery 2.2.5 — https://github.com/krzysztofzablocki/Sourcery
// DO NOT EDIT
enum AnyEntity: Entity {
case item(Item)
case player(Player)
var x: Int {
switch self {
case let .item(value):
value
case let .player(value):
value
}
}
var y: Int {
switch self {
case let .item(value):
value
case let .player(value):
value
}
}
init(_ value: Item) {
self = .item(value)
}
init(_ value: Player) {
self = .player(value)
}
}
Config.yml
sources:
- Sources/Game
- Sources/Support
templates:
- Templates
output: Sources/Generated