I'm trying to understand why the where clause of a generic method is ignored
I've made a simple use case in Swift 3 (you can copy the code in a playground if you wanna fiddle with it):
//MARK: - Classes
protocol HasChildren {
var children:[Human] {get}
}
class Human {}
class SeniorHuman : Human, HasChildren {
var children: [Human] {
return [AdultHuman(), AdultHuman()]
}
}
class AdultHuman : Human, HasChildren {
var children: [Human] {
return [YoungHuman(), YoungHuman(), YoungHuman()]
}
}
class YoungHuman : Human {}
//MARK: - Generic Methods
/// This method should only be called for YoungHuman
func sayHelloToFamily<T: Human>(of human:T) {
print("Hello \(human). You have no children. But do you conform to protocol? \(human is HasChildren)")
}
/// This method should be called for SeniorHuman and AdultHuman, but not for YoungHuman...
func sayHelloToFamily<T: Human>(of human:T) where T: HasChildren {
print("Hello \(human). You have \(human.children.count) children, good for you!")
}
Ok, so now let's run some tests. If we have :
let senior = SeniorHuman()
let adult = AdultHuman()
print("Test #1")
sayHelloToFamily(of: senior)
print("Test #2")
sayHelloToFamily(of: adult)
if let seniorFirstChildren = senior.children.first {
print("Test #3")
sayHelloToFamily(of: seniorFirstChildren)
print("Test #4")
sayHelloToFamily(of: seniorFirstChildren as! AdultHuman)
}
The output is:
Test #1
Hello SeniorHuman. You have 2 children, good for you!
Test #2
Hello AdultHuman. You have 3 children, good for you!
Test #3
Hello AdultHuman. You have no children. But do you conform to protocol? true
//Well, why are you not calling the other method then?
Test #4
Hello AdultHuman. You have 3 children, good for you!
//Oh... it's working here... It seems that I just can't use supertyping
Well... apparently, for the where
protocol clause to work, we need to pass a strong type that conforms to the protocol in its definition.
Just using supertyping is not enough, even if it is obvious in the test #3 that the given instance is actually conforming to the HasChildren
protocol.
So, what am I missing here, is this just not possible? Do you have some links giving more information about what's going on, or more information about where
clauses, or subtyping and its behavior in general?
I've read a few useful resources but none seems to have an exhaustive explanation about why it's not working:
A generic method may be overloaded like any other method. A class can provide two or more generic methods that specify the same method name but different method parameters.
To create an overload, create a new function within your class and use the same name. To differentiate, you need to have it accept a different number of parameters.
The overload definitions are a list of the different sets of arguments that can be used with a method. For example, the Substring method of System. String has two definitions: PS> 'thisString'.
The type of the method to be called is selected during compile time. What does the compiler know about your types?
if let seniorFirstChildren = senior.children.first {
seniorFirstChildren
is Human
because that's how children
are declared. We have no information whether the child
is adult or senior.
However, consider this:
if let seniorFirstChildren = senior.children.first as? AdultHuman {
Now the compiler knows seniorFirstChildren
is AdultHuman
and it will call the method you expect.
You have to distinguish between static types (types known during compilation) and dynamic types (types known at runtime).
From the Language Guide - Type Casting:
Checking Type
Use the type check operator (
is
) to check whether an instance is of a certain subclass type. The type check operator returnstrue
if the instance is of that subclass type andfalse
if it is not.
The type check operator is
is resolved at runtime, whereas the overload resolution of the call to sayHelloToFamily
using the first children
member of your instance of AdultHuman
(binded to seniorFirstChildren
) as argument is resolved at compile time (in which case it's typed as Human
, which does not conform to HasChildren
). If you explicitly tell the compiler that seniorFirstChildren
is an AdultHuman
instance (using the unsafe as! AdultHuman
), then naturally the compiler will make use of this information to choose the more specific overload.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With