dev.mythicdrops.Either.kt Maven / Gradle / Ivy
package dev.mythicdrops
internal sealed class Either {
data class Left(val left: E) : Either()
data class Right(val right: A) : Either()
fun fold(ifLeft: (E) -> R, ifRight: (A) -> R): R =
when (this) {
is Left -> ifLeft(left)
is Right -> ifRight(right)
}
fun foldLeft(init: R, f: (E) -> R): R =
fold(f) { init }
fun foldRight(init: R, f: (A) -> R): R =
fold({ init }, f)
fun bimap(f: (E) -> F, g: (A) -> B): Either =
fold({ left(f(it)) }, { right(g(it)) })
fun map(f: (A) -> B): Either =
bimap({ it }, f)
fun mapLeft(f: (E) -> F): Either =
bimap(f) { it }
fun swap(): Either =
fold(::right, ::left)
companion object {
fun left(e: E): Either = Left(e)
fun right(a: A): Either = Right(a)
fun pure(a: A): Either = right(a)
fun map2(e1: Either, e2: Either, f: (A, B) -> C): Either =
e1.fold(::left) { a -> e2.fold(::left) { b -> right(f(a, b)) } }
fun product(e1: Either, e2: Either): Either> =
map2(e1, e2, ::Pair)
fun ap(ef: Either B>, ea: Either): Either =
map2(ef, ea) { f, a -> f(a) }
fun flatMap(e: Either, f: (A) -> Either): Either =
e.fold(::left, f)
fun flatMapLeft(e: Either, f: (E) -> Either): Either =
e.fold(f, ::right)
fun join(e: Either>): Either =
flatMap(e) { it }
}
}
// These exist as extension methods because Kotlin doesn't have lower bounds checking
internal fun Either.map2(other: Either, f: (A, B) -> C): Either =
Either.map2(this, other, f)
internal fun Either.product(other: Either): Either> =
Either.product(this, other)
internal fun Either.times(other: Either): Either> =
Either.product(this, other)
internal fun Either B>.ap(e: Either): Either =
Either.ap(this, e)
internal fun Either.flatMap(f: (A) -> Either): Either =
Either.flatMap(this, f)
internal fun Either.flatMapLeft(f: (E) -> Either): Either =
Either.flatMapLeft(this, f)
internal fun Either>.join(): Either =
Either.join(this)
internal fun Either.orElse(f: (E) -> A): A =
fold(f) { it }
// Traversable for Either
// Note: The definition is duplicated because Kotlin can't type check `sequence = traverse(id)`
// Note: The definition of traverse is intentionally not `map(f).sequence()` to avoid an extra traversal
internal fun Collection.traverse(f: (A) -> Either): Either> =
fold(Either.pure(listOf())) { eacc, ee -> eacc.map2(f(ee)) { acc, e -> acc + e } }
internal fun Collection>.sequence(): Either> =
fold(Either.pure(listOf())) { eacc, ee -> eacc.map2(ee) { acc, e -> acc + e } }