Swift: generic overloads, definition of more specialized
I'd say your problem is that you don't explicitely tell the compiler that P == ()
try the following code in a playground :
Void.self == (Void).self // true
Void() == () // true
(Void)() == () // true
(Void) == () // Cannot convert value of type '(Void).Type' to expected argument type '()'
Foo<Int>.self == (() -> Int).self // false
(() -> Int).self == ((Void) -> Int).self // false
Foo<Int>.self == ((Void) -> Int).self // true
Since (Void)
cannot be converted to ()
, I guess the compiler can't understand that foo<R>(_ f: () -> R)
is actually a specialization of foo<P, R>(_ f: (P) -> R)
.
I suggest you create generic type aliases for your function types to help the compiler understand what you're doing eg. :
typealias Bar<P, R> = (P) -> R
typealias Foo<R> = Bar<Void, R>
Now you can can define your function like that :
func foo<R>(_ f: Foo<R>) { print("r") } // Note that this does not trigger a warning.
func foo<P, R>(_ f: Bar<P, R>) { print("pr") }
and then use them with any closure you want :
let f: () -> Int = { 42 }
foo(f) // prints "r"
let b: (Int) -> Int = { $0 }
foo(b) // prints "pr"
let s: (String) -> Double = { _ in 0.0 }
foo(s) // prints "pr"
But you can actually just write :
func foo<R>(_ f: (()) -> R) { print("r") }
func foo<P, R>(_ f: (P) -> R) { print("pr") }
or even :
func foo<R>(_ f: (Void) -> R) { print("r") } // triggers warning :
// When calling this function in Swift 4 or later, you must pass a '()' tuple; did you mean for the input type to be '()'?
func foo<P, R>(_ f: (P) -> R) { print("pr") }
and you get the same results.
Swift Generics: More specialized than generic?
Unlike C++, Swift does not deal with generics by substituting the concrete types at the call site and making a non-generic copy (at least in general it doesn't - as an optimization that's allowed, but I digress)
Swift deals with genericity by passing in metadata information describing the actual types at each invocation into one master function, which then uses metadata-provided entry points to manipulate your objects
In your example, Initializable does not provide any operations, so when the compiler tries to execute defVal(T) it has no clue what to do (how can it ensure that there is an overload of defVal for your type?)
What you want to do is actually define defVal as a static function on the Initializable protocol, and then implement it in the extensions, then Swift will know that T.defVal() means something akin to
metadata[Initializable]->defVal(metadata)
Oh, since you're trying to execute a *(T,T), you might also want to make a Multipliable protocol and then your T will be typed as
<T: protocol<Initializable,Multipliable>>
Wrong specialized generic function gets called in Swift 3 from an indirect call
This is indeed correct behaviour as overload resolution takes place at compile time (it would be a pretty expensive operation to take place at runtime). Therefore from within test(value:)
, the only thing the compiler knows about value
is that it's of some type that conforms to DispatchType
– thus the only overload it can dispatch to is func doBar<D : DispatchType>(value: D)
.
Things would be different if generic functions were always specialised by the compiler, because then a specialised implementation of test(value:)
would know the concrete type of value
and thus be able to pick the appropriate overload. However, specialisation of generic functions is currently only an optimisation (as without inlining, it can add significant bloat to your code), so this doesn't change the observed behaviour.
One solution in order to allow for polymorphism is to leverage the protocol witness table (see this great WWDC talk on them) by adding doBar()
as a protocol requirement, and implementing the specialised implementations of it in the respective classes that conform to the protocol, with the general implementation being a part of the protocol extension.
This will allow for the dynamic dispatch of doBar()
, thus allowing it to be called from test(value:)
and having the correct implementation called.
protocol DispatchType {
func doBar()
}
extension DispatchType {
func doBar() {
print("general function called")
}
}
class DispatchType1: DispatchType {
func doBar() {
print("DispatchType1 called")
}
}
class DispatchType2: DispatchType {
func doBar() {
print("DispatchType2 called")
}
}
func test<D : DispatchType>(value: D) {
value.doBar()
}
let d1 = DispatchType1()
let d2 = DispatchType2()
test(value: d1) // "DispatchType1 called"
test(value: d2) // "DispatchType2 called"
Swift optional promotion vs generic overload resolution
Specificity seems to always trump variance conversions, according to my experiments. For example:
func bar<T>(_ x: [Int], _ y: T) { print("A") }
func bar<T: P>(_ x: [Any], _ y: T) { print("B") }
bar([1], Y()) // A
bar
is more specific, but requires a variance conversion from [Int]
to [Any]
.
For why you can convert from (Y?) -> Void
to (P) -> Void
, see this. Note that Y
is a subtype of Y?
, by compiler magic.
Since it is so consistent, this behaviour seems to be by design. Since you can't really make Y
not a subtype of Y?
, you don't have a lot of choices if you want to get the desired behaviour.
I have this work around, and I admit it's really ugly - make your own Optional
type. Let's call it Maybe<T>
:
enum Maybe<T> {
case some(T)
case none
// implement all the Optional methods if you want
}
Now, your (Maybe<Y>) -> Void
won't be converted to (P) -> Void
. Normally I wouldn't recommend this, but since you said:
in the real-world code where I encountered this, the closure has multiple params, any of them can be optional, so this would lead to a combinatorial explosion.
I thought reinventing Optional
might be worth it.
Why is generic specialization lost inside a generic function
Specialization is not a replacement for inheritance. It should be used to improve performance, not change behavior.
For example, distance(from:to:)
is usually O(k), where k is the distance. For RandomAccessCollection it can be performed in O(1) due to a specialization. But the result is the same either way.
Specialization is done at compile-time based on the information the compiler has. In your example, the compiler can see that boolArray
is a [Bool]
, and so it uses the specialized extension. But inside of isBool
, all that the compiler knows is that array
is an Array. It doesn't know when compiling the function what kind of Array will be passed. So it picks the more general version to cover all cases.
(The compiler may create multiple versions of isBool
in the binary for optimization purposes, but luckily I haven't found any situations where this impacts what extensions or overloads are called. Even if it actually creates an inlined, Bool-specific version of isBool
, it will still use the more general Array extension. That's a good thing.)
Leaving your extensions in place, the following would do what you expect (though I don't encourage this):
func isBool<Element>(_ array: [Element]) -> Bool {
array.isBool
}
func isBool(_ array: [Bool]) -> Bool {
array.isBool
}
Now isBool
is overloaded and the most specific one will be selected. Within the context of the second version, array
is known to be [Bool]
, and so the more specialized extension will be selected.
Even though the above works, I would strongly recommend against using specialized extensions or ambiguous overloads that change behavior. It is fragile and confusing. If isBool()
is called in the context of another generic method where Element is not known, it again may not work as you expect.
Since you want to base this on the runtime types, IMO you should query the type at runtime using is
. That gets rid of all the ambiguity. For example:
extension Array {
var isBool: Bool { Element.self is Bool.Type }
}
func isBool<Element>(_ array: [Element]) -> Bool {
array.isBool
}
You can make this much more flexible and powerful by adding a protocol:
protocol BoolLike {}
extension Array {
var isBool: Bool { Element.self is BoolLike.Type }
}
Now, any types you want to get "bool-like" behavior just need to conform:
extension Bool: BoolLike {}
This allows you all the flexibility of your extensions (i.e. the isBool
code doesn't need to know all the types), while ensuring the behavior is applied based on runtime types rather than compile-time types.
Just in case it comes up, remember that protocols do not conform to themselves. So [BoolLike]
would return isBool == false
. The same is true for an extension with where Element: BoolLike
. If you need that kind of thing to work, you need to deal with it explicitly.
extension Array {
var isBool: Bool {
Element.self is BoolLike.Type || Element.self == BoolLike.self
}
}
Related Topics
Count the Number of Lines in a Swift String
Cast to Different C Struct Unsafe Pointer in Swift
Concatenate Literal with Optional String
Remove \\U{E2} Characters from String
Swift Calculate Time for Timers Running in Background
Enum's Rawvalue Property Not Recognized
Opening Import File for Module 'Swift': Not a Directory
Why Is Uint64 Max Equal -1 in Swift
With Swiftui, How to Constrain a View's Size to Another Non-Sibling View
Swift: Uploading Image to Firebase Cloud Storage When User Closes App
How to Tell If a Symbolic Link Exists at a Certain Path
Hittest Prints Ar Entity Name Even When I am Not Tapping on It
Swift Subclasses Used in Generics Don't Get Called When Inheriting from Nsobject
How to Re-Order the Realm Table Using Tableview in Swift
Differences Generic Protocol Type Parameter VS Direct Protocol Type
Swiftui View Property Willset & Didset Property Observers Not Working