Last week we concluded our 3-part introductory series to the `zip`

function. In the second episode of that series we saw that many types support `zip`

-like operations, even though we don’t typically think of those types in that way. For example, two `Result`

values can be zipped up, and even two functions with the same input can be zipped. At the end of the episode we provided some exercises to help viewers dive a little deeper into what `zip`

had to offer, and this week we solve most of those problems!

Exercise 1Can you make the

`zip2`

function on our`Parallel`

type thread safe?

You can use GCD’s `DispatchGroup`

to precisely coordinate two units of work so that we are notified when they are both finished, and we avoid the racing problems we mentioned in the episode. Here’s how we can use it:

```
func zip2<A, B>(
_ pa: Parallel<A>, _ pb: Parallel<B>
) -> Parallel<(A, B)> {
return .init { callback in
let group = DispatchGroup()
var a: A!
var b: B!
group.enter()
pa.run { a = $0; group.leave() }
group.enter()
pb.run { b = $0; group.leave() }
group.notify(queue: .main) {
callback((a, b))
}
}
}
```

Here we have chosen to notify the group’s completion on the main thread, but a more robust `Parallel`

implementation might make that customizable. It’s interesting to think of `DispatchGroup`

‘s as just GCD’s version of a `zip`

operation. We feel that `zip`

is a little more expressive and composable than `DispatchGroup`

’s are.

Exercise 2Generalize the

`Parallel`

type to a type that allows returning values other than`Void`

:`struct F4<A, R> { let run: (@escaping (A) -> R) -> R }`

Define

`zip2`

and`zip2(with:)`

on the`A`

type parameter.

Although this `F4`

type is closely related to `Parallel`

, its `zip`

implementation is a bit different. If we try to repeat what we did for `Parallel`

above we will quickly run into the problem that we must return an `R`

value from each of the `fa.run`

and `fb.run`

functions, and we don’t have any such value. Instead, we can take the approach we first took in episode two when trying to define `zip`

on `Parallel`

, and we will nest the `run`

blocks:

```
func zip2<A, B, R>(
_ fa: F4<A, R>, _ fb: F4<B, R>
) -> F4<(A, B), R> {
return .init { callback in
fa.run { a in
fb.run { b in
callback((a, b))
}
}
}
}
```

Exercise 3Find a function in the Swift standard library that resembles the function above. How could you use

`zip2`

on it?

Have you ever looked at the method signature of `withUnsafeBytes`

? I mean, *really* looked at it? Here it is:

```
func withUnsafeBytes<Result>(
_ body: (UnsafeRawBufferPointer) throws -> Result
) rethrows -> Result
```

There’s a lot of noise in this function signature, so let’s clear it up a bit by removing all the `throws`

stuff and shortening the `Result`

generic name:

```
func withUnsafeBytes<R>(
_ body: (UnsafeRawBufferPointer) -> R
) -> R
```

OK interesting. If we focus on just the shape of this, we see it’s a function of the form: `((UnsafeRawBufferPointer) -> R) -> R`

. That is basically `F4<UnsafeRawBufferPointer, R>`

as defined in exercise 2!

Unfortunately, we cannot just wrap these functions up in an `F4`

value, and then start zipping them. The `throws`

and `rethrows`

annotations make these function signatures distinct from that of `F4`

. Instead of littering our `F4`

type with `throws`

annotations, let’s just define a specialized `zip2`

for functions of the form `((A) throws R) throws R`

:

```
func zip2<A, B, R>(
_ f: @escaping ((A) throws -> R) throws -> R,
_ g: @escaping ((B) throws -> R) throws -> R
) -> (@escaping (A, B) throws -> R) throws -> R {
return { callback in
try f { a in
try g { b in
try callback(a, b)
}
}
}
}
```

OK, now that we have `zip`

defined, how can we use it? Well, imagine we had some C function that was imported that operates on `UnsafeRawBufferPointer`

values. Then we could `zip`

up the `withUnsafeBytes`

of two arrays and invoke that C function:

```
var xs = [1, 2, 3]
var ys = [4, 5, 6]
func someCFunction(
_ x: UnsafeRawBufferPointer,
_ y: UnsafeRawBufferPointer
) -> Int {
// Do something with the pointers here...
return 1
}
try (zip2(xs.withUnsafeBytes, ys.withUnsafeBytes)) { x, y in
someCFunction(x, y)
}
```

This allows you to clearly express that you want to grab the underlying bytes of the array storage and invoke a C function with those contents. The alternative way is to nest multiple calls to `withUnsafeBytes`

, which leads to highly indented code and “callback hell”:

```
try xs.withUnsafeBytes { x in
try ys.withUnsafeBytes { y in
someCFunction(x, y)
}
}
```

Notice that we had to nest two layers deep, and if we were to need more unsafe bytes the indentation would continue to grow. The `zip`

method for handling unsafe bytes is shorter and more expressive.

This exercise consists of multiple parts, and aims to explore what happens when you nest two types that each support a `zip`

operation.

Exercise 4.1Consider the type

`[A]? = Optional<Array<A>>`

. The outer layer`Optional`

has`zip2`

defined, but also the inner layer`Array`

has a`zip2`

. Can we define a`zip2`

on`[A]?`

that makes use of both of these zip structures? Write the signature of such a function and implement it.

We accidentally put this exercise in both part 1 and 2 episodes, so we already solved it before!

Exercise 4.2Consider the type

`[Validated<A, E>]`

. We again have have a nesting of types, each of which have their own`zip2`

operation. Can you define a`zip2`

on this type that makes use of both`zip`

structures? Write the signature of such a function and implement it.

Let’s start by writing the signature of the function we want to implement:

```
func zip2<A, B, E>(
_ a: [Validated<A, E>], _ b: [Validated<B, E>]
) -> [Validated<(A, B), E>] {
fatalError()
}
If we perform a `zip` on the arrays, then we will get an array of tuples, where each component of
the tuple is a validated value. We can then `map` into _that_ array, and apply `zip` on the
validated values:
```swift
func zip2<A, B, E>(
_ a: [Validated<A, E>], _ b: [Validated<B, E>]
) -> [Validated<(A, B), E>] {
return zip2(a, b).map(zip2)
}
```

This allows us to express the idea of taking two lists of validated values, and combining them into one list, where if there are any errors they combine, and otherwise we get a tuple of the valid values.

Exercise 4.3Consider the type

`Func<R, [A]>`

. Again we have a nesting of types, each of which have their own`zip2`

operation. Can you define a`zip2`

on this type that makes use of both structures? Write the signature of such a function and implement it.

Let’s start by writing the signature of the function we want to implement:

```
func zip2<A, B, R>(
_ a: Func<R, [A]>, _ b: Func<R, [B]>
) -> Func<R, [(A, B)]> {
fatalError()
}
```

We know how to perform `zip`

on `Func`

values, so we could start with that:

```
func zip2<A, B, R>(
_ a: Func<R, [A]>, _ b: Func<R, [B]>
) -> Func<R, [(A, B)]> {
zip2(a, b) // Func<R, ([A], [B])>
fatalError()
}
```

Also, `map`

on `Func`

operates on the second type parameter, in this case `([A], [B])`

, and that’s precisely the shape we like for `zip`

, so sounds like we can do those two operations together:

```
func zip2<A, B, R>(
_ a: Func<R, [A]>,
_ b: Func<R, [B]>
) -> Func<R, (A, B)> {
return zip2(a, b).map(zip2)
}
```

Exercise 4.4Finally, consider the type

`Parallel<Validated<A, E>>`

. Yet again we have a nesting of types, each of which have their own`zip2`

operation. Can you define a`zip2`

on this type that makes use of both structures? Write the signature of such a function and implement it.

Well, now I think you are probably seeing the pattern, but let’s go through the steps anyway. Let’s start by writing out the signature:

```
func zip2<A, B, E>(
_ a: Parallel<Validated<A, E>>,
_ b: Parallel<Validated<B, E>>
) -> Parallel<Validated<(A, B), E>> {
fatalError()
}
```

If we `zip`

both of these parallel values together, we will arrive at another parallel value that holds two validated values. So, we can `map`

into that new parallel value, and then `zip`

the two validated values inside it. This implements the function:

```
func zip2<A, B, E>(
_ a: Parallel<Validated<A, E>>,
_ b: Parallel<Validated<B, E>>
) -> Parallel<Validated<(A, B), E>> {
zip2(a, b).map(zip2)
}
```

This allows us to simultaneously perform two parallel tasks and two validations at the same time, bringing the results into one single value. Very powerful!

Exercise 5Do you see anything common in the implementation of all of the functions in the previous exercise? What this is showing is that nested zippable containers are also zippable containers because

`zip`

on the nesting can be defined in terms of zip on each of the containers.

Every implementation of `zip`

on nested containers looked identical. We first `zip`

the outer containers, then `map`

on that container with the `zip`

on the inner containers. What we are seeing here is that nested zippable containers are *always* zippable themselves. Unfortunately Swift does not have the type level features that allows us to express this algorithm generically, and so we are forced to write the `zip2(lhs, rhs).map(zip2)`

boilerplate *every* time we want to nest two zippable containers. Maybe someday this will be better!

And that’s the solutions to the second part of our 3 part introductory series to `zip`

! If you thought those were too easy, be sure to check out the exercises to part 3 too. Until next time!