Skip to main content
Version: 2.x

These

These[A, B] is a data type that models a value that can be either a Left with a value of type A, a Right with a value of type B, or a Both with both a value of type A and a value of type B.

sealed trait These[+A, +B] {
case class Left[+A](a: A) extends These[A, Nothing]
case class Right[+B](b: B) extends These[Nothing, B]
case class Both[+A, +B](a: A, b: B) extends These[A, B]
}

You can think of These as like Either except that it has one additional case. Whereas an Either contains either an A or a B but never both, a These can contain both an A and a B.

A simple example of a situation where These might arise is in merging two upstreams in a streaming application. We could be signaled when at least one upstream has data for us and could have a situation where either only the left upstream has data for us, only the right upstream has data for us, or both upstreams have data for us.

Another example of using the These data type is in implementing a variant of the zipAll operator on collections.

When we zip two collections, if the two collections have different sizes the default zipWith operator will just drop the "extra" elements from the larger collection.

import zio.Chunk

val left: Chunk[Int] =
Chunk(1, 2, 3)
// left: Chunk[Int] = IndexedSeq(1, 2, 3)

val right: Chunk[Int] =
Chunk(4, 5, 6, 7, 8)
// right: Chunk[Int] = IndexedSeq(4, 5, 6, 7, 8)

val zip: Chunk[Int] =
left.zipWith(right)(_ + _)
// zip: Chunk[Int] = IndexedSeq(5, 7, 9)

We can use the zipAll operator to allow the caller to specify how they want to handle the situation where the two collections have different sizes. Normally the signature would look something like this:

def zipAllWith[A, B, C](
as: Chunk[A],
bs: Chunk[B]
)(left: A => C, right: B => C, both: (A, B) => C): Chunk[C] =
???

Now the both function will be called as long as there are elements of both collections to zip together. If the left collection is longer then the left function will be called for the extra elements of the left collection, and if the right collection is longer then the right operator will be called for the extra elements of the right collection.

This works and is basically the signature of the zipAllWith operator on Chunk, but there is something a little less than ideal here that now we need to spread out the logic for handling these cases across three different functions. It would be nice if we could describe this as a single function.

With These we can do just that. We could rewrite the signature of zipAllWith like this:

def zipAllWith[A, B, C](
as: Chunk[A],
bs: Chunk[B]
)(f: These[A, B] => C): Chunk[C] =
???

Now the function f bundles up all the logic that was previously in left, right, and both. The caller gets to provide a single function that handles all three of these cases.

The These data type is a relatively modest one, but it captures a situation that can arise where we can have both values in addition to one or the other. Otherwise, we would have to create our own data type for each of these situations, but now we can simply use These.