FWIW, using the OP's Box
examples, I think I can match the call-site syntax with today's toolchain. This all works in an Xcode 13.2.1 playground...
Boilerplatey Stuff that Probably Doesn't Matter but I Included Anyway for the Sake of Completeness
enum Flag: String, CustomStringConvertible {
case fragile
case pastDue
case damaged
case flatRate
var description: String { self.rawValue }
}
protocol Flags { var flags: [Flag] {get set} }
struct DefaultFlags: Flags { var flags: [Flag] = [] }
struct FlatRate: Flags { var flags: [Flag] = [.flatRate] }
struct Damaged: Flags { var flags: [Flag] = [.damaged] }
enum ShippingFlag: String, CustomStringConvertible {
case express
var description: String { self.rawValue }
}
protocol ShippingFlags { var flags: [ShippingFlag] {get set} }
struct DefaultShippingFlags: ShippingFlags { var flags: [ShippingFlag] = [] }
struct Express: ShippingFlags { var flags: [ShippingFlag] = [.express] }
struct Dimensions {
static let small = Dimensions(length: 1, width: 2, height: 3)
static let medium = Dimensions(length: 1, width: 2, height: 3)
static let large = Dimensions(length: 1, width: 2, height: 3)
var length: Double
var width: Double
var height: Double
}
Existing "code-site" syntax:
struct StructBox<F: Flags> {
var flags: F
var dimensions: Dimensions
init(dimensions: Dimensions, flags: F) {
self.dimensions = dimensions
self.flags = flags
}
func ship<F2: ShippingFlags>(_ f2: F2) -> [ShippingFlag] {
f2.flags
}
func ship() -> [ShippingFlag] {
ship(DefaultShippingFlags())
}
}
extension StructBox where F == DefaultFlags {
init(dimensions: Dimensions) {
self.init(dimensions: dimensions, flags: DefaultFlags())
}
}
enum EnumBox<F: Flags> {
case regular(Dimensions, F)
var flags: [Flag] {
switch self {
case .regular(_, let flags): return flags.flags
}
}
}
extension EnumBox where F == FlatRate {
static func flatRate(_ dimensions: Dimensions) -> EnumBox {
.regular(dimensions, FlatRate())
}
}
And the resulting call-site code & output:
let structBox1 = StructBox(dimensions: .medium)
let structBox2 = StructBox(dimensions: .medium, flags: Damaged())
print(structBox1.flags.flags) // []
print(structBox2.flags.flags) // [damaged]
print(structBox1.ship()) // []
print(structBox1.ship(Express())) // [express]
let enumBox1: EnumBox = .flatRate(.large)
let enumBox2: EnumBox = .regular(.large, Damaged())
print(enumBox1.flags) // [flatRate]
print(enumBox2.flags) // [damaged]
IIUC, you're proposing that the following syntax would have the same behavior?
struct StructBox<F: Flags> {
var flags: F
var dimensions: Dimensions
init(dimensions: Dimensions, flags: F = DefaultFlags()) {
self.dimensions = dimensions
self.flags = flags
}
func ship<F2: ShippingFlags>(_ f2: F2 = DefaultShippingFlags()) -> [ShippingFlag] {
f2.flags
}
}
enum EnumBox<F: Flags> {
case regular(Dimensions, F)
case flateRate(Dimensions, F = FlatRate())
var flags: [Flag] {
switch self {
case .regular(_, let flags): return flags.flags
case .flateRate(_, let flags): return flags.flags
}
}
}
I can see the advantage.
I can't help but wonder, though, if there are ways to accomplish the same thing in the OP's example, could this is another example of "convenient code-gen stuff that gets added to the compiler instead of being a use case for meta-programming"? I'm not saying this is the case here, I'm just asking the question. Arguably my example just skates in as "working" due to a technicality... the OP's use case only had instantiating an enum, not switching on one, and I'm not sure if there's a way to handle that with the existing toolchain.
(Also, I didn't have time to even try @stephencelis's example use case.)