Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
927 views
in Technique[技术] by (71.8m points)

swift - 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()
    }
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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).


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...