Fold

Note: Don’t confuse this with the collection aggregate operation fold.

A Fold is an optic that can see into a structure and get 0 to N foci. It is a generalization of an instance of Foldable.

Creating a Fold can be done by manually defining foldMap.

import arrow.core.*
import arrow.optics.*
import arrow.typeclasses.*
import arrow.core.extensions.*

fun <T> nullableFold(): Fold<T?, T> = object : Fold<T?, T> {
    override fun <R> foldMap(M: Monoid<R>, s: T?, f: (T) -> R): R =
        s?.let(f) ?: M.empty()
}

Or you can get a Fold from any existing Foldable.

import arrow.core.extensions.nonemptylist.foldable.*

val nonEmptyIntFold: Fold<NonEmptyListOf<Int>, Int> = Fold.fromFoldable(NonEmptyList.foldable())

Fold has an API similar to Foldable, but because it’s defined in terms of foldMap, there are no associative fold functions available.

nullableFold<Int>().isEmpty(null)
// true
nonEmptyIntFold.combineAll(Int.monoid(), NonEmptyList.of(1, 2, 3))
// 6
nullableFold<Int>().headOption(null)
// Option.None
nonEmptyIntFold.headOption(NonEmptyList.of(1, 2, 3, 4))
// Option.Some(1)

Composition

Composing Fold can be used for accessing foci in nested structures.

val nestedNelFold: Fold<NonEmptyListOf<NonEmptyListOf<Int>>, NonEmptyListOf<Int>> = Fold.fromFoldable(NonEmptyList.foldable())

val nestedNel = NonEmptyList.of(1, 2, 3, 4).map {
    NonEmptyList.of(it, it)
}

(nestedNelFold compose nonEmptyIntFold).getAll(nestedNel)
// [1, 1, 2, 2, 3, 3, 4, 4]

Fold can be composed with all optics except Setter, and results in the following optics.

  Iso Lens Prism Optional Getter Setter Fold Traversal
Fold Fold Fold Fold Fold Fold X Fold Fold

Do you like Arrow?

Arrow Org
<