Combining independent computations

beginner

Combining effectful values into a tuple

Problem

I have:

  • Several values of types F<A1> ... F<An>.

I want to have:

  • A single value of type F<(A1, ..., An)>.

Solution

You can zip values of different types inside an effect. zip is an operation in the Applicative type class that will access the internals of each effectful value and provide a single effectful value with a tuple with all of them, if possible.

Example

Consider the following effectful values:

let x: ArrayK<Int> = ArrayK([1, 2, 3])
let y: ArrayK<String> = ArrayK(["A", "B"])
let z: ArrayK<Bool> = ArrayK([true, false])

We want to obtain all the tuples with all possible combinations of the three arrays. We can do it with:

let result: ArrayK<(Int, String, Bool)> = ArrayK.zip(x, y, z)^

Combining effectful values with a function

Problem

I have:

  • Several values of types F<A1> ... F<An>.
  • A function (A1, ..., An) -> B.

I want to have:

  • A single value of type F<B>.

Solution

This case is similar to the above, but transforming the tuple into a single value using a function. The Applicative type class provides the map function to achieve this purpose. It accepts several effectful values and a function. It will access the internals of each effectful value and feed all inputs, if possible, into the provided function.

Example

Consider the following data structure and functions:

struct User {
    let name: String
    let email: String
}

// Gets the name entered by the user in the UI, if any.
func readName(from view: View) -> Option<String>
// Gets the email entered by the user in the UI, if any.
func readEmail(from view: View) -> Option<String>

If we want to create a User object from the information the user has entered in the UI, we need both name and email to be present. Using map, we can achieve this:

let user = Option.map(readName(from: view),
                      readEmail(from: view),
                      User.init)^

If both functions readName and readEmail return a present Option, the User initializer will be called and a User will be created. If any of them returns none, the end result will be none.