Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Wrong generic overload function called

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:

  • Type Variance in Swift
  • Covariance and Contravariance
  • Which function does Swift call
like image 946
Yoam Farges Avatar asked Jan 13 '17 10:01

Yoam Farges


People also ask

Can a generic function be overloaded?

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.

How do you overload a function in powershell?

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.

What is overload Powershell?

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


2 Answers

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

like image 95
Sulthan Avatar answered Sep 28 '22 11:09

Sulthan


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 returns true if the instance is of that subclass type and false 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.

like image 24
dfrib Avatar answered Sep 28 '22 11:09

dfrib