Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add attempt builder as an imperative alternative to recover #3340

Open
wants to merge 1 commit into
base: arrow-2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
Comment on lines +512 to +514
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, here also really not fan of the early return 😅
I had to read this snippets too many times to understand what's going on :/

}

/**
* 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()
}

}