commonMain.arrow.optics.Optional.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of arrow-optics-jvm Show documentation
Show all versions of arrow-optics-jvm Show documentation
Functional companion to Kotlin's Standard Library
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