Skip to content

Instantly share code, notes, and snippets.

@kciter
Created June 28, 2025 03:04
Show Gist options
  • Save kciter/9dc9ffbf80d1ef146a556fef66a38840 to your computer and use it in GitHub Desktop.
Save kciter/9dc9ffbf80d1ef146a556fef66a38840 to your computer and use it in GitHub Desktop.
Kotlin Effect
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
}
}
}
}
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))
}
}
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