Created
August 26, 2016 02:50
-
-
Save ramseyboy/91bcc85cb5f28e47c9b5464c1501a983 to your computer and use it in GitHub Desktop.
Optional/Maybe type in Kotlin
This file contains 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
package me.ramseyboy.function | |
import java.util.* | |
/** | |
* A container object which may or may not contain a non-null value. If a value is defined, `isdefined()` will return `true` and `get()` will return the value. | |
* | |
* Additional methods that depend on the presence or absence of a contained value are provided, | |
* such as [orElse()][.orElse] (return a default value if value not defined) | |
* and [ifdefined()][.ifdefined] (execute a block of code if | |
* the value is defined). | |
*/ | |
interface Maybe<T> { | |
/** | |
* If non-null, the value; if null, indicates no value is defined | |
*/ | |
val value: T? | |
/** | |
* If a value is defined in this `Maybe`, returns the value, otherwise throws `NoSuchElementException`. | |
* @return the non-null value held by this `Maybe` | |
* * | |
* @throws NoSuchElementException if there is no value defined | |
* * | |
* @see Maybe.isDefined | |
*/ | |
public fun get(): T? { | |
if (value == null) { | |
throw NoSuchElementException("No value defined") | |
} | |
return value | |
} | |
/** | |
* Return `true` if there is a value defined, otherwise `false`. | |
* @return `true` if there is a value defined, otherwise `false` | |
*/ | |
public fun isDefined(): Boolean { | |
return value != null | |
} | |
/** | |
* If a value is defined, invoke the specified consumer with the value, otherwise do nothing. | |
* @param consumer block to be executed if a value is defined | |
* * | |
* @throws NullPointerException if value is defined and `consumer` is null | |
*/ | |
public fun ifDefined(consumer: Consumer<in T?>) { | |
if (value != null) | |
consumer.accept(value) | |
} | |
/** | |
* If a value is defined, and the value matches the given predicate, return an `Maybe` | |
* describing the value, otherwise return an empty `Maybe`. | |
* @param predicate a predicate to apply to the value, if defined | |
* * | |
* @return an `Maybe` describing the value of this `Maybe` if a value is | |
* * defined and the value matches the given predicate, otherwise an empty `Maybe` | |
* * | |
* @throws NullPointerException if the predicate is null | |
*/ | |
public fun filter(predicate: Predicate<in T>?): Maybe<T> { | |
if (predicate == null) { | |
throw NullPointerException() | |
} | |
if (!isDefined()) | |
return this | |
else | |
return if (predicate.test(value!!)) this else empty<T>() | |
} | |
/** | |
* If a value is defined, apply the provided mapping function to it, and if the result is | |
* non-null, return an `Maybe` describing the result. Otherwise return an empty `Maybe`. | |
* @param The type of the result of the mapping function | |
* * | |
* @param mapper a mapping function to apply to the value, if defined | |
* * | |
* @return an `Maybe` describing the result of applying a mapping function to the value | |
* * of this `Maybe`, if a value is defined, otherwise an empty `Maybe` | |
* * | |
* @throws NullPointerException if the mapping function is null | |
* * | |
* @apiNote This method supports post-processing on Maybe values, without the need to | |
* * explicitly check for a return status. For example, the following code traverses a stream of | |
* * file names, selects one that has not yet been processed, and then opens that file, returning | |
* * an `Maybe<FileInputStream>`: | |
* * | |
* * `Maybe<FileInputStream> fis = | |
* names.stream().filter(name -> !isProcessedYet(name)) | |
* .findFirst() | |
* .map(name -> new FileInputStream(name)); | |
` * | |
* * | |
* * Here, `findFirst` returns an `Maybe<String>`, and then `map` returns an | |
* * `Maybe<FileInputStream>` for the desired file if one exists. | |
*/ | |
public fun <U> map(mapper: Function<in T, out U>?): Maybe<U> { | |
if (mapper == null) { | |
throw NullPointerException() | |
} | |
if (!isDefined()) | |
return empty() | |
else { | |
return Maybe.ofNullable(mapper.apply(value!!)) | |
} | |
} | |
/** | |
* If a value is defined, apply the provided `Maybe`-bearing mapping function to it, | |
* return that result, otherwise return an empty `Maybe`. This method is similar to | |
* [.map], but the provided mapper is one whose result is already an `Maybe`, and if invoked, `flatMap` does not wrap it with an additional `Maybe`. | |
* @param The type parameter to the `Maybe` returned by | |
* * | |
* @param mapper a mapping function to apply to the value, if defined the mapping function | |
* * | |
* @return the result of applying an `Maybe`-bearing mapping function to the value of | |
* * this `Maybe`, if a value is defined, otherwise an empty `Maybe` | |
* * | |
* @throws NullPointerException if the mapping function is null or returns a null result | |
*/ | |
public fun <U> flatMap(mapper: Function<in T, Maybe<U>>?): Maybe<U> { | |
if (mapper == null) { | |
throw NullPointerException() | |
} | |
if (!isDefined()) | |
return empty() | |
else { | |
return mapper.apply(value!!) | |
} | |
} | |
/** | |
* Return the value if defined, otherwise return `other`. | |
* @param other the value to be returned if there is no value defined, may be null | |
* * | |
* @return the value, if defined, otherwise `other` | |
*/ | |
public fun orElse(other: T): T { | |
return value ?: other | |
} | |
/** | |
* Return the value if defined, otherwise invoke `other` and return the result of that | |
* invocation. | |
* @param other a `Supplier` whose result is returned if no value is defined | |
* * | |
* @return the value if defined otherwise the result of `other.get()` | |
* * | |
* @throws NullPointerException if value is not defined and `other` is null | |
*/ | |
public fun orElseGet(other: Supplier<out T>): T { | |
return value ?: other.get() | |
} | |
/** | |
* Return the contained value, if defined, otherwise throw an exception to be created by the | |
* provided supplier. | |
* @param Type of the exception to be thrown | |
* * | |
* @param exceptionSupplier The supplier which will return the exception to be thrown | |
* * | |
* @return the defined value | |
* * | |
* @throws X if there is no value defined | |
* * | |
* @throws NullPointerException if no value is defined and `exceptionSupplier` is null | |
* * | |
* @apiNote A method reference to the exception constructor with an empty argument list can be | |
* * used as the supplier. For example, `IllegalStateException::new` | |
*/ | |
@Throws(Throwable::class) | |
public fun <X : Throwable> orElseThrow(exceptionSupplier: Supplier<out X>): T? { | |
if (value != null) { | |
return value | |
} else { | |
throw exceptionSupplier.get() | |
} | |
} | |
/** | |
* Indicates whether some other object is "equal to" this Maybe. The other object is | |
* considered equal if: * it is also an `Maybe` and; * both instances have no | |
* value defined or; * the defined values are "equal to" each other via `equals()`. | |
* | |
* @param obj an object to be tested for equality | |
* * | |
* @return {code true} if the other object is "equal to" this object otherwise `false` | |
*/ | |
override fun equals(obj: Any?): Boolean { | |
if (obj == null) { | |
throw NullPointerException() | |
} | |
if (this === obj) { | |
return true | |
} | |
if (obj !is Maybe<*>) { | |
return false | |
} | |
return equals(value, obj.value) | |
} | |
/** | |
* Returns the hash code value of the defined value, if any, or 0 (zero) if no value is | |
* defined. | |
* @return hash code value of the defined value or 0 if no value is defined | |
*/ | |
override fun hashCode(): Int { | |
return hashCode(value) | |
} | |
/** | |
* Returns a non-empty string redefinedation of this Maybe suitable for debugging. The exact | |
* definedation format is unspecified and may vary between implementations and versions. | |
* @return the string redefinedation of this instance | |
* * | |
* @implSpec If a value is defined the result must include its string redefinedation in the | |
* * result. Empty and defined Maybes must be unambiguously differentiable. | |
*/ | |
override fun toString(): String { | |
return if (value != null){ | |
"Maybe[$value]" | |
} else { | |
"Maybe.empty" | |
} | |
} | |
companion object { | |
/** | |
* Common instance for `empty()`. | |
*/ | |
private val EMPTY = None<Any>() | |
/** | |
* Returns an empty `Maybe` instance. No value is defined for this Maybe. | |
* @param Type of the non-existent value | |
* * | |
* @return an empty `Maybe` | |
* * | |
* @apiNote Though it may be tempting to do so, avoid testing if an object is empty by comparing | |
* * with `==` against instances returned by `Option.empty()`. There is no guarantee | |
* * that it is a singleton. Instead, use [.isdefined]. | |
*/ | |
public fun <T> empty(): Maybe<T> { | |
val t = EMPTY as Maybe<T> | |
return t | |
} | |
/** | |
* Returns an `Maybe` with the specified defined non-null value. | |
* @param the class of the value | |
* * | |
* @param value the value to be defined, which must be non-null | |
* * | |
* @return an `Maybe` with the value defined | |
* * | |
* @throws NullPointerException if value is null | |
*/ | |
public fun <T> of(value: T): Maybe<T> { | |
return Some(value) | |
} | |
/** | |
* Returns an `Maybe` describing the specified value, if non-null, otherwise returns an | |
* empty `Maybe`. | |
* @param the class of the value | |
* * | |
* @param value the possibly-null value to describe | |
* * | |
* @return an `Maybe` with a defined value if the specified value is non-null, | |
* * otherwise an empty `Maybe` | |
*/ | |
public fun <T> ofNullable(value: T?): Maybe<T> { | |
return if (value == null) empty<T>() else of(value) | |
} | |
/** | |
* Null-safe equivalent of `a.equals(b)`. | |
*/ | |
public fun equals(a: Any?, b: Any?): Boolean { | |
return if ((a == null)) (b == null) else a == b | |
} | |
/** | |
* Returns 0 for null or `o.hashCode()`. | |
*/ | |
public fun hashCode(o: Any?): Int { | |
return if ((o == null)) 0 else o.hashCode() | |
} | |
} | |
} | |
final class Some<T> : Maybe<T> { | |
override val value: T | |
/** | |
* Constructs an instance with the value defined. | |
* @param value the non-null value to be defined | |
* * | |
* @throws NullPointerException if value is null | |
*/ | |
constructor(value: T) { | |
if (value == null) { | |
throw NullPointerException() | |
} | |
this.value = value | |
} | |
} | |
final class None<T> : Maybe<T> { | |
override val value: T? | |
/** | |
* Constructs an empty instance. | |
* @implNote Generally only one empty instance, [Maybe.EMPTY], should exist per VM. | |
*/ | |
constructor() { | |
this.value = null | |
} | |
} | |
fun main(args: Array<String>) { | |
val maybe: Maybe<String> = Some("defined") | |
val unwrapped = when(maybe) { | |
is Some -> maybe.get() | |
is None -> "default value" | |
else -> throw RuntimeException("neva gonna happen") | |
} | |
println(unwrapped) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment