Swift - 结合多种折扣类型

Swift - Combine multiple discount types

所以我一直在研究 protocol witness types,它们本质上是协议的具体实现。假设我们有一个对商品打折并返还双倍商品的协议。

而不是这个:

protocol Discountable {
    func discount() -> Double
}

一个人这样做:

struct Discounting<A> {
    let discount: (A) -> Double
}

并且,不是像这样将一种类型一次符合 Discountable 协议:

extension Double: Discountable {
    func discount() -> Double {
        return self * 0.9
    }
}

可以为一个类型提供多个具体实现:

extension Discounting where A == Double {
    static let tenPercentOff = Self { amount in
        amount * 0.9
    }
    
    static let fiveDollarsOff = Self { amount in
        amount - 5
    }
}

但是,我想知道如何 合并 这些折扣的多个。这是我的初始草图:

static func combine(_ discounts: [Discounting<A>]) -> Discounting<A> {
    Discounting { (amount: A) -> Double in
        return discounts.reduce(0.0) { current, discount in
            // ??
        }
    }
}

但是,我不确定在 reduce 闭包中放什么。

如何将多个 Discounting 类型合并为一个?

使用此设计,您无法为任意 A.

编写 Discounting<A> 的列表

A Discounting<A> 表示给定 A 对象计算折扣后价格的方法。请注意,这是 A 的函数,而不是价格的函数。从链接的文章中,此类型参数 A 似乎代表您正在打折的东西。

所以基本上,[Discounting<A>] 包含的信息是一个功能列表,给定一个东西 A,可以为您提供它们的折扣价。如您所见,没有“申请另一个折扣”的余地。应用第一次折扣后,您得到的只是折扣后的价格,但 Discounting 代表 things 的折扣,而不是价格。您需要 discounted A 对象才能应用第二次折扣。

如果你有 Discounting<Double> 但是,组合是可能的,

func combine(_ discounts: [Discounting<Double>]) -> Discounting<Double> {
    Discounting(discount: discounts.reduce({ [=10=] }, { compose([=10=], .discount) }))
}

func compose<T, U, V>(_ f1: @escaping (T) -> U, _ f2: @escaping (U) -> V) -> ((T) -> V) {
    { f2(f1([=10=])) }
}

为了解决一般情况下的问题,Discounting<A> 可以重新设计为返回输入的折扣版本:

struct Discounting<A> {
    let discount: (A) -> A
}

// This is the "protocol witness" version of:
//
// protocol Discountable {
//     func discount() -> Self
// }

这样,您还可以使用与我在上面显示的 Discounting<Double> 相同的代码来组合它们:

func combine<T>(_ discounts: [Discounting<T>]) -> Discounting<T> {
    Discounting(discount: discounts.reduce({ [=12=] }, { compose([=12=], .discount) }))
}

用法示例:

struct Purchase {
    var amount: Double
    var shippingAmount: Double
}

extension Discounting where A == Purchase {
    static let tenPercentOff: Self = .init { purchase in
        Purchase(amount: purchase.amount * 0.9, shippingAmount: purchase.shippingAmount)
    }

    static let tenPercentOffShipping: Self = .init { purchase in
        Purchase(amount: purchase.amount, shippingAmount: purchase.shippingAmount * 0.9)
    }
    
    static let fiveDollarsOff: Self = .init { purchase in
        Purchase(amount: purchase.amount - 5, shippingAmount: purchase.shippingAmount)
    }
}

let combinedDiscounts: Discounting<Purchase> = combine([.tenPercentOff, .fiveDollarsOff, .tenPercentOffShipping])
// Purchase(amount: 85.0, shippingAmount: 90.0)
print(combinedDiscounts.discount(Purchase(amount: 100, shippingAmount: 100)))