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

commonMain.arrow.optics.Optional.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.None
import arrow.core.Option
import arrow.core.Some
import arrow.core.flatMap
import arrow.core.getOrElse
import arrow.core.identity
import arrow.core.prependTo
import arrow.core.toOption
import arrow.typeclasses.Monoid
import kotlin.jvm.JvmStatic

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

public fun  Optional(getOption: (source: S) -> Option, set: (source: S, focus: A) -> S): Optional =
  POptional({ s -> getOption(s).toEither { s } }, set)

/**
 * [Optional] is an optic that allows to focus into a structure and querying or [copy]'ing an optional focus.
 *
 * ```kotlin
 * import arrow.core.None
 * import arrow.core.Option
 * import arrow.core.Some
 * import arrow.optics.Optional
 *
 * data class User(val username: String, val email: Option) {
 *     companion object {
 *       // can be out generated by @optics
 *       val email: Optional = Optional(User::email) { user, email ->
 *         user.copy(email = Some(email))
 *       }
 *   }
 * }
 *
 * fun main(args: Array) {
 *   val original = User("arrow-user", None)
 *   val set = User.email.set(original, "[email protected]")
 *   val modified = User.email.modify(set, String::toLowerCase)
 *   println("original: $original, set: $set, modified: $modified")
 * }
 * ```
 * 
 *
 * A (polymorphic) [POptional] is useful when setting or modifying a value for a type with a optional polymorphic focus
 * i.e. POptional, Either, Int, String>
 *
 * A [POptional] can be seen as a weaker [Lens] and [Prism] and combines their weakest functions:
 * - `set: (S, B) -> T` meaning we can focus into an `S` and set a value `B` for a target `A` and obtain a modified source `T`
 * - `getOrModify: (S) -> Either` meaning it returns the focus of a [POptional] OR the original value
 *
 * @param S the source of a [POptional]
 * @param T the modified source of a [POptional]
 * @param A the focus of a [POptional]
 * @param B the modified focus of a [POptional]
 */
public interface POptional : PSetter, POptionalGetter, PTraversal, PEvery {

  /**
   * Get the modified source of a [POptional]
   */
  override fun set(source: S, focus: B): T

  /**
   * Get the focus of a [POptional] or return the original value while allowing the type to change if it does not match
   */
  override fun getOrModify(source: S): Either

  override fun  foldMap(M: Monoid, source: S, map: (focus: A) -> R): R =
    getOrModify(source).map(map).getOrElse { M.empty() }

  /**
   * Modify the focus of a [POptional] with a function [map]
   */
  override fun modify(source: S, map: (focus: A) -> B): T =
    getOrModify(source).fold(::identity) { a -> set(source, map(a)) }

  /**
   * Set the focus of a [POptional] with a value.
   * @return null if the [POptional] is not matching
   */
  public fun setNullable(source: S, b: B): T? =
    modifyNullable(source) { b }

  /**
   * Modify the focus of a [POptional] with a function [map]
   * @return null if the [POptional] is not matching
   */
  public fun modifyNullable(source: S, map: (focus: A) -> B): T? =
    getOrNull(source)?.let { set(source, map(it)) }

  /**
   * Join two [POptional] with the same focus [B]
   */
  public infix fun  choice(other: POptional): POptional, Either, A, B> =
    POptional(
      { sources ->
        sources.fold(
          { leftSource ->
            getOrModify(leftSource).mapLeft { Either.Left(it) }
          },
          { rightSource ->
            other.getOrModify(rightSource).mapLeft { Either.Right(it) }
          }
        )
      },
      { sources, focus ->
        sources.mapLeft { leftSource -> this.set(leftSource, focus) }.map { rightSource -> other.set(rightSource, focus) }
      }
    )

  /**
   * Create a product of the [POptional] and a type [C]
   */
  public override fun  first(): POptional, Pair, Pair, Pair> =
    POptional(
      { (source, c) -> getOrModify(source).mapLeft { Pair(it, c) }.map { Pair(it, c) } },
      { (source, c2), (update, c) -> setNullable(source, update)?.let { Pair(it, c) } ?: Pair(set(source, update), c2) }
    )

  /**
   * Create a product of a type [C] and the [POptional]
   */
  public override fun  second(): POptional, Pair, Pair, Pair> =
    POptional(
      { (c, s) -> getOrModify(s).mapLeft { c to it }.map { c to it } },
      { (c2, s), (c, b) -> setNullable(s, b)?.let { c to it } ?: (c2 to set(s, b)) }
    )

  /**
   * Compose a [POptional] with a [POptional]
   */
  public infix fun  compose(other: POptional): POptional =
    POptional(
      { source ->
        getOrModify(source).flatMap { a ->
          other.getOrModify(a).mapLeft { b -> set(source, b) }
        }
      },
      { source, d -> modify(source) { a -> other.set(a, d) } }
    )

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

  public companion object {

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

    /**
     * [POptional] that takes either [S] or [S] and strips the choice of [S].
     */
    public fun  codiagonal(): Optional, S> = POptional(
      { sources -> sources.fold({ Either.Right(it) }, { Either.Right(it) }) },
      { sources, focus -> sources.mapLeft { focus }.map { focus } }
    )

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

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

    /**
     * [POptional] that never sees its focus
     */
    public fun  void(): Optional = POptional(
      { Either.Left(it) },
      { source, _ -> source }
    )

    /**
     * [Optional] to safely operate on the head of a list
     */
    @JvmStatic
    public fun  listHead(): Optional, A> = Optional(
      getOption = { if (it.isNotEmpty()) Some(it[0]) else None },
      set = { list, newHead -> if (list.isNotEmpty()) newHead prependTo list.drop(1) else emptyList() }
    )

    /**
     * [Optional] to safely operate on the tail of a list
     */
    @JvmStatic
    public fun  listTail(): Optional, List> = Optional(
      getOption = { if (it.isEmpty()) None else Some(it.drop(1)) },
      set = { list, newTail -> if (list.isNotEmpty()) list[0] prependTo newTail else emptyList() }
    )

    @JvmStatic
    public fun  nullable(): Optional = Optional(
      getOption = { it.toOption() },
      set = { source, new -> source?.let { new } }
    )
  }
}