Skip to content

Instantly share code, notes, and snippets.

@L-Briand
Last active May 17, 2023 07:17
Show Gist options
  • Save L-Briand/0f024fef7bc6e9573a73364484bb703d to your computer and use it in GitHub Desktop.
Save L-Briand/0f024fef7bc6e9573a73364484bb703d to your computer and use it in GitHub Desktop.
Utility Optionated classes
sealed class Either<out L, out R> {
abstract val left: L
abstract val right: R
abstract fun invert(): Either<R, L>
}
class Left<out L>(override val left: L) : Either<L, Nothing>() {
companion object {
@JvmStatic
val Unit = Left(kotlin.Unit)
}
override val right: Nothing get() = throw IllegalAccessException("Cannot get Right value on Either Left")
override fun toString(): String = "Left($left)"
override fun invert(): Either<Nothing, L> = Right(left)
}
class Right<out R>(override val right: R) : Either<Nothing, R>() {
companion object {
@JvmStatic
val Unit = Left(kotlin.Unit)
}
override val left: Nothing get() = throw IllegalAccessException("Cannot get Left value on Either Right")
override fun toString(): String = "Right($right)"
override fun invert(): Either<R, Nothing> = Left(right)
}
inline fun <L, R> Either<L, R>.alsoLeft(
run: (L) -> Unit
): Either<L, R> {
when (this) {
is Left -> run(left)
is Right -> Unit
}
return this
}
inline fun <L, R> Either<L, R>.alsoRight(
run: (R) -> Unit
): Either<L, R> {
when (this) {
is Left -> Unit
is Right -> run(right)
}
return this
}
inline fun <L, R> Either<L, R>.also(
left: (L) -> Unit,
right: (R) -> Unit
): Either<L, R> {
when (this) {
is Left -> left(this.left)
is Right -> right(this.right)
}
return this
}
inline fun <OldL, NewL, R> Either<OldL, R>.letLeft(
let: (OldL) -> NewL
): Either<NewL, R> = when (this) {
is Left -> Left(let(left))
is Right -> this
}
inline fun <L, OldR, NewR> Either<L, OldR>.letRight(
let: (OldR) -> NewR
): Either<L, NewR> = when (this) {
is Left -> this
is Right -> Right(let(right))
}
inline fun <OldL, OldR, NewL, NewR> Either<OldL, OldR>.let(
left: (OldL) -> NewL,
right: (OldR) -> NewR
): Either<NewL, NewR> = when (this) {
is Left -> Left(left(this.left))
is Right -> Right(right(this.right))
}
inline fun <L, R> Either<L, R>.requireLeft(
run: (R) -> Nothing
): L = when (this) {
is Left -> left
is Right -> run(right)
}
inline fun <L, R> Either<L, R>.requireLeftOrRight(
run: (Right<R>) -> Nothing
): L = when (this) {
is Left -> left
is Right -> run(this)
}
inline fun <L, R> Either<L, R>.requireRight(
run: (L) -> Nothing
): R = when (this) {
is Left -> run(left)
is Right -> right
}
inline fun <L, R> Either<L, R>.requireRightOrLeft(
run: (Left<L>) -> Nothing
): R = when (this) {
is Left -> run(this)
is Right -> right
}
inline fun <L, R, T> Either<L, R>.fold(
onLeft: (L) -> T,
onRight: (R) -> T
): T = when (this) {
is Left -> onLeft(left)
is Right -> onRight(right)
}
fun <L, R> Either<L, R>.leftAsOption(): Option<L> = when (this) {
is Left -> Value(left)
is Right -> Empty
}
fun <L, R> Either<L, R>.rightAsOption(): Option<L> = when (this) {
is Left -> Value(left)
is Right -> Empty
}
fun <L, R> Either<L, R>.requireLeft(): L = requireLeft { error("Expected Left, found Right") }
fun <L, R> Either<L, R>.requireRight(): R = requireRight { error("Expected Right, found Left") }
fun <L, R> Either<L, R>.collapseLeft(): L? = fold({ it }, { null })
fun <L, R> Either<L, R>.collapseRight(): R? = fold({ null }, { it })
import kotlinx.serialization.Serializable
@Serializable(OptionSerializer::class)
sealed class Option<out T>
data class Value<out T>(val value: T) : Option<T>() {
override fun toString(): String = "Value($value)"
}
object Empty : Option<Nothing>() {
override fun toString(): String = "Empty"
}
inline fun <T> Option<T>.alsoValue(
run: (T) -> Unit
): Option<T> {
when (this) {
Empty -> Unit
is Value -> run(value)
}
return this
}
inline fun <T> Option<T>.alsoEmpty(
run: () -> Unit
): Option<T> {
when (this) {
Empty -> run()
is Value -> Unit
}
return this
}
inline fun <T> Option<T>.also(
empty: () -> Unit,
value: (T) -> Unit
): Option<T> {
when (this) {
Empty -> empty()
is Value -> value(this.value)
}
return this
}
inline fun <T, R> Option<T>.letValue(
run: (T) -> R
) = when (this) {
is Value -> Value(value.let(run))
Empty -> Empty
}
inline fun <T, R> Option<T>.letEmpty(
run: () -> R
): Either<T, R> = fold({ Left(it) }, { Right(run()) })
inline fun <T, L, R> Option<T>.letLeft(
value: (T) -> L,
empty: () -> R,
): Either<L, R> = when (this) {
Empty -> Right(empty())
is Value -> Left(value(this.value))
}
inline fun <T, L, R> Option<T>.letRight(
empty: () -> L,
value: (T) -> R,
): Either<L, R> = when (this) {
Empty -> Left(empty())
is Value -> Right(value(this.value))
}
inline fun <T> Option<T>.requireValue(
run: () -> Nothing
) = when (this) {
Empty -> run()
is Value -> value
}
inline fun <T> Option<T>.requireValueOrEmpty(
run: (Empty) -> Nothing
) = when (this) {
Empty -> run(Empty)
is Value -> value
}
inline fun <T> Option<T>.requireEmpty(run: (T) -> Nothing) = when (this) {
Empty -> this
is Value -> run(value)
}
inline fun <T> Option<T>.requireEmptyOrValue(run: (Option<T>) -> Nothing) = when (this) {
Empty -> Empty
is Value -> run(this)
}
fun <T> Option<T>.requireValue() = requireValue { error("Expected Value, found Empty") }
fun <T> Option<T>.requireEmpty() = requireEmpty { error("Expected Empty, found Value") }
inline fun <T, O> Option<T>.fold(onValue: (T) -> O, onEmpty: () -> O): O = when (this) {
Empty -> onEmpty()
is Value -> onValue(value)
}
fun <T> Option<T>.collapse(): T? = fold({ it }, { null })
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
/**
* This serializer is used to encode/decode options with field presence inside JSON.
*
* Make sure to initialize Json with `{ encodeDefaults = false }`
* When defining a data class, initialize fields to Empty.
*
* ```kotlin
* @Serializable
* data class Data(val myOpt: Option<String>? = Empty)
* ```
*
* Doing it this way allows the deserializer fallback to empty when the field is not present inside a json object.
*/
class OptionSerializer<T>(val delegate: KSerializer<T>) : KSerializer<Option<T>> {
override val descriptor: SerialDescriptor = delegate.descriptor
override fun deserialize(decoder: Decoder): Option<T> =
Value(delegate.deserialize(decoder))
override fun serialize(encoder: Encoder, value: Option<T>) {
if (value is Value) delegate.serialize(encoder, value.value)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment