【问题标题】:Swift - Combine multiple discount typesSwift - 结合多种折扣类型
【发布时间】:2023-03-18 08:13:01
【问题描述】:

所以我一直在玩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 类型中的多个合并为一个?

【问题讨论】:

  • 我刚刚阅读了链接的文章。看来您可能对这种模式有一点误解。
  • 我不太清楚你为什么这么认为——本文的一位作者围绕这种模式创建了一个验证库,它还实现了“组合”功能:swift-validations。但是,我不想结合验证,而是想结合折扣——例如结合示例中的 10% 折扣和 5 美元折扣。
  • 好吧,也许你没有误解pattern,但你肯定误解了Discounting代表的类型。无论如何,组合验证器与您在这里得到的有点不同,即验证器不相互依赖——它们的顺序无关紧要。 OTOH,折扣的顺序很重要 - 每个折扣的结果取决于前一个的结果。
  • 我重读了这篇文章,对用例有了更多的了解。您似乎也想对对象的不同部分应用折扣(例如shippingAmountamount)?那么我认为使用(A) -&gt; A 作为函数类型将是最好的替代设计。请参阅我编辑的答案。 Discounting 的设计目前不支持组合折扣。 “其他人也用这种模式实现了一个组合函数”在这里并不是一个很好的论点。这种模式与您是否可以实现combine 函数并没有真正的关系。

标签: swift swift-protocols


【解决方案1】:

使用这种设计,您无法为任意A 组成Discounting&lt;A&gt;s 列表。

Discounting&lt;A&gt; 表示在给定A 对象的情况下计算折扣后价格的方法。请注意,这是A 的函数,而不是价格的函数。从链接的文章中,这个类型参数A 似乎代表了您要打折的东西。

所以基本上,[Discounting&lt;A&gt;] 包含的信息是一个功能列表,给定一个东西A,可以为您提供它们的折扣价。如您所见,没有“申请另一个折扣”的空间。应用第一次折扣后,您得到的只是折扣价,但Discounting 表示事物 的折扣,而不是价格。您需要 discounted A 对象来应用第二个折扣。

但是,如果您有 Discounting&lt;Double&gt;,则可以合成,

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

func compose<T, U, V>(_ f1: @escaping (T) -> U, _ f2: @escaping (U) -> V) -> ((T) -> V) {
    { f2(f1($0)) }
}

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

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

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

这样,您也可以使用与我在上面显示的Discounting&lt;Double&gt;s 相同的代码来编写它们:

func combine<T>(_ discounts: [Discounting<T>]) -> Discounting<T> {
    Discounting(discount: discounts.reduce({ $0 }, { compose($0, $1.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)))

【讨论】:

  • 你是绝对正确的。感谢您的详细回答——我确实以完全错误的方式查看了 Discount 表示的计算。
  • 但是,现在我将闭包更改为(A) -&gt; (A),我无法实现pullback 函数,对吗?
  • @PrayForTech 正确。但是,如果您可以将另一个(A) -&gt; B 参数添加到pullback,您仍然可以在技术上编写一个函数来更改&lt;&gt; 中的类型。虽然它不是很有用,因为您不能仅从一个 Double (A) 创建一个 Purchase (B)。
猜你喜欢
  • 1970-01-01
  • 2014-07-24
  • 2019-03-11
  • 2015-11-18
  • 1970-01-01
  • 1970-01-01
  • 2021-07-08
  • 1970-01-01
相关资源
最近更新 更多