I came across this issue when porting a Java project to Swift. Apologies if it has been asked before (I am sure it must) but I lack the Google foo to search for appropriate prior posts.
Consider the following:
protocol Child
{
}
protocol Parent
{
func foo() -> Child
}
class B: Child
{
}
class A: Parent // Error - class A does not conform to protocol 'Parent'
{
func foo() -> B
{
return B()
}
}
If I change the return type of foo() in A to be Child it compiles fine. Why does the compiler not consider the original func foo() -> B to fulfil the contract func foo() -> Child when a B is clearly a Child?
The reason I don't want to change the return type to Child is because the Java equivalent in the project I am porting compiles fine and code internal to A automatically knows that foo() returns a B and exploits that fact.
Bonus question: why does autocorrect on my Mac think func is meant to be "functionalists"?
protocol Foo {
func view() -> UIView
}
class MyView: UIView {}
class Bar: Foo {
// error: Type 'Bar' does not conform to protocol 'Foo'
func view() -> MyView { fatalError() }
}
If you want covariance, you need to use an associatedtype.
Sorry, but I don't understand why your example shouldn't be OK (apart form the fatalError). MyView is clearly a subclass of UIView and so can be used anywhere a UIView is used.
There's potential for breaking source compatibility and potentially leading to dangerous behaviour, so I am not sure how likely it is that this will be fixed. But it seems like lots of people want it, so maybe there's a chance the core team will allow it.
The OP's example won't be supported even with covariant returns and is unrelated to your example. Returning a value of type Child and returning a value of type B are totally different. B conforms to the protocol Child, while the existential type Child does not conform to Child.
Sorry, I was referring to my own example (when I said I think it should be supported), not OP's example as I was replying to the OP questioning why my example isn't OK at the moment.
That doesn't seem to make any sense. The contract is that I have to return something that conforms to the Child protocol. class B conforms to the Child protocol. Why doesn't a function that returns a B satisfy the requirement that it return something that conforms to Child?
By the way, my name is "jeremyp". It's weird seeing everybody refer to "the OP". You can call me by my name and if you are concerned about the pronoun, "he" is fine.
A protocol without Self or associated type requirements can be used as a concrete type; that type is called a "protocol type" in some places in Swift, but is otherwise known as an "existential type." There is a ton to know about these types, but I will quote the following paragraph by way of background:
In the future, it would be possible for Child to conform to itself because there are no Self or associated type requirements. But even in that future, B (a concrete type) would not have a subtyping relationship with Child (another concrete type) any more than it would with another hypothetical class C that conforms to Child.
Your protocol Parent demands a function that returns a value of concrete type Child. Returning a value of type B does not and will never satisfy that requirement. To create a contract for a function that "returns something that conforms to the Child protocol," use an associated type constraint in your protocol.
Yeah, this is a bit rubbish. Not your explanation, which is excellent, thank you, but the whole hidden concrete type nonsense.
Any reasonable person seeing func foo() -> Child would assume it could return anything that conforms to the Child protocol. There's nothing in the code above that even hints at the fact that a new concrete type has been automagically created behind the scenes. This is an implementation detail that has surfaced in the programmer's model.
I would argue that is counter intuitive to most people looking at the code coming from languages other than Swift. Even to me who has been programming in Swift since version 1 it looks like "your function can return anything as long as it conforms to Child"
That would make things a lot more painful. The real example has much more complex type relationships. I'd suddenly have generics multiplying all over the place that don't need to exist in the original code. I'll figure out a hack instead.
The contract you specified in your Parent protocol is to return something that is a Child. The implementation, as I have written it, does exactly that.
If you want your conforming type to explicitly return a type that conforms to Child, then you need to use an associated type to tell the compiler that is your intention with an associated type :
And, whatever you do, never try arguing "well it works differently to Java". Swift is not another language, it is what it is. I have worked in (at least) COBOL, C, Pascal, C++, dBase, Clipper, Delphi, C#, Objective-C and Swift. Most of my work is teaching developers how to design frameworks, which involves implementing Design Patterns. Some design patterns are immediately realisable in most languages, others are not, which is when you have to either work within the constraints of a language or change to another.
Well, it can. That isn’t what’s at issue at all. We are talking about the return value’s static type, not its dynamic type (type(of: foo() as Any)).
The static type is declared to be Child, which is a perfectly reasonable thing to do. A method can return anything that conforms to Child, at its discretion, which can vary from one invocation to the next at runtime.
This is distinct from a method that always returns a particular type conforming to Child that is knowable statically. That particular type might be knowable statically to:
the caller but not the implementor (generic <T: Child> () -> T),
the implementor but not the caller (“opaque” or “reverse generic” () -> some Child), a new feature that could be potentially made more general in the future with the notation () -> <T: Child> T,
both the implementor and the caller (() -> B),
or—for the sake of completeness even though I am repeating myself—neither the implementor nor the caller (() -> Child).
A protocol, in turn, can require the static type of the return value to be known by the type’s caller and not chosen by the type’s implementor by requiring the use of generics. A protocol cannot yet require the use of “reverse generics” but I suspect it will eventually be possible to do so. Or a protocol can require the type to be known to both parties or to neither party. This is distinct from whether the protocol itself puts any constraints on what that type should be (an associated type).
Not all type relationships are “generics.” Protocols with associated types are not generic protocols (which are unlikely ever to happen). Again, to appreciate the difference, ask yourself the question: the type in question is meant to be known statically to the person who is writing what code?
The point of having all of these features in a statically typed language is to allow you to model these type relationships so that the type checker can help guarantee certain invariants at compile time. If this isn’t what you want, and you’re looking for polymorphism to be a runtime concept only, then Swift may not be the right tool for the job.