Created
November 23, 2020 12:23
-
-
Save marcellogalhardo/f26542c584fadd82a27f3048e3e201a9 to your computer and use it in GitHub Desktop.
An option class for Kotlin.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.io.Serializable | |
import kotlin.contracts.ExperimentalContracts | |
import kotlin.contracts.InvocationKind | |
import kotlin.contracts.contract | |
/** | |
* A type that represents either a wrapped value or null, the absence of a value. | |
*/ | |
public sealed class Option<out T> : Serializable { | |
@PublishedApi | |
internal object None : Option<Nothing>() | |
@PublishedApi | |
internal data class Some<out T>(val value: T) : Option<T>() | |
/** | |
* Returns `true` if this instance represents a null. | |
* In this case [isSome] returns `false`. | |
*/ | |
public val isNone: Boolean | |
get() = this is None | |
/** | |
* Returns `true` if this instance represents a value. | |
* In this case [isNone] returns `false`. | |
*/ | |
public val isSome: Boolean | |
get() = this is Some | |
/** | |
* Returns the encapsulated value if this instance represents [some][Option.isSome] or `null` | |
* if it is [none][Option.isNone]. | |
* | |
* This function is a shorthand for `getOrElse { null }` (see [getOrElse]) or | |
* `fold(onSome = { it }, onNone = { null })` (see [fold]). | |
*/ | |
public fun getOrNull(): T? = when (this) { | |
is None -> null | |
is Some -> value | |
} | |
/** | |
* Returns a string `Some(v)` if this instance represents [Some][Option.isSome] | |
* where `v` is a string representation of the value or a string `None(null)` if | |
* it is [None][isNone] where `x` is always null. | |
*/ | |
public override fun toString(): String = when (this) { | |
is None -> "None(null)" | |
is Some -> "Some($value)" | |
} | |
/** | |
* Companion object for [Option] class that contains its constructor functions | |
* [Some] and [None]. | |
*/ | |
public companion object { | |
/** | |
* Returns an instance that encapsulates the given [value] as [Some][Option.isSome]. | |
*/ | |
public fun <T> some(value: T): Option<T> = Some(value) | |
/** | |
* Returns an instance that encapsulates the given null as [None][Option.isNone]. | |
*/ | |
public fun <T> none(): Option<T> = None | |
} | |
} | |
/** | |
* Calls the specified function [block] and returns its encapsulated Option if invocation was not nullable, | |
* otherwise returns None. | |
*/ | |
@Suppress("FunctionName", "TooGenericExceptionCaught") | |
public inline fun <T> Option(block: () -> T?): Option<T> { | |
return when (val value = block()) { | |
value != null -> Option.some<T>(value) | |
else -> Option.none() | |
} | |
} | |
/** | |
* Returns the encapsulated value if this instance represents [Some][Option.isSome] or throws an | |
* error if it is [None][Option.isNone]. | |
* | |
* This function is a shorthand for `getOrElse { throw it }` (see [getOrElse]). | |
*/ | |
public fun <T> Option<T>.getOrThrow(): T { | |
return when (this) { | |
is Option.None -> error("Required Option was None.") | |
is Option.Some -> value | |
} | |
} | |
/** | |
* Returns the encapsulated value if this instance represents [Some][Option.isSome] or the | |
* Option of [onNone] function if it is [None][Option.isNone]. | |
* | |
* Note, that this function rethrows any [Throwable] exception thrown by [onNone] function. | |
* | |
* This function is a shorthand for `fold(onSome = { it }, onNone = onNone)` (see [fold]). | |
*/ | |
@OptIn(ExperimentalContracts::class) | |
public inline fun <R, T : R> Option<T>.getOrElse(onNone: () -> R): R { | |
contract { | |
callsInPlace(onNone, InvocationKind.AT_MOST_ONCE) | |
} | |
return when (this) { | |
is Option.None -> onNone() | |
is Option.Some -> value | |
} | |
} | |
/** | |
* Returns the encapsulated value if this instance represents [Some][Option.isSome] or the | |
* [defaultValue] if it is [None][Option.isNone]. | |
* | |
* This function is a shorthand for `getOrElse { defaultValue }` (see [getOrElse]). | |
*/ | |
public fun <R, T : R> Option<T>.getOrDefault(defaultValue: R): R { | |
return when (this) { | |
is Option.None -> defaultValue | |
is Option.Some -> value | |
} | |
} | |
/** | |
* Returns the Option of [onSome] for the encapsulated value if this instance represents [Some][Option.isSome] | |
* or the Option of [onNone] function if it is [None][Option.isNone]. | |
* | |
* Note, that this function rethrows any [Throwable] exception thrown by [onSome] or by [onNone] function. | |
*/ | |
@OptIn(ExperimentalContracts::class) | |
public inline fun <R, T> Option<T>.fold( | |
onNone: () -> R, | |
onSome: (value: T) -> R | |
): R { | |
contract { | |
callsInPlace(onNone, InvocationKind.AT_MOST_ONCE) | |
callsInPlace(onSome, InvocationKind.AT_MOST_ONCE) | |
} | |
return when (this) { | |
is Option.None -> onNone() | |
is Option.Some -> onSome(value) | |
} | |
} | |
/** | |
* Returns the encapsulated Option of the given [transform] function applied to the encapsulated value | |
* if this instance represents [Some][Option.isSome] or none if it is [None][Option.isNone]. | |
* | |
* Note, that this function rethrows any [Throwable] exception thrown by [transform] function. | |
* See [mapCatching] for an alternative that encapsulates exceptions. | |
*/ | |
@OptIn(ExperimentalContracts::class) | |
public inline fun <R, T> Option<T>.map(transform: (value: T) -> R): Option<R> { | |
contract { | |
callsInPlace(transform, InvocationKind.AT_MOST_ONCE) | |
} | |
return when (this) { | |
is Option.None -> Option.none() | |
is Option.Some -> Option.some(transform(value)) | |
} | |
} | |
/** | |
* Returns the encapsulated Option of the given [transform] function applied to the encapsulated value | |
* if this instance represents [Some][Option.isSome] or none if it is [None][Option.isNone]. | |
* | |
* This function catches any [Throwable] exception thrown by [transform] function and encapsulates it as a None. | |
* See [map] for an alternative that rethrows exceptions from `transform` function. | |
*/ | |
public inline fun <R, T> Option<T>.mapCatching(transform: (value: T) -> R): Option<R> { | |
return when (this) { | |
is Option.None -> Option.none() | |
is Option.Some -> Option { transform(value) } | |
} | |
} | |
/** | |
* Returns a [Option] of the given [transform] function applied to the encapsulated value | |
* if this instance represents [Some][Option.isSome] or none if it is [None][Option.isNone]. | |
* | |
* Note, that this function rethrows any [Throwable] exception thrown by [transform] function. | |
* See [flatMapCatching] for an alternative that encapsulates exceptions. | |
*/ | |
@OptIn(ExperimentalContracts::class) | |
public inline fun <R, T> Option<T>.flatMap(transform: (value: T) -> Option<R>): Option<R> { | |
contract { | |
callsInPlace(transform, InvocationKind.AT_MOST_ONCE) | |
} | |
return when (this) { | |
is Option.None -> Option.none() | |
is Option.Some -> transform(value) | |
} | |
} | |
/** | |
* Returns a [Option] of the given [transform] function applied to the encapsulated value | |
* if this instance represents [Some][Option.isSome] or none if it is [None][Option.isNone]. | |
* | |
* This function catches any [Throwable] exception thrown by [transform] function and encapsulates it as a None. | |
* See [map] for an alternative that rethrows exceptions from `transform` function. | |
*/ | |
public inline fun <R, T> Option<T>.flatMapCatching(transform: (value: T) -> Option<R>): Option<R> { | |
return when (this) { | |
is Option.None -> Option.none() | |
is Option.Some -> Option { transform(value).getOrThrow() } | |
} | |
} | |
/** | |
* Returns the encapsulated Option of the given [transform] function applied to the encapsulated [Throwable] exception | |
* if this instance represents [None][Option.isNone] or none if it is [None][Option.isNone]. | |
* | |
* Note, that this function rethrows any [Throwable] exception thrown by [transform] function. | |
* See [recoverCatching] for an alternative that encapsulates exceptions. | |
*/ | |
@OptIn(ExperimentalContracts::class) | |
public inline fun <R, T : R> Option<T>.recover(transform: () -> R): Option<R> { | |
contract { | |
callsInPlace(transform, InvocationKind.AT_MOST_ONCE) | |
} | |
return when (this) { | |
is Option.None -> Option.Some(transform()) | |
is Option.Some -> this | |
} | |
} | |
/** | |
* Returns the encapsulated Option of the given [transform] function applied to the encapsulated [Throwable] exception | |
* if this instance represents [None][Option.isNone] or none if it is [None][Option.isNone]. | |
* | |
* This function catches any [Throwable] exception thrown by [transform] function and encapsulates it as a None. | |
* See [recover] for an alternative that rethrows exceptions. | |
*/ | |
public inline fun <R, T : R> Option<T>.recoverCatching(transform: () -> R): Option<R> { | |
return when (this) { | |
is Option.None -> Option { transform() } | |
is Option.Some -> this | |
} | |
} | |
/** | |
* Performs the given [action] if this instance represents [None][Option.isNone]. | |
* Returns the original `Option` unchanged. | |
*/ | |
@OptIn(ExperimentalContracts::class) | |
public inline fun <T> Option<T>.onNone(action: () -> Unit): Option<T> { | |
contract { | |
callsInPlace(action, InvocationKind.AT_MOST_ONCE) | |
} | |
if (this is Option.None) action() | |
return this | |
} | |
/** | |
* Performs the given [action] on the encapsulated value if this instance represents [Some][Option.isSome]. | |
* Returns the original `Option` unchanged. | |
*/ | |
@OptIn(ExperimentalContracts::class) | |
public inline fun <T> Option<T>.onSome(action: (value: T) -> Unit): Option<T> { | |
contract { | |
callsInPlace(action, InvocationKind.AT_MOST_ONCE) | |
} | |
if (this is Option.Some) action(value) | |
return this | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment