commonMain.arrow.optics.Optional.kt Maven / Gradle / Ivy
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 } }
)
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy