Fold

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

Creating a Fold can be done by manually defining foldMap.

import arrow.*
import arrow.typeclasses.*
import arrow.optics.*

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.data.*

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

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

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

Composition

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

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

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

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

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

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