Variadic Zips... are they possible yet?

I’m playing around with various forms of zip for a class I’m teaching. I can write the following:

func zip<A, R, S>(
_ f: @escaping (A) async -> R,
_ g: @escaping (A) async -> S
) -> (A) async -> (R, S) {
    { a in await (f(a),g(a)) }
}

and I can make it variadic

func variadicZip<A, each R>(
    _ fs: repeat @escaping (A) -> each R
) -> (A) -> (repeat each R) {
    { a in (repeat (each fs)(a)) }
}

and I can write the inout form:

func nonCopyingZip<A: ~Copyable, R, S>(
    _ f: @escaping (borrowing A, inout R) -> Void,
    _ g: @escaping (borrowing A, inout S) -> Void
) -> (borrowing A, inout (R, S)) -> Void {
    { a, pair in
        f(a, &pair.0)
        g(a, &pair.1)
    }
}

the problem is that I can't seem to make this form variadic. Every attempt I make doesn't just fail to compile, it crashes the compiler. Example:

func variadicNonCopyingZip<A: ~Copyable, each R>( // escapes, serializes
    _ fs: repeat @escaping (borrowing A, inout each R) -> Void
) -> (borrowing A, inout (repeat each R)) -> Void {
    { a, outputs in
        let pairs = repeat (each fs, each outputs)
        for var pair in repeat each pairs {
            pair.0(a, &pair.1)
        }
    }
}

This yields:

1.	Apple Swift version 6.2 (swift-6.2-RELEASE)
2.	Compiling with the current language version
3.	While evaluating request ASTLoweringRequest(Lowering AST to SIL for file "...")
4.	While silgen emitFunction SIL function "@$s20Tests21variadicNonCopyingZipyyx_q_q_Qp_tztcyx_q_ztcq_QpRv_Ri_zr0_lF".
 for 'variadicNonCopyingZip(_:)' (at ...)
5.	While silgen closureexpr SIL function "@$s20ParallelizationTests21variadicNonCopyingZipyyx_q_q_Qp_tztcyx_q_ztcq_QpRv_Ri_zr0_lFyx_q_q_Qp_tztcfU_".
 for expression at [... line:38:5 - line:43:5] RangeText="{ a, outputs in
        let pairs = repeat (each fs, each outputs)
        for var pair in repeat each pairs {
            pair.0(a, &pair.1)
        }
    "

Question: should this be doable?

1 Like

and. fwiw, I get similar crashes trying to make these functions be async. This was just the simplest form that shows the issue. I would also add that the various AI tools don't seem to be able to offer anything useful either.

Hi rvsrvs, I’m investigating another crash in the compiler around packs (https://github.com/swiftlang/swift/issues/84490). I think this issue is different but it could have a similar related cause. I think this issue is worth a separate report.

1 Like

I also made an issue for the crash related to variadic zips:

1 Like

Here ya' go: https://github.com/swiftlang/swift/issues/84568

3 Likes

The bigger question remains... I know that the code above is wrong, but I can't quite figure out the syntax for inout parameters like this that should work of if, at this stage of swift's development, this should be doable at all. Any suggestions appreciated.

There are workarounds: Variadic generic zip · GitHub

so, if I'm understanding that pattern, the key is going to be to create a nominal type to use internal to the function which will allow me use mutating instead of directly acting on the inout tuple being passed in?

Yes it avoids direct mutation of the parameter pack. There are quite a few open issues related to mutable parameter packs in the swift repo, a few of which I’ve filed myself

hmm. now you guys are making me go check out what happens with borrowing and consuming. nothing good is my guess.

Very clever! I like it but... unless I am mistaken, its making a copy of output back into itself when you create the tuple. I'm going to spend some time today seeing if I can follow this recipe for manipulating the inputs and create a form where a tuple of MutableSpans are passed in and where the functions operate on MutableSpan instead of directly on the inout tuple.

It would be nice if the authors of MutableSpan provided a mechanism for getting the spans of a tuple directly. (hint hint :) )

I suspect that I'm going to need to make a variadically generic, nominal Tuple type to do this.

I tried this but can't make this work with .map for example: The error is quite funny:

Directly handing + to .map gives the following:

func test() {
    var a: [Double] = [1, 2, 3]
    var b: [Double] = [4, 5, 6]
    
    newzip(a, b).map(+)
    // Pack reference 'ZipSequence<[Double], [Double]>.Element' (aka 'Double, Double') can only appear in pack expansion 
}

But when digging a little deeper it's quite ironic: (Cannot convert value of type to the same type)

func test() {
    var a: [Double] = [1, 2, 3]
    var b: [Double] = [4, 5, 6]
    
    func add(parameters: (Double, Double)) -> Double {
        parameters.0 + parameters.1
    }
    
    newzip(a, b).map(add)
    // Cannot convert value of type '((Double, Double)) -> Double' to expected argument type '(ZipSequence<[Double], [Double]>.Element) -> Double' (aka '((Double, Double)) -> Double')
}

This would be very useful to me however. Hope this becomes supported!

1 Like