有没有一种方法可以根据参数类型简化这个 'matrix of overloads',这些参数类型最终都可以由特定类型表示?

Is there a way to simplify this 'matrix of overloads' based on argument types which are all ultimately representable by a specific type?

我们正在尝试创建一个最终在内部使用字符串和可选字符串的函数 addQueryItem

为了 API 中的更多灵活性,而不是使用 String 作为参数类型,我们使用 CustomStringConvertible(String 实现的),因此我们可以使用任何可以表示为字符串。

此外,我们可以向它传递基于 String 的枚举,我们还希望它接受 RawRepresentable 类型,其中 RawValueCustomStringConvertible 本身。

但是,由于我们现在在技术上接受每个参数的两种不同类型的值,我们最终不得不为这两种类型的每种组合创建一个 'matrix of overloads'——总共四个。

我的第一个想法是通过扩展 RawRepresentable 来使用面向协议的编程,这样如果它的 RawValue 也是 CustomStringConvertible,它就会遵守 CustomStringConvertible。然后我可以直接将其传递给采用两个 CustomStringConvertible 参数并消除其他三个参数的版本。但是,编译器不喜欢它,因为我正在尝试扩展协议,而不是具体类型。

// This doesn't work
extension RawRepresentable : CustomStringConvertible
where RawValue:CustomStringConvertible {

    var description: String {
        return self.rawValue
    }
}

由于无法执行上述操作,如上所述,我必须具备以下所有四个条件:

func addQueryItem(name:CustomStringConvertible, value:CustomStringConvertible?){

    if let valueAsString = value.flatMap({ String(describing:[=12=]) }) {
        queryItems.append(name: String(describing:name), value: valueAsString)
    }
}

func addQueryItem<TName:RawRepresentable>(name:TName, value:CustomStringConvertible?)
where TName.RawValue:CustomStringConvertible {
    addQueryItem(name: name.rawValue, value: value)
}

func addQueryItem<TValue:RawRepresentable>(name:CustomStringConvertible, value:TValue?)
where TValue.RawValue:CustomStringConvertible {

    addQueryItem(name: name, value: value?.rawValue)
}

func addQueryItem<TName:RawRepresentable, TValue:RawRepresentable>(name:TName, value:TValue?)
where TName.RawValue:CustomStringConvertible,
      TValue.RawValue:CustomStringConvertible
{
    addQueryItem(name: name.rawValue, value: value?.rawValue)
}

所以,既然看起来不可能让 RawRepresentable 遵守 CustomStringConvertible,有没有其他方法可以解决这个 'matrix-of-overloads' 问题?

不,您不能通过扩展使一个协议符合另一个协议。语言不支持。

为了扩展我的评论,我相信您正在与 Swift 类型系统作斗争。在 Swift 中,您通常不应尝试自动转换类型。调用者在需要功能时应明确符合其类型。因此,对于您的 Order 枚举示例,我认为它应该以这种方式实现:

首先,有一个名称和值的协议:

protocol QueryName {
    var queryName: String { get }
}

protocol QueryValue {
    var queryValue: String { get }
}

现在对于字符串可转换的枚举,不必自己实现它很好。

extension QueryName where Self: RawRepresentable, Self.RawValue == String  {
    var queryName: String { return self.rawValue }
}

extension QueryValue where Self: RawRepresentable, Self.RawValue == String  {
    var queryValue: String { return self.rawValue }
}

但是,为了类型安全,您需要明确遵守协议。这样你就不会与不打算以这种方式使用的东西发生碰撞。

enum Order: String, RawRepresentable, QueryName {
    case buy
}

enum Item: String, RawRepresentable, QueryValue {
    case widget
}

现在也许 QueryItems 真的需要字符串。好的。

class QueryItems {
    func append(name: String, value: String) {}
}

但是包装它的东西可以是类型安全的。这样 Order.buyPurchase.buy 就不会发生冲突(因为它们不能同时通过):

class QueryBuilder<Name: QueryName, Value: QueryValue> {
    var queryItems = QueryItems()

    func addQueryItem(name: QueryName, value: QueryValue?) {
        if let value = value {
            queryItems.append(name: name.queryName, value: value.queryValue)
        }
    }
}

您可以使用上面的代码来降低类型安全性(使用 StringCustomConvertible 之类的东西并使 QueryBuilder 成为非泛型,我不推荐这样做,但您可以这样做)。但我仍然强烈建议您让调用者明确标记他们计划以这种方式使用的类型,方法是明确标记(仅此而已)它们符合协议。


展示不太安全的版本会是什么样子:

protocol QueryName {
    var queryName: String { get }
}

protocol QueryValue {
    var queryValue: String { get }
}

extension QueryName where Self: RawRepresentable, Self.RawValue == String  {
    var queryName: String { return self.rawValue }
}

extension QueryValue where Self: RawRepresentable, Self.RawValue == String  {
    var queryValue: String { return self.rawValue }
}

extension QueryName where Self: CustomStringConvertible {
    var queryName: String { return self.description }
}

extension QueryValue where Self: CustomStringConvertible {
    var queryValue: String { return self.description }
}


class QueryItems {
    func append(name: String, value: String) {}
}

class QueryBuilder {
    var queryItems = QueryItems()

    func addQueryItem<Name: QueryName, Value: QueryValue>(name: Name, value: Value?) {
        if let value = value {
            queryItems.append(name: name.queryName, value: value.queryValue)
        }
    }
}

enum Order: String, RawRepresentable, QueryName {
    case buy
}

enum Item: String, RawRepresentable, QueryValue {
    case widget
}