Created
June 28, 2025 03:04
-
-
Save kciter/9dc9ffbf80d1ef146a556fef66a38840 to your computer and use it in GitHub Desktop.
Kotlin Effect
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 kotlin.contracts.ExperimentalContracts | |
import kotlin.contracts.InvocationKind | |
import kotlin.contracts.contract | |
@OptIn(ExperimentalContracts::class) | |
inline fun <V, E: Throwable> binding(crossinline block: BindingScope<E>.() -> V): Effect<V, E> { | |
contract { | |
callsInPlace(block, InvocationKind.EXACTLY_ONCE) | |
} | |
return with(BindingScopeImpl<E>()) { | |
try { | |
Effect.Success(block()) | |
} catch (_: BindException) { | |
error!! | |
} | |
} | |
} | |
internal object BindException: Exception() | |
interface BindingScope<E: Throwable> { | |
fun <V> Effect<V, E>.bind(): V | |
} | |
@PublishedApi | |
internal class BindingScopeImpl<E: Throwable>: BindingScope<E> { | |
var error: Effect.Failure<E>? = null | |
override fun <V> Effect<V, E>.bind(): V { | |
return when (this) { | |
is Effect.Success -> value | |
is Effect.Failure -> { | |
[email protected] = this | |
throw BindException | |
} | |
} | |
} | |
} |
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 kotlin.contracts.ExperimentalContracts | |
import kotlin.contracts.InvocationKind | |
import kotlin.contracts.contract | |
sealed class Effect<out V, out E: Throwable> { | |
abstract operator fun component1(): V? | |
abstract operator fun component2(): E? | |
companion object { | |
inline fun <V> of(f: () -> V): Effect<V, Throwable> { | |
return try { | |
Success(f()) | |
} catch (e: Throwable) { | |
Failure(e) | |
} | |
} | |
} | |
class Success<out V>(val value: V): Effect<V, Nothing>() { | |
override fun component1(): V = value | |
override fun component2(): Nothing? = null | |
override fun equals(other: Any?): Boolean { | |
if (this === other) return true | |
if (other == null || this::class != other::class || this.value != (other as Success<*>).value) return false | |
return true | |
} | |
override fun hashCode(): Int = this.value.hashCode() | |
override fun toString(): String = "Result.Success($value)" | |
} | |
class Failure<out E: Throwable>(val error: E): Effect<Nothing, E>() { | |
override fun component1(): Nothing? = null | |
override fun component2(): E = error | |
override fun equals(other: Any?): Boolean { | |
if (this === other) return true | |
if (other == null || this::class != other::class || this.error != (other as Failure<*>).error) return false | |
return true | |
} | |
override fun hashCode(): Int = this.error.hashCode() | |
override fun toString(): String = "Result.Failure($error)" | |
} | |
} | |
/** | |
* Map | |
*/ | |
@OptIn(ExperimentalContracts::class) | |
inline fun <V, E: Throwable, R> Effect<V, E>.map(transform: (V) -> R): Effect<R, E> { | |
contract { | |
callsInPlace(transform, InvocationKind.AT_MOST_ONCE) | |
} | |
return when (this) { | |
is Effect.Success -> Effect.Success(transform(value)) | |
is Effect.Failure -> this | |
} | |
} | |
@OptIn(ExperimentalContracts::class) | |
inline fun <V, E: Throwable, R> Effect<V, E>.flatMap(transform: (V) -> Effect<R, E>): Effect<R, E> { | |
contract { | |
callsInPlace(transform, InvocationKind.AT_MOST_ONCE) | |
} | |
return when (this) { | |
is Effect.Success -> transform(value) | |
is Effect.Failure -> this | |
} | |
} | |
/** | |
* Recover | |
*/ | |
@OptIn(ExperimentalContracts::class) | |
inline fun <V, E: Throwable> Effect<V, E>.recover(transform: (E) -> V): Effect.Success<V> { | |
contract { | |
callsInPlace(transform, InvocationKind.AT_MOST_ONCE) | |
} | |
return when (this) { | |
is Effect.Success -> this | |
is Effect.Failure -> Effect.Success(transform(error)) | |
} | |
} | |
@OptIn(ExperimentalContracts::class) | |
inline fun <V, E: Throwable> Effect<V, E>.recoverIf(predicate: (E) -> Boolean, transform: (E) -> V): Effect<V, E> { | |
contract { | |
callsInPlace(predicate, InvocationKind.AT_MOST_ONCE) | |
callsInPlace(transform, InvocationKind.AT_MOST_ONCE) | |
} | |
return when (this) { | |
is Effect.Success -> this | |
is Effect.Failure -> if (predicate(error)) { | |
Effect.Success(transform(error)) | |
} else { | |
this | |
} | |
} | |
} | |
@OptIn(ExperimentalContracts::class) | |
inline fun <V, E: Throwable> Effect<V, E>.recoverUnless(predicate: (E) -> Boolean, transform: (E) -> V): Effect<V, E> { | |
contract { | |
callsInPlace(predicate, InvocationKind.AT_MOST_ONCE) | |
callsInPlace(transform, InvocationKind.AT_MOST_ONCE) | |
} | |
return when (this) { | |
is Effect.Success -> this | |
is Effect.Failure -> if (!predicate(error)) { | |
Effect.Success(transform(error)) | |
} else { | |
this | |
} | |
} | |
} | |
/** | |
* On | |
*/ | |
@OptIn(ExperimentalContracts::class) | |
inline fun <V, E: Throwable> Effect<V, E>.onSuccess(action: (V) -> Unit): Effect<V, E> { | |
contract { | |
callsInPlace(action, InvocationKind.AT_MOST_ONCE) | |
} | |
if (this is Effect.Success) { | |
action(value) | |
} | |
return this | |
} | |
@OptIn(ExperimentalContracts::class) | |
inline fun <V, E: Throwable> Effect<V, E>.onFailure(action: (E) -> Unit): Effect<V, E> { | |
contract { | |
callsInPlace(action, InvocationKind.AT_MOST_ONCE) | |
} | |
if (this is Effect.Failure) { | |
action(error) | |
} | |
return this | |
} | |
/** | |
* Unwrap | |
*/ | |
@OptIn(ExperimentalContracts::class) | |
fun <V, E: Throwable> Effect<V, E>.unwrap(): V { | |
contract { | |
returns() implies (this@unwrap is Effect.Success<V>) | |
} | |
return when (this) { | |
is Effect.Success -> value | |
is Effect.Failure -> throw error | |
} | |
} | |
/** | |
* Fold | |
*/ | |
@OptIn(ExperimentalContracts::class) | |
inline fun <V, E: Throwable, R> Effect<V, E>.fold( | |
onSuccess: (V) -> R, | |
onFailure: (E) -> R | |
): R { | |
contract { | |
callsInPlace(onSuccess, InvocationKind.AT_MOST_ONCE) | |
callsInPlace(onFailure, InvocationKind.AT_MOST_ONCE) | |
} | |
return when (this) { | |
is Effect.Success -> onSuccess(value) | |
is Effect.Failure -> onFailure(error) | |
} | |
} | |
/** | |
* mapError | |
*/ | |
@OptIn(ExperimentalContracts::class) | |
inline fun <V, E: Throwable, R: Throwable> Effect<V, E>.mapError(transform: (E) -> R): Effect<V, R> { | |
contract { | |
callsInPlace(transform, InvocationKind.AT_MOST_ONCE) | |
} | |
return when (this) { | |
is Effect.Success -> this | |
is Effect.Failure -> Effect.Failure(transform(error)) | |
} | |
} |
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 kotlin.contracts.ExperimentalContracts | |
import kotlin.contracts.InvocationKind | |
import kotlin.contracts.contract | |
@OptIn(ExperimentalContracts::class) | |
inline fun <V> effect(block: () -> V): Effect<V, Throwable> { | |
contract { | |
callsInPlace(block, InvocationKind.EXACTLY_ONCE) | |
} | |
return try { | |
Effect.Success(block()) | |
} catch (e: Throwable) { | |
Effect.Failure(e) | |
} | |
} | |
@OptIn(ExperimentalContracts::class) | |
inline infix fun <T, V> T.effect(block: T.() -> V): Effect<V, Throwable> { | |
contract { | |
callsInPlace(block, InvocationKind.EXACTLY_ONCE) | |
} | |
return try { | |
Effect.Success(block()) | |
} catch (e: Throwable) { | |
Effect.Failure(e) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment