Language limitation, or am I not doing this right? `Error: Pack expansion requires that 'each Element' and 'repeat each Element' have the same shape`

I'm trying to write a variadic zip function that will take a function A... -> B, and returns a function Functor<A>... -> Functor<B>. I feel like I'm close, but am running into an error and I'm not sure if this is just a current limitation of the language, or if I'm doing something wrong:

struct Functor<A> {
    let work: () -> A

    func map<B>(_ transform: @escaping (A) -> B) -> Functor<B> {
        Functor<B> { transform(work()) }
    }
}

func zip<each Element>(_ functor: repeat Functor<each Element>) -> Functor<(repeat each Element)> {
    Functor<(repeat each Element)> {
        (repeat (each functor).work())
    }
}
// So far so good

// Here comes trouble...
func zip<each Element, Result>(with transform: @escaping ((repeat each Element)) -> Result) -> (repeat Functor<each Element>) -> Functor<Result> {
    func resultFunc(_ element: repeat Functor<each Element>) -> Functor<Result> {
        zip(repeat each element).map(transform)
    }
    
    return resultFunc // Error: Pack expansion requires that 'each Element' and 'repeat each Element' have the same shape
}

To simplify the example I tried

let foo = resultFunc

And it's inferred to be of type

(repeat Functor<each Element>) -> Functor<Result>

which on the surface, seems to be exactly what I want, but when I explicitly specify the type to be the same as what's inferred

let foo: (repeat Functor<each Element>) -> Functor<Result> = resultFunc 
// Error: Pack expansion requires that 'each Element' and 'repeat each Element' have the same shape

I get the same error.
Is this an inherent limitation of the language right now, or am I not specifying my types correctly? Any way to do what I'm wanting?

What I'm trying to avoid
func zip2<A, B, C>(
  with transform: @escaping (A, B) -> C
) -> (Functor<A>, Functor<B>) -> Functor<C> {
  { zip2($0, $1).map(transform) }
}
func zip3<A, B, C, D>(
  with transform: @escaping (A, B, C) -> D
) -> (Functor<A>, Functor<B>, Functor<C>) -> Functor<D> {
  { zip3($0, $1, $2).map(transform) }
}
// etc... up to Nth arity 

Is this what you want?

func zip<each Element, Result>(transform: @escaping (repeat each Element) -> Result)  -> (repeat Functor<each Element>) -> Functor<Result> {
    { (functors: repeat Functor<each Element>) in
        return Functor<Result> { transform(repeat (each functors).work())  }
    }
}

let result = zip(transform: { (a: Int, b: Int, c: String) in "" })

print(type(of: result)) 
// (Functor<Int>, Functor<Int>, Functor<String>) -> Functor<String>
1 Like

Oh my gosh yes, that compiles! I didn't think it was possible with the closure so I was making that inner func. Thank you! Still curious why the types weren't matching though.
I think I can make this work, but there's an odd difference when I use it as compared with the non-variadic versions.
With the non-variadic versions, I could do this:

func boo(a: Int, b: Double, c: String) -> String {
    "\(a), \(b), \(c)"
}

let functOfBoo = zip3(with: boo)(
    Functor { 4 }, 
    Functor { 4.0 }, 
    Functor { "4" } 
) 

But with the variadic version:

func boo(a: Int, b: Double, c: String) -> String {
    "\(a), \(b), \(c)"
}

let functOfBoo = zip(with: boo)(
    Functor { 4 }, // Error: Cannot pass value pack expansion to non-pack parameter of type 'Functor<Int>'
    Functor { 4.0 }, // Error: Cannot pass value pack expansion to non-pack parameter of type 'Functor<Int>'
    Functor { "4" } // Error: Cannot pass value pack expansion to non-pack parameter of type 'Functor<Int>'
) // The same error is shown for all three parameters, always referring to whatever type was in first position

However I can make the variadic version work by separating the calls, assigning the result function and calling it into two steps:

func boo(a: Int, b: Double, c: String) -> String {
    "\(a), \(b), \(c)"
}

let booFunctFactory = zip(with: boo)
let functOfBoo = booFunctFactory(
    Functor { 4 }, 
    Functor { 4.0 }, 
    Functor { "4" } 
) 
// This works

I can make this work, but I'm curious about why this is needed and if there's a way to recover the easier use at the callsite?

I would expect this to work :thinking: @hborla could you take a look here, please? Is this intended or something that the compiler should be able to handle?

1 Like