Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Forcing super class in Swift Generics

Tags:

generics

swift

I was trying to implement a new method to join two Array, returning an Array containing a common data type to the other two.

To keep it clear, I would expect a way to do something like (knowing that the syntax is not correct...):

@infix func + <T,U,X where X super T, X super U>(left : Array<T>, right : Array<U>) 
-> Array<X>{
    //join both arrays
}

Always thinking that the compiler is capable of detecting the common ancestor type for both Classes. If this is actually not possible, what would be the correct approach? Making that "super" type explicit?

like image 587
khose Avatar asked Jun 20 '14 13:06

khose


1 Answers

Swift's type inference is smarter than you think. The secret is to look at the signature of the nil-coalescing operator ??:

func ??<T>(optional: T?, defaultValue: @autoclosure () -> T) -> T
func ??<T>(optional: T?, defaultValue: @autoclosure () -> T?) -> T?

It's clear when using this operator that it will promote T to be the nearest common ancestor of any types passed to it, all the way up to Any, e.g.:

let i: Int? = 3
let s: String? = "3"
let a: Any? = i ?? s

This compiles and works, but the type of a is Any (which is actually a protocol). In some cases you need to give type evidence to the compiler and in some cases you don't. If the types share a common ancestor that is not a protocol, it appears that evidence is not needed. You may think that ?? is getting special treatment by the compiler, but it isn't. You can roll your own very easily.

To come to your attempt, you are thus overthinking it. (As I was when I had a similar problem.) You only need one type.

If we were to implement your + as a function, it would look like this:

func joinArrays<T>(array1: [T], array2: [T]) -> [T] {
    return array1 + array2
}

class Base {}
class Derived1 : Base {}
class Derived2 : Base {}

let a1 = [Derived1(), Derived1()]
let a2 = [Derived2(), Derived2()]
let a = joinArrays(a1, a2)

The type of a is Array<Base>, because that is the nearest common ancestor of the generic type parameters.

I've used this "type promotion" to create all sorts of sophisticated coalescing/monadic operators, à la Haskell. The only niggle has been that Swift doesn't support covariance of generic type parameters.

like image 97
Gregory Higley Avatar answered Oct 04 '22 03:10

Gregory Higley