简体   繁体   中英

Generic factory method and type inference

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!

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)
    }
}

In short explanation, you use generics not correct. It's not realtime feature, it's precompile thing. If you need to make abstract class from generic input values, see and do like this:

class Abstract<T> {
    init(value: T) {
        print("inputed value: \(value)")
    }
}

class Something {
    class func zip<A, B>(value: A, value2: B) -> Abstract<(A, B)> {
        print("Something.zip", value, value2)

        return Abstract<(A, B)>(value: (value, value2))
    }
}

Something.zip(5, value2: 40) // "inputed value: (5, 40)"

T simply isn't related to A and B in that way and so can't be inferred.

Eg.

let z = Something<(String, String)>.zip(1, 2)
let z2 = Something<AnyObject>.zip(1, 2)

work just fine to return a Something<(Int, Int)>

You can introduce type inference for your case like this:

final class Something<T> {

    let value: T

    init(initial: T) {
        value = initial
    }

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

}

let y = Something.zip(1, 2) //works

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM