I didn't really understand your answer, so I figured that I'd just experiment until I could see the pattern. Unfortunately, I found very few signs of any underlying pattern -- and I didn't even get a chance to experiment with generics. All I can see is behavior that is frighteningly inexplicable. I've tried to explain my confusion in the inline comments below. (FYI: I'm still using Swift 2.2 at the moment... if that matters.)
- Aaron // Trivial class hierarchy for the examples class C {} class D: C {} class E: D {} // Trivial protocol hierarchy for the examples protocol P { var a: Bool { get } } protocol Q: P { var b: Bool { get } } class R: Q { let a = true let b = true } // Base protocol with some default implementations protocol BaseProtocol {} extension BaseProtocol { // f1 has a more permissive parameter type than f2 func f1(x: Int??) -> String { return "Base" } func f2(x: Int?) -> String { return "Base" } // g1 has a more permissive parameter type than g2 func g1(x: C) -> String { return "Base" } func g2(x: D) -> String { return "Base" } // h1 has a more permissive parameter type than h2 func h1(x: P) -> String { return "Base" } func h2(x: Q) -> String { return "Base" } // two functions with incomparable but overlapping parameter types func v1(x: (Int?, Int)) -> String { return "Base" } func v2(x: (Int, Int?)) -> String { return "Base" } // two functions with incomparable but overlapping parameter types func w1(x: () -> Int?) -> String { return "Base" } func w2(x: () throws -> Int) -> String { return "Base" } } // Derived protocol with some default implementations protocol DerivedProtocol: BaseProtocol {} extension DerivedProtocol { // f2 has a more permissive parameter type than f1 func f1(x: Int?) -> String { return "Derived" } func f2(x: Int??) -> String { return "Derived" } // g2 has a more permissive parameter type than g1 func g1(x: D) -> String { return "Derived" } func g2(x: C) -> String { return "Derived" } // h2 has a more permissive parameter type than h1 func h1(x: Q) -> String { return "Derived" } func h2(x: P) -> String { return "Derived" } // two functions with incomparable but overlapping parameter types func v1(x: (Int, Int?)) -> String { return "Derived" } func v2(x: (Int?, Int)) -> String { return "Derived" } // two functions with incomparable but overlapping parameter types func w1(x: () throws -> Int) -> String { return "Derived" } func w2(x: () -> Int?) -> String { return "Derived" } } class Z: DerivedProtocol {} // First we check the functions whose parameters have built-in types: print(Z().f1(7)) // prints "Derived" print(Z().f2(7)) // prints "Base" // The outcome seems to demonstrate that the location of the function's definition within the protocol hierarchy is irrelevant. As in Java, it appears that all accessible, applicable functions are being given equal consideration, and then the function whose signature has a more specific parameter type is chosen. That makes sense to me. // Next we check functions whose parameters have class types: print(Z().g1(E())) // prints "Derived" print(Z().g2(E())) // prints "Derived" // Surprisingly, the outcome here appears to contradict the previous one: a function with a less specific type is given preference. This must be due to the fact that it is defined in the derived protocol rather than the base protocol. Does that mean we should call this case "overriding" while the previous one was "overloading"? But I can't understand why there could be any value in treating user-defined types different from built-in types. Maybe the behavior here is a bug? // Now we check the same functions again using an argument with a less specific type that happens to be identical to the formal parameter type of one of the functions: print(Z().g1(D())) // prints "Derived" print(Z().g2(D())) // prints "Base" // And the outcome is different than before! So, for user-defined types, is there a special rule that only applies when the formal and actual parameters have precisely the same type? Of course, we wouldn't need to call this a special case if the behavior in the second example had been the same as the first, which lends support to the idea that the second example is actually illustrating a bug... but we're not done testing yet. // What happens with functions whose formal parameters have protocol types? print(Z().h1(R())) // prints "Derived" print(Z().h2(R())) // prints "Derived" print(Z().h1(R() as Q)) // prints "Derived" print(Z().h2(R() as Q)) // prints "Derived" // Well it appears they are handled like class types rather than built-in types -- except that the special-case behavior that was observed when the class types are identical is not coming into effect here. So, maybe the second example was not a bug? Are we making a deliberate decision to sometimes give the function definition in the derived class precedence over the first? But what exactly would we mean by "sometimes"? And why would having that sometimes-different behavior be a good thing. If we have three different elementary overload resolution schemes for the different kinds of types, what's going to happen when we try to combine those types? Or combine overloading with other language features? Like generics, etc.? Yikes! // I also decided to test the behavior for a pair of functions, both of which can be applied to the argument but neither of which can match the argument's type in a more specific way than the other. I suspected this might somehow trigger a type error, but it didn't. Instead, the definition in the derived protocol was given preference: print(Z().v1((0, 0))) // prints "Derived" print(Z().v2((0, 0))) // prints "Derived" // I suppose that, if a type error isn't going to be raised when neither function is a better fit than the other, then it makes sense to use the definition in the derived class as a default. Unfortunately, my final example demonstrates that we can't even count on that much: print(Z().w1({ return 7 })) // prints "Derived" print(Z().w2({ return 7 })) // prints "Base" // Given that these last two examples exhibit different behavior, the compiler really ought to define them both as type errors. However, I'm even more worried by the diverging behavior of the earlier examples. On Tue, Jun 28, 2016 at 7:09 PM, Dmitri Gribenko <griboz...@gmail.com> wrote: > On Tue, Jun 28, 2016 at 6:07 PM, Aaron Bohannon <aaron...@gmail.com> > wrote: > > AH! I wasn't familiar enough with the LazyCollectionType protocol to > > realize that its map() function had a different signature. > > > > So, presumably, if I pass a non-throwing closure, the compiler will > choose > > the lazy map(). However, it's not immediately obvious to me why it > would be > > chosen. Is it because the LazyCollectionType version of map() "shadows" > the > > CollectionType whenever either one could be chosen (since > LazyCollectionType > > extends CollectionType)? Or is it because the function with the more > > restrictive argument type is always chosen when more than one version of > the > > function could match? > > LazyCollectionType.map() does not shadow Collection.map(), but the > type checker prefers the lazy one because LazyCollectionType refines > Collection. Basically, it prefers the more refined one, but the other > one is still an option. > > Dmitri > > -- > main(i,j){for(i=2;;i++){for(j=2;j<i;j++){if(!(i%j)){j=0;break;}}if > (j){printf("%d\n",i);}}} /*Dmitri Gribenko <griboz...@gmail.com>*/ >
_______________________________________________ swift-users mailing list swift-users@swift.org https://lists.swift.org/mailman/listinfo/swift-users