# Introduction

In addition to abstractions for concrete types, ZIO Prelude provides a set of functional abstractions to describe the common structure of *parameterized types*.

A parameterized type is a type that is parameterized on one or more other types. For example, a `List[A]`

is parameterized on the element type `A`

.

When we are describing the common structure of parameterized types we are talking about the structure of the parameterized type without knowing about the type it is parameterized on. For example, consider the following two abstractions to describe associative ways of combining two values of a data type.

`trait Associative[A] {`

def combine(left: => A, right: => A): A

}

trait AssociativeBoth[F[_]] {

def both[A, B](left: => F[A], right: => F[B]): F[(A, B)]

}

The `Associative`

abstraction describes a way of associatively combining two concrete data types.

The syntax is relatively straightforward here. The `Associative`

is parameterized on a type `A`

that has an associative `combine`

operator.

When we define an instance of `Associative`

we know the type of the values we are combining and can use that information in implementing the `combine`

operator. For example we know we have two `Int`

values and can combine them by adding them together.

`implicit val IntAssociative: Associative[Int] =`

new Associative[Int] {

def combine(left: => Int, right: => Int): Int =

left + right

}

// IntAssociative: Associative[Int] = repl.MdocSession$MdocApp$$anon$1@42ba923

In contrast, the `AssociativeBoth`

abstraction describes a way of associatively combining two parameterized types.

The syntax here may look a little new. Instead of being parameterized on an `A`

, `AssociativeBoth`

is parameterized on an `F[_]`

.

There is nothing special about the `F`

here, just like the `A`

in `Associative`

it is just a placeholder for some type. By convention we start with `A`

for concrete types and `F`

for parameterized types but we can call these types whatever we want.

What is important is the `[_]`

after `F`

. This tells the Scala compiler that this is a parameterized type, such as a `List`

rather than a `List[Int]`

.

The importance of this becomes clear when we define an instance of `AssociativeBoth`

because we do not know anything about the types `A`

and `B`

that the `left`

and `right`

values are parameterized on. This prevents us from doing anything with the `A`

and `B`

values that requires knowledge of their types and so essentially forces us to work exclusively at the level of the `F`

structure.

To see this, consider how we would implement an instance of `AssociativeBoth`

for `List`

.

`implicit val ListAssociativeBoth: AssociativeBoth[List] =`

new AssociativeBoth[List] {

def both[A, B](left: => List[A], right: => List[B]): List[(A, B)] =

left.flatMap(a => right.map(b => (a, b)))

}

// ListAssociativeBoth: AssociativeBoth[List] = repl.MdocSession$MdocApp$$anon$2@4d59f640

Notice how the `AssociativeBoth`

instance is parameterized on `List`

rather than a list of any specific type. This is important because it says the `ListAssociativeBoth`

instance knows how to combine any two lists in an associative way, not just two lists of some specific type.

What would the implementation of such a way of combining look like? It would not be able to use any information about the `A`

or `B`

values, because they could be anything, so it would have to combine them in a way that worked for any `A`

and `B`

values.

The `AssociativeBoth`

abstraction does this by combining the `A`

and `B`

values into a tuple. We will see later that this is one of two fundamental ways that we can combine two values in a generic way.

In our implementation for `List`

we return the Cartesian product of the two lists, which is an associative operation after reassociating nested tuples.

We'll talk more about `AssociativeBoth`

in the section on that abstraction. For now the goal of this discussion is not to discuss `AssociativeBoth`

in detail but to get a sense of what it means to describe the structure of a parameterized type instead of a concrete one.

Just like for concrete types, the abstractions for parameterized types fall into two categories.

## Properties Of Parameterized Types

The first set of abstractions define properties of single values of a parameterized type `F[A]`

. All of them describe the fundamental nature of the `A`

parameter with respect to `F`

.

### Invariant

The first relationship that `A`

can have to `F`

is that `F`

both produces and consumes `A`

values. For example, a `JsonCodec[A]`

both produces `A`

values by turning JSON into `A`

values and consumes `A`

values by turning `A`

values into JSON.

Parameterized types that both produce and consume `A`

values are *invariant* in the `A`

type parameter and we describe them with the `Invariant`

abstraction.

An invariant type `F[A]`

can be transformed into an `F[B]`

with an `Equivalence[A, B]`

using the `invmap`

operator. Conceptually, if we have a data type that produces and consumes `A`

values we can always turn it into a data type that produces and consumes `B`

values by transforming all inputs into `A`

values and all outputs back to `B`

values with the equivalence relationship.

This is useful to "lift" equivalence relationships into the context of a parameterized type. For example, if we have defined an equivalence between new and old versions of our data model we can use `Invariant`

to convert the `JsonCodec`

for the old data model into a `JsonCodec`

for the new data model.

### Covariant

The second relationship that `A`

can have to `F`

is that `F`

produces `A`

values but does not consume them. Data types that produce `A`

values may either just contain existing `A`

values, like a `Chunk`

, or potentially produce `A`

values at some point in the future, like a `ZIO`

.

Parameterized types that produce but do not consume `A`

values are *covariant* in the `A`

type parameter and we describe them with the `Covariant`

abstraction.

A covariant type `F[A]`

can be transformed into an `F[B]`

with a function `A => B`

using the `map`

operator. Conceptually, if we have a data type that produces `A`

values then we can create a data type that produces `B`

values simply by taking each output and transforming it with the function.

This is useful to allow us to transform the output of a covariant type to build richer data pipelines. For example, if we had a function that decrypted some data and a `ZIO`

effect that loaded the encrypted data from a file we could return a new `ZIO`

effect that produced the decrypted data, which we could then compose with the rest of our program.

### Contravariant

The third relationship that `A`

can have to `F`

is that `F`

consumes `A`

values but does not produce them. Examples of data types that consume values of one or more types include Scala functions with respect to their input, `ZIO`

with respect to its environment type, and `ZSink`

with respect to stream elements.

Parameterized types that consume but do not produce `A`

values are *contravariant* in the `A`

type parameter and we describe them with the `Contravariant`

abstraction.

A contravariant type `F[A]`

can be transformed into an `F[B]`

with a function `B => A`

using the `contramap`

operator. Conceptually, if we have a data type that consumes `A`

values then we can create a data type that consumes `B`

values simply by transforming each `B`

value into an `A`

value with the function before feeding it to the original data type.

This is useful to allow us to transform the input of a contravariant type to build richer data pipelines. For example, if we have a sink that takes chunks of bytes and writes them to a file we could transform it into a sink that takes strings and writes them to a file by providing a function to transform strings into bytes.

### ForEach

The `ForEach`

abstraction builds on the `Covariant`

abstraction by describing a data type that not only produces `A`

values but actually contains zero or more `A`

values as opposed to merely being able to potentially generate them at some point in the future. For example, a `Chunk`

has zero or more existing `A`

values and so has a `ForEach`

instance defined for it whereas a `ZIO`

merely may produce an `A`

value at some point in the future.

A data type with a `ForEach`

instance contains zero or more existing `A`

values so we can iterate over it, potentially transforming its values while maintaining its structure using an operator like `ZIO.foreach`

. We can also tear it down entirely and reduce it to a summary value with an operator like `foldLeft`

.

This ability to get an `A`

value out of the data type only exists because `ForEach`

describes a data type where the `A`

values already exist. If we had a data type like `ZIO`

then we would not be able to get an `A`

value out of it because a `ZIO`

is only a description of a workflow that may produce an `A`

value when it is run.

The `ForEach`

abstraction is one of the most practically useful functional abstractions in ZIO Prelude. Most collection operators can be defined in terms of `ForEach`

and especially in combination with the abstractions for describing ways of combining concrete types it provides very powerful ways of working with collections.

### NonEmptyForEach

The `NonEmptyForEach`

abstraction further builds on `ForEach`

to describe a parameterized type that contains one or more existing `A`

values. For example, a `NonEmptyChunk`

is guaranteed to contain at least one `A`

value.

Because a data type with a `NonEmptyForEach`

instance must always contain at least one existing `A`

value we can define certain operators for it that would not be safe to define for a data type that might not contain any `A`

values. For example, we can define a `reduce`

operator that reduces the collection to a summary value with an associative operator whereas with a `ForEach`

instance we can only define a `fold`

operator that also requires an identity value to handle the empty case.

## Combining Parameterized Types

The second set of abstractions describe ways of combining parameterized types.

Because of the additional structure of parameterized types there are three separate ways that we can combine values of parameterized types, each of which may satisfy properties of associativity, commutativity, and identity.

The first way of combining an `F[A]`

and an `F[B]`

is to return both `A`

and `B`

values. This corresponds to some notion of doing both things, though what exactly this means will depend on the parameterized type and the combining operation.

This way of combining is described by the `AssociativeBoth`

, `CommutativeBoth`

, and `IdentityBoth`

functional abstractions.

The second way of combining an `F[A]`

and an `F[B]`

is to return either an `A`

or a `B`

value. This corresponds to some notion of choosing which value to return, though again what exactly this means and how we choose will depend on the parameterized type and the combining operation.

This way of combining is described by the `AssociativeEither`

, `CommutativeEither`

, and `IdentityEither`

functional abstractions.

The third way of combining actually combines two layers of `F`

values, converting an `F[F[A]]`

into an `F[A]`

. This corresponds to some notion of evaluating the outer layer and then evaluating the inner layer.

This way of combining is described by the `AssociativeFlatten`

and `IdentityFlatten`

functional abstractions.

### AssociativeBoth

The `AssociativeBoth`

abstraction describes a parameterized type `F[A]`

with an associative operator `both`

that can combine a value of type `F[A]`

and a value of type `F[B]`

into a value of type `F[(A, B)]`

. This is described by the `zip`

operator on `ZIO`

and corresponds to running the left value and then running the right value.

### CommutativeBoth

`CommutativeBoth`

describes a way of combining an `F[A]`

and an `F[B]`

to produce an `F[(A, B)]`

that is both associative and commutative.

This is described by the `zipPar`

operator on `ZIO`

and corresponds to running the left value and the right value in parallel, since this is the only way that the order will not matter as required by the commutative property.

### IdentityBoth

The `IdentityBoth`

abstraction describes a way of combining an `F[A]`

and an `F[B]`

to produce an `F[(A, B)]`

that is both associative and has an identity value of type `F[Any]`

. In ZIO the identity value is value is `ZIO.unit`

since zipping any value with unit is equivalent to returning the original value unchanged

### AssociativeEither

The `AssociativeEither`

abstraction describes a parameterized type `F[A]`

with an associative operator `either`

that can combine a value of type `F[A]`

and a value of type `F[B]`

into a value of type `F[Either[A, B]]`

. This is described by the `orElseEither`

operator on `ZIO`

and corresponds to running the left value, but if it fails then running the right value.

### CommutativeEither

`CommutativeEither`

builds on `AssociativeEither`

by describing an operator that combines an `F[A]`

and and `F[B]`

to produce an `F[Either[A, B]]`

in a way that is both associative and commutative. In ZIO this corresponds to the `raceEither`

operator which runs both effects concurrently and returns the first to succeed, since running both effects concurrently is the only way that the order will not matter as required by the commutative property.

### IdentityEither

The `IdentityEither`

abstraction describes a way of combining an `F[A]`

and an `F[B]`

to produce an `F[Either[A, B]]`

that is both associative and has an identity value of type `F[Nothing]`

. `ZIO`

does not have such an identity value since it is not possible to fail without any error but conceptually such an identity would correspond to a failure that did not contain any information.

### AssociativeFlatten

The `AssociativeFlatten`

abstraction describes the ability to flatten two layers of `F`

structure in an associative way. In `ZIO`

this corresponds to the `flatten`

operator, and in combination with the `Covariant`

abstraction the `flatMap`

operator, corresponding to running one `ZIO`

value and using its result to generate another `ZIO`

value and running that value.

### IdentityFlatten

The `IdentityFlatten`

abstraction describes an identity value of type `F[Any]`

with respect to the associative flattening operation defined by `AssociativeFlatten`

. In `ZIO`

this corresponds to `ZIO.unit`

and is the same as the identity value defined by the `IdentityBoth`

instance.