首页 > 解决方案 > Swift 泛型方法不使用大多数特定类型

问题描述

我有一个适用于几种类型的标记协议

protocol Foobar {}
extension String: Foobar {}
extension Int: Foobar {}

我想为这个协议定义一个实现一些默认行为的通用方法,但我可以为特定类型重载。这有效:

func foobar<T: Foobar>(_ t: T) {
    print("default")
}

func foobar(_ t: Int) {
    print("int")
}

foobar("hi.") // default
foobar(3)     // int

问题是如果我将它包装在另一个通用方法调用中

func wrapper<U>(_ t: U) where U: Foobar {
    foobar(t)
}

wrapper("hi.") // default
wrapper(3)     // default <---- !!!

我曾希望编译器看到Int: Foobarsowrapper(3)是用U == Int(即最具体的类型)编译的,因此行为与foobar(3). 然而似乎相反U == Foobar

有没有不同的方法来写这个,这样wrapper(3)做同样的事情foobar(3)?我知道我可以在其中执行运行时类型检查以强制执行相同的行为,但在我看来,编译器应该能够弄清楚。


正如评论中指出的那样,我可以专攻wrapper

func wrapper(_ t: Int) { foobar(t) }

这行得通,但想象一下我有一堆其他类型也符合Foobar并有自己的重载。现在我必须写

func wrapper(_ t: Int)    { foobar(t) }
func wrapper(_ t: Double) { foobar(t) }
func wrapper(_ t: String) { foobar(t) }
func wrapper(_ t: Bool)   { foobar(t) }
// ...

使用这样的代码,我希望我可以使用泛型来避免重复

标签: swiftgenericsoverloading

解决方案


这些评论让我确信,我想要的解决方案是不可能的,所以我想出了一种不同的方法,可以在不重复代码的情况下实现类似的功能。

首先要注意的是,各种foobar方法在 Swift 中确实没有关系。因为它们的参数类型不同,所以尽管名称相同,但它们被视为完全不同的方法。所以还不如不要试图让它们看起来一样

func foobarDefault<T: Foobar>(_ t: T) {
    print("default")
}

func foobarInt(_ t: Int) {
    print("int")
}

现在问题变得更清楚了:你有完全不同的方法,怎么wrapper知道要调用哪一个作为它的参数?简单:将信息放入协议中!

protocol Foobar {
    static var foobar: (Self) -> Void { get }
}
// default implementation
extension Foobar {
    static var foobar: (Self) -> Void { return foobarDefault }
}
// conformance
extension String: Foobar {}
extension Int: Foobar { static let foobar = foobarInt }

包装器实现很简单

func wrapper<U: Foobar>(_ t: U) {
    U.foobar(t)
}

这正是我想要的,唯一的区别是你在协议一致性旁边指定了 foobar 重载,现在我想到它就更好了。就我而言,我什至决定省略默认扩展名,以便更明确地说明每种类型如何实现协议!


正如杰西指出的那样,这是一个非常愚蠢的例子。在我的实际应用中,foobar方法更复杂。这更像

struct Validator {
  // has some internal data determining how it validates things
  let blah = ...

  // knows how to validate everything based on its own data
  func validateDefault<T: Foobar>(_ t: T) -> Bool { ... }
  func validateInt(_ t: Int) -> Bool { ... }
}

因为验证器是在运行时动态构建的,所以包装器需要注入一个验证器wrapper(foobar, validator)。那么这里怎么设置呢?我选择这样做

protocol Foobar {
  static var validate: (Validator) -> (Self) -> Bool
}
extension Int: Foobar {
  static let validate = Validator.validateInt // so simple!
}

func wrapper<U: Foobar>(_ t: U, _ validator: Validator) -> Bool {
    return U.foobar(validator)(t)
}

是的,你可以用实例方法来代替,但这有点糟糕:

protocol Foobar {
  func validate(_ validator: Validator) -> Bool
}
extension Int: Foobar { // this is the part that gets repeated for every type!
  func validate(_ validator: Validator) -> Bool {
    return validator.validateInt(self)
  }
} // much noisier

func wrapper<U: Foobar>(_ t: U, _ validator: Validator) -> Bool {
    return t.foobar(validator)
}

推荐阅读