All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.arrow.optics.Lens.kt Maven / Gradle / Ivy

There is a newer version: 2.0.1-alpha.1
Show newest version
package arrow.optics

import arrow.core.Either
import arrow.core.NonEmptyList
import arrow.core.identity
import arrow.typeclasses.Monoid
import kotlin.jvm.JvmStatic

/**
 * [Lens] is a type alias for [PLens] which fixes the type arguments
 * and restricts the [PLens] to monomorphic updates.
 */
public typealias Lens = PLens

/**
 * A [Lens] (or Functional Reference) is an optic that can focus into a structure for
 * getting, setting or modifying the focus (target).
 *
 * A (polymorphic) [PLens] is useful when setting or modifying a value for a constructed type
 * i.e. PLens, Pair, Double, String>
 *
 * A [PLens] can be seen as a pair of functions:
 * - `get: (S) -> A` meaning we can focus into an `S` and extract an `A`
 * - `set: (B) -> (S) -> T` meaning we can focus into an `S` and set a value `B` for a target `A` and obtain a modified source `T`
 *
 * @param S the source of a [PLens]
 * @param T the modified source of a [PLens]
 * @param A the focus of a [PLens]
 * @param B the modified focus of a [PLens]
 */
public interface PLens : Getter, POptional, PSetter,
  PTraversal, PEvery {

  override fun get(source: S): A

  override fun set(source: S, focus: B): T

  override fun getOrModify(source: S): Either =
    Either.Right(get(source))

  override fun  foldMap(M: Monoid, source: S, map: (focus: A) -> R): R =
    map(get(source))

  /**
   * Join two [PLens] with the same focus in [A]
   */
  public infix fun  choice(other: PLens): PLens, Either, A, B> = PLens(
    { ss -> ss.fold(this::get, other::get) },
    { ss, b -> ss.mapLeft { s -> set(s, b) }.map { s -> other.set(s, b) } }
  )

  /**
   * Pair two disjoint [PLens]
   */
  public infix fun  split(other: PLens): PLens, Pair, Pair, Pair> =
    PLens(
      { (s, c) -> get(s) to other.get(c) },
      { (s, s1), (b, b1) -> set(s, b) to other.set(s1, b1) }
    )

  /**
   * Create a product of the [PLens] and a type [C]
   */
  override fun  first(): PLens, Pair, Pair, Pair> = PLens(
    { (s, c) -> get(s) to c },
    { (s, _), (b, c) -> set(s, b) to c }
  )

  /**
   * Create a product of a type [C] and the [PLens]
   */
  override fun  second(): PLens, Pair, Pair, Pair> = PLens(
    { (c, s) -> c to get(s) },
    { (_, s), (c, b) -> c to set(s, b) }
  )

  /**
   * Compose a [PLens] with another [PLens]
   */
  public infix fun  compose(other: PLens): PLens = Lens(
    { a -> other.get(get(a)) },
    { s, c -> set(s, other.set(get(s), c)) }
  )

  public operator fun  plus(other: PLens): PLens =
    this compose other

  public companion object {

    public fun  id(): PIso = PIso.id()

    /**
     * [PLens] that takes either [S] or [S] and strips the choice of [S].
     */
    public fun  codiagonal(): Lens, S> = Lens(
      get = { it.fold(::identity, ::identity) },
      set = { s, b -> s.mapLeft { b }.map { b } }
    )

    /**
     * Invoke operator overload to create a [PLens] of type `S` with target `A`.
     * Can also be used to construct [Lens]
     */
    public operator fun  invoke(get: (S) -> A, set: (S, B) -> T): PLens =
      object : PLens {
        override fun get(source: S): A = get(source)
        override fun set(source: S, focus: B): T = set(source, focus)
      }

    /**
     * [Lens] to operate on the head of a [NonEmptyList]
     */
    @JvmStatic
    public fun  nonEmptyListHead(): Lens, A> =
      Lens(
        get = NonEmptyList::head,
        set = { nel, newHead -> NonEmptyList(newHead, nel.tail) }
      )

    /**
     * [Lens] to operate on the tail of a [NonEmptyList]
     */
    @JvmStatic
    public fun  nonEmptyListTail(): Lens, List> =
      Lens(
        get = NonEmptyList::tail,
        set = { nel, newTail -> NonEmptyList(nel.head, newTail) }
      )

    /**
     * [PLens] to focus into the first value of a [Pair]
     */
    @JvmStatic
    public fun  pairPFirst(): PLens, Pair, A, R> =
      PLens(
        get = { it.first },
        set = { (_, b), r -> r to b }
      )

    /**
     * [Lens] to focus into the first value of a [Pair]
     */
    @JvmStatic
    public fun  pairFirst(): Lens, A> =
      pairPFirst()

    /**
     * [PLens] to focus into the second value of a [Pair]
     */
    @JvmStatic
    public fun  pairPSecond(): PLens, Pair, B, R> =
      PLens(
        get = { it.second },
        set = { (a, _), r -> a to r }
      )

    /**
     * [Lens] to focus into the second value of a [Pair]
     */
    @JvmStatic
    public fun  pairSecond(): Lens, B> =
      pairPSecond()

    /**
     * [PLens] to focus into the first value of a [Triple]
     */
    @JvmStatic
    public fun  triplePFirst(): PLens, Triple, A, R> =
      PLens(
        get = { it.first },
        set = { (_, b, c), r -> Triple(r, b, c) }
      )

    /**
     * [Lens] to focus into the first value of a [Triple]
     */
    @JvmStatic
    public fun  tripleFirst(): Lens, A> =
      triplePFirst()

    /**
     * [PLens] to focus into the second value of a [Triple]
     */
    @JvmStatic
    public fun  triplePSecond(): PLens, Triple, B, R> =
      PLens(
        get = { it.second },
        set = { (a, _, c), r -> Triple(a, r, c) }
      )

    /**
     * [Lens] to focus into the second value of a [Triple]
     */
    @JvmStatic
    public fun  tripleSecond(): Lens, B> =
      triplePSecond()

    /**
     * [PLens] to focus into the third value of a [Triple]
     */
    @JvmStatic
    public fun  triplePThird(): PLens, Triple, C, R> =
      PLens(
        get = { it.third },
        set = { (a, b, _), r -> Triple(a, b, r) }
      )

    /**
     * [Lens] to focus into the third value of a [Triple]
     */
    @JvmStatic
    public fun  tripleThird(): Lens, C> =
      triplePThird()
  }
}