Transforming multiple effects

beginner

Transforming with an effecful function

Problem

I have:

  • A value of type F<A>.
  • A function of type (A) -> G<B>.

I want to have:

  • A value of type F<G<B>>.

Solution

Despite having two different effects, they do not interact with each other. This is a regular map transformation, as provided in the Functor type class.

Example

Consider having an array of values of type String, and trying to parse each value to an Int. Parsing is an effectul operation as it can return a value, if the number can be parsed, or none, otherwise. In this case, our F is Array and our G is Option:

func parseInt(_ str: String) -> Option<Int> {
    Int(str).toOption()
}
//             F<A>
let array: Array<String> = ["1", "5", "3"]

//                    F  <  G   < B >>
let arrayOfOptions: Array<Option<Int>> = array.map(parseInt)

Swapping effects

Problem

I have:

  • A value of type F<G<A>>.

I want to have:

  • A value of type G<F<A>>.

Solution

If we want to swap the relative order of the applied effects, we can use the sequence function, provided in the Traverse type class.

Example

If we take the previous example, where we have an Array<Option<Int>> and we want to have an Option<Array<Int>>, which will be present if all elements in the array are present, or none if any of the elements is none, we need to apply the sequence` function:

// Returns Option.some([1, 5, 3])
let optionArray: Option<Array<Int>> = arrayOfOptions.sequence()^

If any of the elements of the array is none, the result of sequence is none:

let arrayWithNone: Array<Option<Int>> = [.some(1), .none()]
let noneArray: Option<Array<Int>> = arrayWithNone.sequence()^

Transforming and swapping with an effectful function

Problem

I have:

  • A value of type F<A>.
  • A function (A) -> G<B>.

I want to have:

  • A value of type G<F<B>>.

Solution

Looking at the type signatures we have above, this pattern is equivalent to doing map and then sequence. There is a function that does this in a single step: traverse, provided by the Traverse type class.

Example

We can run the previous examples in a single step:

let numbers = ["1", "5", "3"]
// Returns Option.some([1, 5, 3])
let parsedNumbers: Option<[Int]> = numbers.traverse(parseInt)^

let nonNumbers = ["9", "abc", "3"]
// Returns Option.none()
let notParsedNumbers: Option<[Int]> = nonNumbers.traverse(parseInt)^