2
votes

I have a Swift protocol defined like this:

protocol MyProtocol {
    func genericMethod<T:MyProtocol>(param:T) -> ()
}

I can implement the generic method in a base class like this:

class MyBaseClass : MyProtocol {
    func genericMethod<T where T:MyProtocol>(param:T) -> () {
        println("Performing generic method for type \(T.self)")
    }
}

class MySubClass : MyBaseClass {
    ...
}

So far, so good. I can implement this method and it compiles and runs just fine.

Now, I want to do something similar but in my base class I want to further constrain the type of the generic method by requiring it to conform with a protocol such as Comparable. I try this:

class MyBaseClass : MyProtocol {
    func genericMethod<T where T:MyProtocol, T:Comparable>(param:T) -> () {
        println("Performing generic method for type \(T.self)")
    }
}

Once I add this additional constraint on type T, the class MyClass will not compile because it does not conform to the protocol anymore.

It seems like adding an additional constraint on a generic type should not cause it to cease conforming with a protocol. What am I missing? It seems to me that the protocol is saying that genericMethod must be passed a parameter of a type that conforms with MyProtocol. When I go to implement this in MyBaseClass - just one possible implementation of MyProtocol - that I should be able to restrict that implementation further by saying that the parameter myst conform with Comparable in addition to MyProtocol

Is there a way to refine a generic type in a base implementation like I'm trying to do here?

2
I don't understand why you're surprised. Perhaps I'm missing something obvious, but it seems to me that the protocol says, "Whoever adopts me must implement genericMethod with the parameter being his own type. But T:MyBaseClass, T:Comparable is not MyBaseClass's own type, since MyBaseClass does not adopt Comparable. So, as the compiler says, you've violated the contract laid out by the protocol.matt
Using the Self reference in the protocol was just one way to demonstrate the problem. I get the same problem if I use one protocol and then another. I will update the question to make it more clear why I am surprised at this behaviorTim Dean
OK, thanks for the revision. Well, the answer to the question of why the compiler doesn't like it is that these are not a match. The protocol requirement func genericMethod<T:MyProtocol>(param:T) -> () is not the same as func genericMethod<T where T:MyProtocol, T:Comparable>(param:T) -> (). You are saying in a kind of mental justificatory footnote "one is a more restricted version of the other", but that doesn't matter to the compiler; the point is that one is not the other.matt
To see this, try a simpler variant: func genericMethod<T> (param:T) -> () and func genericMethod<T where T:Comparable>(param:T) -> (). They are not a match either, and it's clearer why, I think. To put it another way, you seem to think that one generic is a kind of "subclass" or "subset" of the other. But that's not how these generic signatures work. It's a match or it isn't.matt
I suggest you review the Liskov Substitution Principle en.wikipedia.org/wiki/Liskov_substitution_principleDaniel T.

2 Answers

2
votes

Adding the additional constraint on a generic type should cause it to cease conforming with the protocol because the protocol is supposed to guarantee conformance, and conformance cannot be guaranteed with subtypes that aren't Comparable. If you want all MyProtocol objects to conform to Comparable then you should make it part of the MyProtocol definition.

protocol MyProtocol: Comparable {
    //...
}

I haven't tried this, but it might also work if you make MyBaseClass a Comparable type.

0
votes

One solution is to go the other way - define your protocol's version of the generic as the most restrictive case. This compiles:

protocol P {
    func genericMethod<T where T:P, T:Comparable>(param:T) -> ()
}

class C1 : P {
    func genericMethod<T> (param:T) -> () {} // compiles even though omits Comparable
    func test() {
        genericMethod(C1()) // compiles even though C1 is not a Comparable
    }
}