1

有没有办法在数组中实现有序的条件类型安全?

例如,如果我想要一种类似 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()
)
4

0 回答 0