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

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

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()
  }
}