Swift: Generics and Type Constraints, Strange Behavior

Is there a way to constrain `Self` to a generic type?

For the generic type Owl<T> you are allowed to have the constraint where Self: Owl<String>, but it will only work in contexts where the concrete type information is available.

To make it clear what is happening, consider this:

let nightOwl = Owl<String>(name: "Night Owl", power: "Who")
nightOwl.doSomething() // prints "Owl<String>: Night Owl"

As opposed to this:

let nightOwl: Bird = Owl<String>(name: "Night Owl", power: "Who")
nightOwl.doSomething() // prints "default Bird: Night Owl"

When Swift creates the protocol witness tables for the types Owl<T> and FlappyBird, it has to act differently for each one because Owl is generic. If it doesn't have the concrete type information, i.e. Owl<String> at the call site, it must use the default implementation for Owl<T>. This "loss" of type information is happening when you are inserting the owls into the array of type [Bird].

In the case of FlappyBird, since there is only one possible implementation (since it's not generic), the compiler produces a witness table with the "expected" method reference, which is print("FlappyBird: \(name)"). Since FlappyBird is not generic, its witness table doesn't need any reference to the unconstrained default implementation of doSomething() and can therefore correctly call the the constrained implementation even when the concrete type information is missing.

To make it clear that the compiler "needs" to have the fall back behavior for a generic type, you can remove the Bird conformance from Owl<T> and try to rely solely on the constrained default implementation. This will result in a compilation error with an error that is, as usual with Swift, highly misleading.

Value of type 'Owl' has no member 'doSomething'

Basically, it seems the witness table can't be built because it requires the existence of an implementation that will work for all types T on Owl.

References

  1. https://forums.swift.org/t/swifts-method-dispatch/7228
  2. https://developer.apple.com/videos/play/wwdc2016/416/

Unexpected Behavior in Swift Overloaded Function with Generic Parameters or T or Array of T

The compiler is getting confused by the use of the wrapped optional comming out of ary.first which will definitely go to the first function signature. But even fixing that will still leave some ambiguity on the processing of simpler cases such as [[1,2][3,4]].

Another way to approach this would be to handle all the types internally rather than leaving it up to the compiler:

func flatten<U>( _ ary:[Any] ) -> [ U ]
{
func makeFlat(_ flat:[U], _ elem:Any) -> [U]
{
if let item = elem as? U { return flat + [item] }
if let ary = elem as? [U] { return flat + ary }
if let subAry = elem as? [Any] { return flat + flatten(subAry) }
return flat
}
return ary.reduce([U](),makeFlat)
}

Swift Generic constraints in init

The problem is that the first init method

init<T: Equatable>(data: [T]) 

introduces a local type placeholder T which hides (and is completely
unrelated to) the placeholder T of the Generic type, so it
is essentially the same problem as in Array extension to remove object by value.

As of Swift 2 you can solve that with a "restricted extension":

extension Generic where T : Equatable {
init(data: [T]) {
let handler: (T, T) -> Bool = { $0 == $1 }
compare = handler
// ...
}
}

For Swift 1.x the only solution is probably to define a global helper
function

func makeGeneric<T : Equatable>(data: [T]) -> Generic<T> {
return Generic(compareHandler: { $0 == $1 }, data: data)
}

(and I could not think of a sensible name for the function :).



Related Topics



Leave a reply



Submit