2

I'm learning Swift coming from other languages with powerful type systems and I am wondering if their is any way to make a protocol conditionally conform to another protocol.

Let's take an example: I define a ShowableAsInt protocol, that allows to get an Int representation for any type that conforms to it.

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"

Now I define a Container protocol that simply wraps an element.

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
}

Because Container is a simple wrapper, whenever the wrapped type can be shown as an Int, the container can also be shown as an Int. I tried to express this, but failed:

// 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"

I know that this conditional conformance can be expressed on class and struct. Is there any way to do the same for protocols? If not, is there any reason for this restriction?

francoisr
  • 4,407
  • 1
  • 28
  • 48

1 Answers1

4

The reason why this is not allowed is stated 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.

See also this question.

If you just don't want to write the duplicate implementation of intRepr everytime, you can do this:

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
Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • Your alternative does the trick for this small example, but it breaks composability. In many realistic scenarios, `Container` is from a library, and I'm not in control of the declarations of `StringContainer` and `IntContainer`. So I cannot write "laws" about `Container` themselves, without having to explicitly apply these laws to every type that conforms to `Container`. It's quite a deep limitation of the type system and, even if I understand its "simplicity > complexity" motivation, it makes me kinda sad. – francoisr Jun 03 '19 at 17:51
  • 1
    @francoisr Well, nothing is perfect. – Sweeper Jun 03 '19 at 17:53