Generic factory method and type inference


(Rudolf Adamkovič) #1

Hello there!

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 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!

R+


(Dmitri Gribenko) #2

The compiler can't infer T from your call. T and (A, B) are three
separate type parameters.

Dmitri

···

On Thu, Mar 17, 2016 at 6:58 AM, Rudolf Adamkovič <swift-users@swift.org> wrote:

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

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

--
main(i,j){for(i=2;;i++){for(j=2;j<i;j++){if(!(i%j)){j=0;break;}}if
(j){printf("%d\n",i);}}} /*Dmitri Gribenko <gribozavr@gmail.com>*/


(Brent Royal-Gordon) #3

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

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. The return type of the `zip` method is not connected to T; you can actually put any random type in the angle brackets after Something:

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

Unfortunately, Swift doesn't currently have the features needed to properly connect `T` to the return type. 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:

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

Hope this helps,

···

--
Brent Royal-Gordon
Architechies


(Chris Lattner) #4

I know that your root problem was already answered, but FYI swift master makes it much more clear what is wrong:

test.swift:1:19: error: generic parameter 'T' could not be inferred
let y = Something.zip(1, 2)
                  ^
test.swift:1:13: note: 'T' declared as parameter to type 'Something'
final class Something<T> {
            ^

-Chris

···

On Mar 17, 2016, at 6:58 AM, Rudolf Adamkovič via swift-users <swift-users@swift.org> wrote:

How come I can’t call zip without explicitly specifying 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!


(Rudolf Adamkovič) #5

Thanks for all the information. This was a cross-post from Stack Overflow:

http://stackoverflow.com/questions/36061561/generic-factory-method-and-type-inference

If you are a Stack Overflow user, you can copy the answer there and I will accept it as correct.

Either way, thanks for the explanation!

R+

···

On 17 Mar 2016, at 16:51, Brent Royal-Gordon <brent@architechies.com> wrote:

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

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. The return type of the `zip` method is not connected to T; you can actually put any random type in the angle brackets after Something:

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

Unfortunately, Swift doesn't currently have the features needed to properly connect `T` to the return type. 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:

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

Hope this helps,
--
Brent Royal-Gordon
Architechies