[SOLVED] Generic factory method and type inference

Issue

I have the following class with a generic factory method:

final class Something<T> {
    
    let value: T
    
    init(initial: T) {
        value = initial
    }
    
}

extension Something {
    
    class func zip<A, B>(_ a: A, _ b: B) -> Something<(A, B)> {
        let initial = (a, b)
        return Something<(A, B)>(initial: initial)
    }
    
}

How come I can’t call zip without explicitly specifying the return type?

// ERROR: Cannot invoke `zip` with an argument list of type `(Int, Int)`
let y = Something.zip(1, 2)

// OK: Works but it’s unacceptable to require this on caller's side
let x = Something<(Int, Int)>.zip(1, 2)

Thank you for your time!

Solution

The reason you’re seeing this is that there’s nothing in this call:

let y = Something.zip(1, 2)

That tells Swift what T should be.

Your call implicitly specifies what A and B should be, and specifies the method should return Something<A, B>. But that Something<A, B> is not connected to Something<T>.

In fact, nothing at all in your call is connected to T; T is left unspecified, so it could be anything. I mean that literally—you can actually put (nearly) any random type in the angle brackets after Something and it’ll work exactly the same:

let y = Something<UICollectionViewDelegateFlowLayout>.zip(1, 2)

What you would really like to do is somehow specify that T has to be a tuple and the two parameters are of the same types as the tuple’s elements. Unfortunately, Swift doesn’t currently have the features needed to properly do that. If the language were more sophisticated, you could say something like this:

extension<A, B> Something where T == (A, B) {
    class func zip(a: A, _ b: B) -> Something {
        let initial = (a, b)
        return Something(initial: initial)
    }
}

But for now, you’ll have to make do with this horrible hack, which works by meaninglessly reusing the T type parameter so that it’s no longer at loose ends:

extension Something {
    class func zip<B>(a: T, _ b: B) -> Something<(T, B)> {
        let initial = (a, b)
        return Something<(T, B)>(initial: initial)
    }
}

Answered By – Becca Royal-Gordon

Answer Checked By – Dawn Plyler (BugsFixing Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *