有条件的协议符合协议

Conditional Protocol Conformance to Protocol

我正在学习 Swift 来自其他具有强大类型系统的语言,我想知道它们是否有任何方法可以使协议有条件地符合另一个协议。

让我们举个例子:我定义了一个 ShowableAsInt 协议,它允许为任何符合它的类型获得 Int 表示。

protocol ShowableAsInt {
        func intRepr() -> Int
}

extension Int : ShowableAsInt {
    func intRepr() -> Int {
        return self
    }
}

extension String : ShowableAsInt {
    func intRepr() -> Int {
        return self.count
    }
}

func show(_ s: ShowableAsInt) {
    print(s.intRepr())
}

show("abc") // "3"
show(42) // "42"

现在我定义了一个 Container 简单包装元素的协议。

protocol Container {
    associatedtype T
    var element: T {
        get
    }
}

struct StringContainer : Container {
    typealias T = String
    let element: String
}

struct IntContainer : Container {
    typealias T = Int
    let element: Int
}

因为 Container 是一个简单的包装器,只要包装类型可以显示为 Int,容器也可以显示为 Int。我试图表达这个,但失败了:

// Doesn't compile: can't extend a protocol to conform to another one?
extension Container : ShowableAsInt where T : ShowableAsInt {
    func intRepr() -> Int {
        return element.intRepr()
    }
}

// I would like these to compile
show(IntContainer(42)) // "42"
show(StringContainer("abc")) // "3"

我知道这个条件符合可以在classstruct上表达。有没有办法对协议做同样的事情?如果不是,是否有任何限制的原因?

不允许这样做的原因here:

This protocol extension would make any Collection of Equatable elements Equatable, which is a powerful feature that could be put to good use. Introducing conditional conformances for protocol extensions would exacerbate the problem of overlapping conformances, because it would be unreasonable to say that the existence of the above protocol extension means that no type that conforms to Collection could declare its own conformance to Equatable, conditional or otherwise.

另见

如果你只是不想每次都写intRepr的重复实现,你可以这样做:

struct StringContainer : ShowableAsIntContainer {
    typealias T = String
    let element: String
}

struct IntContainer : ShowableAsIntContainer {
    typealias T = Int
    let element: Int
}

extension Container where T : ShowableAsInt {
    func intRepr() -> Int {
        return element.intRepr()
    }
}

typealias ShowableAsIntContainer = ShowableAsInt & Container