Skip to content

Commit

Permalink
Add attempt builder as an emperative alternative to recover
Browse files Browse the repository at this point in the history
  • Loading branch information
kyay10 committed Dec 28, 2023
1 parent c91d53a commit 8579d76
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 22 deletions.
2 changes: 2 additions & 0 deletions arrow-libs/core/arrow-core/api/arrow-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -1040,6 +1040,7 @@ public final class arrow/core/raise/RaiseKt {
public static final fun _foldOrThrow (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
public static final fun _foldUnsafe (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
public static final fun _merge (Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
public static final fun attempt (Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
public static final fun catch (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
public static final fun catch (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)Lkotlin/jvm/functions/Function1;
public static final fun catch (Lkotlin/jvm/functions/Function2;)Lkotlin/jvm/functions/Function2;
Expand Down Expand Up @@ -1089,6 +1090,7 @@ public final class arrow/core/raise/RaiseKt {
public static final fun toResult (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun toResult (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun traced (Larrow/core/raise/Raise;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
public static final fun valueOrEmpty (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
public static final fun withError (Larrow/core/raise/Raise;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
public static final fun zipOrAccumulate (Larrow/core/raise/Raise;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function9;)Ljava/lang/Object;
public static final fun zipOrAccumulate (Larrow/core/raise/Raise;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function8;)Ljava/lang/Object;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import arrow.core.recover
import kotlin.coroutines.cancellation.CancellationException
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind.AT_MOST_ONCE
import kotlin.contracts.InvocationKind.EXACTLY_ONCE
import kotlin.contracts.contract
import kotlin.experimental.ExperimentalTypeInference
import kotlin.jvm.JvmMultifileClass
Expand Down Expand Up @@ -662,7 +661,8 @@ public inline fun <Error, OtherError, A> Raise<Error>.withError(
contract {
callsInPlace(transform, AT_MOST_ONCE)
}
return recover(block) { raise(transform(it)) }
val error = attempt { return block(this) }
raise(transform(error))
}

/**
Expand Down Expand Up @@ -693,3 +693,41 @@ public inline fun <A> merge(
}
return recover(block, ::identity)
}

/**
* Execute the [Raise] context function resulting in an early-return to an outer scope,
* or any _logical error_ of type [Error].
* This function behaves like an imperative version of recover.
* <!--- INCLUDE
* import arrow.core.getOrElse
* import arrow.core.raise.attempt
* import arrow.core.raise.either
* import io.kotest.matchers.shouldBe
* import kotlin.random.Random
* -->
* ```kotlin
* val foo = either { if (Random.nextBoolean()) raise("failed") else 42 }
*
* fun test() {
* either {
* val msg = attempt { return@either foo.bind() }
* raise(msg.toList())
* } shouldBe foo.mapLeft(String::toList)
*
* run {
* attempt { return@run foo.bind() }
* 1
* } shouldBe foo.getOrElse { 1 }
* }
*
* ```
* <!--- KNIT example-raise-dsl-13.kt -->
* <!--- TEST lines.isEmpty() -->
*/
@RaiseDSL
public inline fun <Error> attempt(block: Raise<Error>.() -> Nothing): Error {
contract {
callsInPlace(block, AT_MOST_ONCE)
}
return merge(block)
}
Original file line number Diff line number Diff line change
Expand Up @@ -256,17 +256,17 @@ public inline fun <Error, A, B, C, D, E, F, G, H, I, J> Raise<Error>.zipOrAccumu
): J {
contract { callsInPlace(block, AT_MOST_ONCE) }
var error: Any? = EmptyValue
val a = recover({ action1(RaiseAccumulate(this)) }) { error = combine(error, it.reduce(combine), combine); EmptyValue }
val b = recover({ action2(RaiseAccumulate(this)) }) { error = combine(error, it.reduce(combine), combine); EmptyValue }
val c = recover({ action3(RaiseAccumulate(this)) }) { error = combine(error, it.reduce(combine), combine); EmptyValue }
val d = recover({ action4(RaiseAccumulate(this)) }) { error = combine(error, it.reduce(combine), combine); EmptyValue }
val e = recover({ action5(RaiseAccumulate(this)) }) { error = combine(error, it.reduce(combine), combine); EmptyValue }
val f = recover({ action6(RaiseAccumulate(this)) }) { error = combine(error, it.reduce(combine), combine); EmptyValue }
val g = recover({ action7(RaiseAccumulate(this)) }) { error = combine(error, it.reduce(combine), combine); EmptyValue }
val h = recover({ action8(RaiseAccumulate(this)) }) { error = combine(error, it.reduce(combine), combine); EmptyValue }
val i = recover({ action9(RaiseAccumulate(this)) }) { error = combine(error, it.reduce(combine), combine); EmptyValue }
return if (error !== EmptyValue) raise(unbox<Error>(error))
else block(unbox(a), unbox(b), unbox(c), unbox(d), unbox(e), unbox(f), unbox(g), unbox(h), unbox(i))
val a = valueOrEmpty(action1) { error = combine(error, it.reduce(combine), combine) }
val b = valueOrEmpty(action2) { error = combine(error, it.reduce(combine), combine) }
val c = valueOrEmpty(action3) { error = combine(error, it.reduce(combine), combine) }
val d = valueOrEmpty(action4) { error = combine(error, it.reduce(combine), combine) }
val e = valueOrEmpty(action5) { error = combine(error, it.reduce(combine), combine) }
val f = valueOrEmpty(action6) { error = combine(error, it.reduce(combine), combine) }
val g = valueOrEmpty(action7) { error = combine(error, it.reduce(combine), combine) }
val h = valueOrEmpty(action8) { error = combine(error, it.reduce(combine), combine) }
val i = valueOrEmpty(action9) { error = combine(error, it.reduce(combine), combine) }
if (error !== EmptyValue) raise(unbox(error))
return block(unbox(a), unbox(b), unbox(c), unbox(d), unbox(e), unbox(f), unbox(g), unbox(h), unbox(i))
}

/**
Expand Down Expand Up @@ -487,19 +487,33 @@ public inline fun <Error, A, B, C, D, E, F, G, H, I, J> Raise<NonEmptyList<Error
): J {
contract { callsInPlace(block, AT_MOST_ONCE) }
val error: MutableList<Error> = mutableListOf()
val a = recover({ action1(RaiseAccumulate(this)) }) { error.addAll(it); EmptyValue }
val b = recover({ action2(RaiseAccumulate(this)) }) { error.addAll(it); EmptyValue }
val c = recover({ action3(RaiseAccumulate(this)) }) { error.addAll(it); EmptyValue }
val d = recover({ action4(RaiseAccumulate(this)) }) { error.addAll(it); EmptyValue }
val e = recover({ action5(RaiseAccumulate(this)) }) { error.addAll(it); EmptyValue }
val f = recover({ action6(RaiseAccumulate(this)) }) { error.addAll(it); EmptyValue }
val g = recover({ action7(RaiseAccumulate(this)) }) { error.addAll(it); EmptyValue }
val h = recover({ action8(RaiseAccumulate(this)) }) { error.addAll(it); EmptyValue }
val i = recover({ action9(RaiseAccumulate(this)) }) { error.addAll(it); EmptyValue }
val a = valueOrEmpty(action1, error::addAll)
val b = valueOrEmpty(action2, error::addAll)
val c = valueOrEmpty(action3, error::addAll)
val d = valueOrEmpty(action4, error::addAll)
val e = valueOrEmpty(action5, error::addAll)
val f = valueOrEmpty(action6, error::addAll)
val g = valueOrEmpty(action7, error::addAll)
val h = valueOrEmpty(action8, error::addAll)
val i = valueOrEmpty(action9, error::addAll)
error.toNonEmptyListOrNull()?.let { raise(it) }
return block(unbox(a), unbox(b), unbox(c), unbox(d), unbox(e), unbox(f), unbox(g), unbox(h), unbox(i))
}

@PublishedApi
internal inline fun <A, Error> valueOrEmpty(
block: RaiseAccumulate<Error>.() -> A,
addErrors: (NonEmptyList<Error>) -> Unit
): Any? {
contract {
callsInPlace(block, AT_MOST_ONCE)
callsInPlace(addErrors, AT_MOST_ONCE)
}
val errorList = attempt { return block(RaiseAccumulate(this)) }
addErrors(errorList)
return EmptyValue
}

/**
* Transform every element of [iterable] using the given [transform], or accumulate all the occurred errors using [combine].
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,4 +293,25 @@ class EagerEffectSpec {
.get() shouldBe i
}
}

@Test fun attemptSuccess() = runTest {
checkAll(Arb.int()) { i ->
run { attempt<Nothing> { return@run i } } shouldBe i
}
}

@Test fun attemptRaise() = runTest {
checkAll(Arb.int(), Arb.string()) { i, s ->
run {
attempt { raise(i) } shouldBe i
s
} shouldBe s
}
}

@Test fun attemptNested() = runTest {
checkAll(Arb.int()) { i ->
attempt { attempt<Nothing> { raise(i) } } shouldBe i
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// This file was automatically generated from Raise.kt by Knit tool. Do not edit.
package arrow.core.examples.exampleRaiseDsl13

import arrow.core.getOrElse
import arrow.core.raise.attempt
import arrow.core.raise.either
import io.kotest.matchers.shouldBe
import kotlin.random.Random

val foo = either { if (Random.nextBoolean()) raise("failed") else 42 }

fun test() {
either {
val msg = attempt { return@either foo.bind() }
raise(msg.toList())
} shouldBe foo.mapLeft(String::toList)

run {
attempt { return@run foo.bind() }
1
} shouldBe foo.getOrElse { 1 }
}

Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,8 @@ class RaiseKnitTest {
arrow.core.examples.exampleRaiseDsl12.test()
}

@Test fun exampleRaiseDsl13() = runTest {
arrow.core.examples.exampleRaiseDsl13.test()
}

}

0 comments on commit 8579d76

Please sign in to comment.