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

commonMain.arrow.optics.Iso.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.None
import arrow.core.Option
import arrow.core.Either.Right
import arrow.core.Some
import arrow.core.Validated
import arrow.core.Validated.Invalid
import arrow.core.Validated.Valid
import arrow.core.compose
import arrow.core.identity
import arrow.typeclasses.Monoid
import kotlin.jvm.JvmStatic

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

private val stringToList: Iso> =
  Iso(
    get = CharSequence::toList,
    reverseGet = { it.joinToString(separator = "") }
  )

/**
 * An [Iso] is a loss less invertible optic that defines an isomorphism between a type [S] and [A]
 * i.e. a data class and its properties represented by TupleN
 *
 * A (polymorphic) [PIso] is useful when setting or modifying a value for a constructed type
 * i.e. PIso, Option, Int?, String?>
 *
 * An [PIso] is also a valid [PLens], [PPrism]
 *
 * @param S the source of a [PIso]
 * @param T the modified source of a [PIso]
 * @param A the focus of a [PIso]
 * @param B the modified target of a [PIso]
 */
public interface PIso : PPrism, PLens, Getter, POptional,
  PSetter, Fold, PTraversal, PEvery {

  /**
   * Get the focus of a [PIso]
   */
  override fun get(source: S): A

  /**
   * Get the modified focus of a [PIso]
   */
  override fun reverseGet(focus: B): T

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

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

  /**
   * Modify polymorphically the focus of a [PIso] with a function
   */
  override fun modify(source: S, map: (focus: A) -> B): T =
    reverseGet(map(get(source)))

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

  /**
   * Reverse a [PIso]: the source becomes the target and the target becomes the source
   */
  public fun reverse(): PIso =
    PIso(this::reverseGet, this::get)

  /**
   * Set polymorphically the focus of a [PIso] with a value
   */
  public fun set(b: B): T =
    reverseGet(b)

  /**
   * Pair two disjoint [PIso]
   */
  public infix fun  split(other: PIso): PIso, Pair, Pair, Pair> =
    PIso(
      { (a, c) -> get(a) to other.get(c) },
      { (b, d) -> reverseGet(b) to other.reverseGet(d) }
    )

  /**
   * Create a pair of the [PIso] and a type [C]
   */
  override fun  first(): PIso, Pair, Pair, Pair> = Iso(
    { (a, c) -> get(a) to c },
    { (b, c) -> reverseGet(b) to c }
  )

  /**
   * Create a pair of a type [C] and the [PIso]
   */
  override fun  second(): PIso, Pair, Pair, Pair> = PIso(
    { (c, a) -> c to get(a) },
    { (c, b) -> c to reverseGet(b) }
  )

  /**
   * Create a sum of the [PIso] and a type [C]
   */
  override fun  left(): PIso, Either, Either, Either> = PIso(
    { it.mapLeft(this::get) },
    { it.mapLeft(this::reverseGet) }
  )

  /**
   * Create a sum of a type [C] and the [PIso]
   */
  override fun  right(): PIso, Either, Either, Either> = PIso(
    { it.map(this::get) },
    { it.map(this::reverseGet) }
  )

  /**
   * Compose a [PIso] with a [PIso]
   */
  public infix fun  compose(other: PIso): PIso = PIso(
    other::get compose this::get,
    this::reverseGet compose other::reverseGet
  )

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

  public companion object {

    /**
     * create an [PIso] between any type and itself.
     * Id is the zero element of optics composition, for any optic o of type O (e.g. PLens, Prism, POptional, ...):
     * o compose Iso.id == o
     */
    public fun  id(): Iso = Iso(::identity, ::identity)

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

    /**
     * [PIso] that defines equality between a [List] and [Option] [NonEmptyList]
     */
    @JvmStatic
    public fun  listToPOptionNel(): PIso, List, Option>, Option>> =
      PIso(
        get = { aas -> if (aas.isEmpty()) None else Some(NonEmptyList(aas.first(), aas.drop(1))) },
        reverseGet = { optNel -> optNel.fold({ emptyList() }, NonEmptyList::all) }
      )

    /**
     * [Iso] that defines equality between a [List] and [Option] [NonEmptyList]
     */
    @JvmStatic
    public fun  listToOptionNel(): Iso, Option>> =
      listToPOptionNel()

    /**
     * [PIso] that defines the equality between [Either] and [Validated]
     */
    @JvmStatic
    @Deprecated("Validated functionality is being merged into Either.\n Consider using `id` after migration.")
    public fun  eitherToPValidated(): PIso, Either, Validated, Validated> =
      PIso(
        get = { it.fold(::Invalid, ::Valid) },
        reverseGet = Validated::toEither
      )

    /**
     * [Iso] that defines the equality between [Either] and [Validated]
     */
    @JvmStatic
    @Deprecated("Validated functionality is being merged into Either.\n Consider using `id` after migration.")
    public fun  eitherToValidated(): Iso, Validated> =
      eitherToPValidated()

    /**
     * [Iso] that defines the equality between a Unit value [Map] and a [Set] with its keys
     */
    @JvmStatic
    public fun  mapToSet(): Iso, Set> =
      Iso(
        get = { it.keys },
        reverseGet = { keys -> keys.associateWith { } }
      )

    /**
     * [PIso] that defines the equality between the nullable platform type and [Option].
     */
    @JvmStatic
    public fun  nullableToPOption(): PIso, Option> =
      PIso(
        get = Option.Companion::fromNullable,
        reverseGet = { it.fold({ null }, ::identity) }
      )

    @JvmStatic
    public fun  nullableToOption(): PIso, Option> =
      nullableToPOption()

    /**
     * [PIso] that defines the equality between [Option] and the nullable platform type.
     */
    @JvmStatic
    public fun  optionToPNullable(): PIso, Option, A?, B?> =
      PIso(
        get = { it.fold({ null }, ::identity) },
        reverseGet = Option.Companion::fromNullable
      )

    /**
     * [PIso] that defines the isomorphic relationship between [Option] and the nullable platform type.
     */
    @JvmStatic
    public fun  optionToNullable(): Iso, A?> = optionToPNullable()

    /**
     * [Iso] that defines the equality between and [arrow.core.Option] and [arrow.core.Either]
     */
    @JvmStatic
    public fun  optionToPEither(): PIso, Option, Either, Either> =
      PIso(
        get = { opt -> opt.fold({ Either.Left(Unit) }, ::Right) },
        reverseGet = { either -> either.fold({ None }, ::Some) }
      )

    /**
     * [Iso] that defines the equality between and [arrow.core.Option] and [arrow.core.Either]
     */
    @JvmStatic
    public fun  optionToEither(): Iso, Either> =
      optionToPEither()

    /**
     * [Iso] that defines equality between String and [List] of [Char]
     */
    @JvmStatic
    public fun stringToList(): Iso> =
      stringToList

    /**
     * [PIso] that defines equality between [Validated] and [Either]
     */
    @JvmStatic
    public fun  validatedToPEither(): PIso, Validated, Either, Either> =
      PIso(
        get = Validated::toEither,
        reverseGet = Validated.Companion::fromEither
      )

    /**
     * [Iso] that defines equality between [Validated] and [Either]
     */
    @JvmStatic
    public fun  validatedToEither(): Iso, Either> =
      validatedToPEither()
  }
}