swiftcombine

Can't use eraseToAnyPublisher after a collect "Type of expression is ambiguous without a type annotation"


I have two deferred publishers emitting an array of Ints. They are Futures under the hood of that maters.

I would like to have these two publishers "run" in parallel so lets say p1 returns [1, 2, 3] and p2 [4, 5, 6] I would like to be able to get [4, 5, 6, 1, 2, 3] or [1, 2, 3, 4, 5, 6]

Here is my code making an attempt

let p1: AnyPublisher<[Int], Never>
let p2: AnyPublisher<[Int], Never>
let combined: AnyPublisher<[Int], Never> = Publishers.MergeMany([p1, p2])
            .collect()
            .eraseToAnyPublisher()

This gives me the error Type of expression is ambiguous without a type annotation Notably the type after collect() is of type Publishers.Collect<Publishers.MergeMany<AnyPublisher<[Int], Never>>> so I figured I could just erase it to be any publisher.

As near as I can tell collect does what I want. But since I want to get out the publisher and not sink I am not sure how to get around this.

I also tried

    var res: AnyPublisher<[Reminder], Never>
    Publishers.MergeMany([singleReminderPublisher(), singleReminderPublisher(), singleReminderPublisher()])
        .collect()
        .assign(to: &res)
    return res

but that gives me Cannot convert value of type 'AnyPublisher<[Reminder], Never>' to expected argument type 'Published<[AnyPublisher<[Reminder], Never>.Output]>.Publisher'. This is certainly a more useful error message but I still don't know what to do with it.

Edit: Thanks to @Joakim's comment about trying to get Publisher<[[Int]], Never> I was inspired with one solution

Publishers.Zip3(p1, p2, p3)
            .map( { r1, r2, r3 in
                var combine: [Int] = []
                combine.append(contentsOf: r1)
                combine.append(contentsOf: r2)
                combine.append(contentsOf: r3)
                return combine
            })
            .eraseToAnyPublisher()

The major downside of this to me is that it is not agnostic to the number of publishers and there's extra storage use that could matter if these objects were larger or there were more of them. I would also love to understand why .collect().eraseToAnyPublisher() did not work still.


Solution

  • Keep in mind that Publisher<T, Never> are supposed to publish any number of Ts. The number of elements that the MergeMany publisher will publish is the sum of all the elements that p1 and p2 will publish.

    Since p1 and p2 are Futures, they will each publish one Int array, so MergeMany will publish two Int arrays. You then collect this, converting the publisher that would publish two Int arrays, into a publisher that will only publish both Int arrays as one element. That one element is of type [[Int]], because the two Int arrays are put into another array.

    Your desired result is the concatenation of the two arrays that p1 and p2 publish, so the natural thing to do is to flatten it with a map.

    let combined: AnyPublisher<[Int], Never> = Publishers.MergeMany([p1, p2])
        .collect()
        .map { x in x.flatMap { $0 } }
        .eraseToAnyPublisher()
    

    Alternatively (IMO this looks better), you can flatMap the publisher that produces 2 Int arrays, into a publisher that produces Ints, then collect this Int publisher.

    let combined: AnyPublisher<[Int], Never> = Publishers.MergeMany([p1, p2])
        .flatMap { $0.publisher }
        .collect()
        .eraseToAnyPublisher()