-
Notifications
You must be signed in to change notification settings - Fork 451
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
[PROPOSAL] Effect as typealias #2782
Conversation
|
@kyay10 removing OptionShift, NullableShift and ResultShift would be great but it seems context receivers will be out of scope for Arrow 2.0 since it’ll only become available in 1.9 or later. So it’ll probably be for Arrow 3.0 in maybe 2 years 😄 Hope we get them way sooner, and then maybe we can use them in 2.0 anyway 🤞 |
@nomisRev right yes it won't be stable yet for Arrow 2.0 |
Possibly, if we find something worthy of doing this. Currently the only benefit I currently see is not requiring What I find more important is that we design the APIs to work nicely with context receivers, whenever they get released. object Error
context(Shift<Error>)
fun one(): Int {
shift(Error)
-1
}
val resolved: Effect<Error, Int> =
effect { one() } catch { 1 }
context(Shift<Error>)
fun resolved2() : Int =
// immediately returns `Int` here without requiring `bind` because we're inside `Shift` DSL.
effect { one() } catch { 1 } If we're lucky context(Shift<Error>)
fun resolved2() : Int =
::one catch { 1 } So this is IMO already ready to take advantage of Context Receivers in a significant way, and we should continue to design things in this way. Designing code for context receivers seems to be the same as designing code for Receiver DSLs in Kotlin. |
context receivers feature doesn't work with KMP, so adopting it now implies dropping KMP support, which is not reasonable IMO |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That looks really good , are we also addressing the catch API here since it is a vital part of Effect and is missing. // handleError/With, redeem/With, mapLeft, etc..
recover: suspend (shifted: R) -> B, | ||
transform: suspend (value: A) -> B, | ||
): B = | ||
suspendCoroutineUninterceptedOrReturn { cont -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👏🏾
private val token: Token, | ||
override val context: CoroutineContext, | ||
private val parent: Continuation<B> | ||
private val parent: Continuation<B>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private val parent: Continuation<B>, | |
private val parent: Continuation<B> |
Sorry for that misunderstanding with the error handlers from 1.X up there |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks great @nomisRev !
@@ -290,7 +309,7 @@ public interface EffectScope<in R> { | |||
* <!--- KNIT example-effect-scope-10.kt --> | |||
*/ | |||
@OptIn(ExperimentalContracts::class) | |||
public suspend fun <R, B : Any> EffectScope<R>.ensureNotNull(value: B?, shift: () -> R): B { | |||
public suspend fun <R, B : Any> Shift<R>.ensureNotNull(value: B?, shift: () -> R): B { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
I converted this PR back to a DRAFT to prevent it from getting merged. I am currently still writing docs, and tests and investigating One problem I found is that having object User
object Error
val x = effect<Error, User> { // effect<Nothing, User> {
throw IllegalArgumentException("builder missed args")
}.attempt<IllegalArgumentException, Error, User> { shift(Error) }
//.attempt { ex: IllegalArgumentException -> shift(Error) } // this would also work
// Examples below can also be fixed by explicitly specifying the type on the left-hand side.
val error = effect<Error, User> { shift(Error) } // // Shift(error)
val a = error.catch<Error, Error, User> { error -> User }
val b = error.catch<Error, String, User> { error -> shift("other-failure") }
val c = error.catch<Error, Nothing, User> { error -> throw RuntimeException("BOOM") } This doesn't pose a huge problem perse, but I thought it was worth noting in this PR. |
I expect the new partial type annotations in Kotlin 1.7 to help a bit with it. For me the main problem has always been that if need to specify the error type you are forced to specify everything. But maybe now we can get away with less... val a = error.catch<Error, Error, _> { error -> User }
val b = error.catch<_, _, User> { error -> shift("other-failure") }
val c = error.catch<_, _, User> { error -> throw RuntimeException("BOOM") } |
@serras thanks for that comment. I need to look into it. I tried to solve the problem on branch The biggest issue is that |
…effect-as-typealias
I think this PR is ready for re-review @i-walker @raulraja @serras. I explored the changes for New Changes
|
If this is approved by everyone, I’ll backport the changes to 1.x were applicable. |
I’m definitely OK with the backport. However, what is the plan forward? Are you closing this PR and making a new one against a (rebased) arrow-2 branch, or merging with this? |
Whatever makes most sense for you, and the other reviewers. cc\ @raulraja, @i-walker.
In case this was for the back port. My plan forward, after this PR, is to make the relevant changes in 1.x to close the gap as much as possible. Ideally everyone is already using |
I am going to close this PR in favour of #2797. We can re-open this PR if needed. |
This proposal flattens
Effect
into atypealias
, and renamesEffectScope
toShift
.It replaces the existing error handlers with
catch
,and due to the flattening of
Effect
the existingcatch
insideShift
is the equivalent ofcatch + bind
.To make the distinction between extension, and DSL method more clear I flagged it as a
DslMarker
.Which makes DSL
catch
usage show up as a keyword. Similar to Route API (get
,post
, ...) of `Ktor.Only downside, it requires imports for all
Effect
methods but since the API surface drastically decreased I don't see this as a problem.The only APIs available are
fold
(+ connivence Arrow Core mappers) +catch
.Side-note: with context receivers we can in a future version of Arrow split
Effect
from Arrow Core, and move it to its own module (kernel
).API Design / Changes
The
Effect<E, A>
type is now atypealias
.This has as a benefit that there is no more intermediate type, or difference between a
lambda
of the same shape asEffect<E, A>
and the type itself. This also removes the need for some cases of@UnsafeVariance
, and variance. Downside is that the API has now become top-level and requires explicit imports.catch
/attempt
catch
also has an overload that only takesresolve: suspend Shift<E2>.(E) -> A
, and simply ignores theThrowable
.attempt
is a variant that cannot changeE
toE2
, but it only requires a handler forThrowable
. It thus takesrecover: suspend Shift<E>.(Throwable) -> A
instead ofrecover: suspend Shift<E2>.(Throwable) -> A
.attempt
has an overload that refinesThrowable
toT : Throwable
. IfThrowable !is T
than it rethrows theThrowable
.catch
andattempt
by only requiring a single additionalContinuation
allocation which we can neglect.catch
is the uber handler,Effect<E, A>.catch(recover: suspend Shift<E2>.(Throwable) -> A, resolve: suspend Shift<E2>.(E) -> A): Effect<E2, A>
. It has the ability to change error typeE
toE2
whilst both handlingThrowable
, andE
. We can see this as the most powerful error handler, that covers all use cases of the existing handlers and more.TODO:
EagerEffect