有没有办法在数组中实现有序的条件类型安全?
例如,如果我想要一种类似 DSL 的方式来创建汉堡,我可能想说:
let burgerComponents = .burger(
.buns(2),
.beefPatty(1),
.lettuce(),
.mustard(),
.mushrooms()
)
如果添加 bun 类型,则无法将其再次添加到该数组中。如果您添加牛肉馅饼,您将无法再次添加,以此类推。
一种天真的方法是创建一个Struct
:
struct Burger {
let buns: Int
let patties: Int
let lettuce: Bool
let mustard: Bool
let mushrooms: Bool
init(
buns: Int = 2,
patties: Int = 1,
lettuce: Bool = false,
mustard: Bool = false,
mushrooms: Bool = false) {
self.buns = buns
self.patties = patties
self.lettuce = lettuce
self.mustard = mustard
self.mushrooms = mushrooms
}
}
extension Burger {
static func burger(
buns: Int = 2,
patties: Int = 1,
lettuce: Bool = false,
mustard: Bool = false,
mushrooms: Bool = false) -> Burger {
return Burger(buns: buns,
patties: patties,
lettuce: lettuce,
mustard: mustard,
mushrooms: mushrooms
)
}
}
let burger: Burger = .burger(buns: 2,
mushrooms: true)
但是,您不能为 Burger 结构扩展和添加额外的属性(蛋黄酱、辣芥末等)。Typed Tagless 解决方案解决了这个问题,但不幸的是没有给出有序的一致性。
如果您需要一个不可扩展的解决方案,该Struct
解决方案就可以工作,但是由于我正在创建一个可扩展的 API,其他贡献者可以添加额外的功能,因此 struct 解决方案很遗憾不能解决这些要求!
我探索过的途径是 Phantom Types 和 Typed Tagless 解决方案。
键入无标记
struct Burger {
let buns: Int
let beefPatty: Int
let lettuce: Bool
let mustard: Bool
let mushrooms: Bool
}
enum BurgerAttributes {
case buns(Int)
case beefPatty(Int)
case lettuce
case mustard
case mushrooms
}
protocol BurgerComponents {
static func buns(_ numberOfBuns: Int) -> Self
static func beefPatty(_ numberOfPatties: Int) -> Self
static func lettuce() -> Self
static func mustard() -> Self
static func mushrooms() -> Self
}
struct BurgerProcess {
let attributes: BurgerAttributes
init(_ attributes: BurgerAttributes) {
self.attributes = attributes
}
}
extension BurgerProcess: BurgerComponents {
static func buns(_ numberOfBuns: Int) -> BurgerProcess {
return BurgerProcess(.buns(numberOfBuns))
}
static func beefPatty(_ numberOfPatties: Int) -> BurgerProcess {
return BurgerProcess(.beefPatty(numberOfPatties))
}
static func lettuce() -> BurgerProcess {
return BurgerProcess(.lettuce)
}
static func mustard() -> BurgerProcess {
return BurgerProcess(.mustard)
}
static func mushrooms() -> BurgerProcess {
return BurgerProcess(.mushrooms)
}
}
protocol BurgerComposer {
static func burger(_ processes: BurgerProcess...) -> Self
}
struct BurgerBuilder {
typealias BurgerCompose = () -> Burger
let compose: BurgerCompose
init(compose: @escaping BurgerCompose) {
self.compose = compose
}
}
extension BurgerBuilder: BurgerComposer {
static func burger(_ processes: BurgerProcess...) -> BurgerBuilder {
return BurgerBuilder {
var _buns: Int = 0
var _patties: Int = 0
var _lettuce: Int = 0
var _mustard: Int = 0
var _mushrooms: Int = 0
for process in processes {
switch process.attributes {
case .buns(let buns):
_buns = buns
case .beefPatty(let patties):
_patties = patties
case .lettuce:
_lettuce = lettuce
case .mustard:
_mustard = true
case .mushrooms:
_mushrooms = true
}
}
return Burger(
buns: _buns,
beefPatty: _patties,
lettuce: _lettuce,
mustard: _mustard,
mushrooms: _mushrooms
)
}
}
}
现在您应该能够以类似 DSL 的方式创建 Burger:
let builder: BurgerBuilder = .burger(
.buns(2),
.beefPatty(1),
.lettuce(),
.mustard(),
.mushrooms()
)
let burger: Burger = builder.compose()
// Output: Burger(buns: 2, beefPatty: 1, lettuce: true, mustard: true, mushrooms: true)
因此,Typed Tagless 解决方案满足了一些要求,但是如果您想要有条件的排序,Typed Tagless 解决方案是不可能的。
使用 Typed Tagless,您可以定义无限数量的属性:
let builder = BurgerBuilder = .burger(
.buns(2),
.buns(0),
.buns(10),
.mustard()
)
外部 API 用户没有安全性。
有没有办法实现以下格式的有序类型一致性?
let burgerComponents = .burger(
.buns(2),
.beefPatty(1),
.lettuce(),
.mustard(),
.mushrooms()
)