Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you implement protocol methods that return covariant Selfs?

error: protocol 'Protocol' requirement 'instance' cannot be satisfied by a non-final class ('Class') because it uses 'Self' in a non-parameter, non-result type position

protocol Protocol {
    var instance: Self {get}
}

class Class: Protocol {
    var instance: Class {return Subclass()}
}

class Subclass: Class {}

Here is how I would express what I want, in C#. (C# does not, to my knowledge, have a way to enforce that the generic parameter "Self" is actually the Self we know from Swift, but it functions well enough as documentation that should make me do the right thing.)

interface Protocol<Self> where Self: Protocol<Self> {
    Self instance {get;}
}

class Class: Protocol<Class> {
    public Class instance {get {return new Subclass();}}
}

class Subclass: Class {}

…how that might look in a future version of Swift:

protocol Protocol {
    typealias FinalSelf: Protocol where FinalSelf.FinalSelf == FinalSelf

    var instance: FinalSelf {get}
}

class Class: Protocol {
    var instance: Class {return Subclass()}
}

class Subclass: Class {}

How I'm emulating the portion of that which is relevant to my problem:

protocol Protocol: ProtocolInstance {
    static var instance: ProtocolInstance {get}
}

protocol ProtocolInstance {}


class Class: Protocol {
    static var instance: ProtocolInstance {return Subclass()}
}

class Subclass: Class {}

And, here is what I believe to be the relevant portion of my code:

protocol Protocol {
    static var 🎁: Self? {get} // an existing instance? 
    static var 🐥: Self {get}  // a new instance

    func instanceFunc()
}

extension Protocol {
    static func staticFunc() {
        (🎁 ?? 🐥).instanceFunc()
    }
}
like image 781
Jessy Avatar asked Oct 07 '15 17:10

Jessy


2 Answers

As it says, you can't do this, and for good reason. You can't prove you'll keep your promise. Consider this:

class AnotherSubclass: Class {}
let x = AnotherSubclass().instance

So x should be AnotherSubclass according to your protocol (that's Self). But it'll actually be Subclass, which is a completely different type. You can't resolve this paradox unless the class is final. This isn't a Swift limitation. This limitation would exist in any correct type system because it allows an type contradiction.

On the other hand, something you can do is promise that instance returns some consistent type across all subclasses (i.e. the superclass). You do that with an associated type:

protocol Protocol {
    typealias InstanceType
    var instance: InstanceType {get}
}

class Class: Protocol {
    var instance: Class {return Subclass()}
}

class Subclass: Class {}
class AnotherSubclass: Class {}
let x = AnotherSubclass().instance

Now x is unambiguously of type Class. (It also happens to be random other subclass, which is kind of weird, but that's what the code says.)

BTW, all of this usually suggests that you're using subclassing when you really shouldn't be. Composition and protocols would probably solve this problem better in Swift. Ask yourself if there's any reason that Subclass needs to actually be a subclass of Class. Could it be an independent type that conforms to the same protocol? All kinds of problems go away when you get rid of subclasses and focus on protocols.


I've been thinking about this more, and there may be a way to get what you're looking for. Rather than saying that all subclasses implement instance, attach instance as an extension. You can still override that if you want to return something else.

protocol Protocol {
    init()
}

class Class: Protocol {
    required init() {}
    var instance: Class { return Subclass() }
}

extension Protocol {
    var instance: Self { return self.dynamicType.init() }
}

class Subclass: Class {}

This dodges the inheritance problem (you can't create the same "AnotherClass returning the wrong type" this way).

like image 73
Rob Napier Avatar answered Oct 17 '22 08:10

Rob Napier


This would actually make sense and work if you don't want to actually return Self for every subclass like this:

protocol Protocol : class {
    typealias Sub : Self
    var instance: Sub {get}
}

Which means that your protocol defines a typealias that has to be a subclass of itself. The following code would just work:

class Class: Protocol {
    var instance: Class {return Subclass()}
}

class Subclass: Class {}

Class().instance    // Returns SubClass()

However the code above doesn't compile with the error

error: inheritance from non-protocol, non-class type '`Self`'

which I think is a bug, because Self is declared as a class type. You can however make it somehow work like this:

protocol Protocol : class {
    typealias Sub : Class
    var instance: Sub {get}
}

but then you don't have much of the protocol itself because only the class itself should ever conform to it.

like image 20
Kametrixom Avatar answered Oct 17 '22 08:10

Kametrixom