I'm not too sure I understand what the purpose of ...
# arrow
d
I'm not too sure I understand what the purpose of it is compared to just getting results from Either...
s
Either
is a value, where
Effect<E, A>
is
suspend Raise<E>.() -> A
.
Either
is the result of invoking
(suspend) Raise<E>.() -> A
.
When you pass me an
Effect<E, A>
I can invoke it many times, but if you pass me a
Either<E, A>
value I can only inspect it but I cannot recompute it. If I need to recompute it then you need to pass me
(suspend) () -> Either<E, A>
or
(suspend) Raise<E>.() -> A
.
Would that be beneficial to include that explanation like this in the updated docs?
d
I think such an explanation is not concrete enough... the question that comes to my mind now is "when do you need to recompute it vs. inspecting it"...? You're still talking about how things are done, not WHY they are done that way.
s
For the same reasons you ever want a lazy value. To defer expensive computations. For optimization of the computation. For injecting a different dependency to the computation. For executing something around the computation. There are lots of reasons you may want an Effect.
d
I noticed Simon used either in the routes of the ktor sample project. I don't remember where I saw this code:
Copy code
private suspend inline fun <reified A : Any> KtorCtx.respond(
    statusCode: HttpStatusCode,
    noinline action: suspend Raise<ServiceError>.() -> A
): Unit = effect(action).fold(
    { error ->
        when (error) {
            is ServiceError -> call.respond(BadRequest, error)
//            is UserAlreadyExists -> call.respond(Conflict, "${error.user} already exists")
//            is UserNotFound -> call.respond(NotFound, "User with id ${error.id} not found")
        }
    }) { a -> call.respond(statusCode, a) }
Then in the Ktor route the action lambda surrounds the whole thing.
For the same reasons you ever want a lazy value.
So why would it be used in that previous case?
s
In function form they're isomorphic to each-other:
Copy code
suspend fun one(): Either<String, Int> = TODO("")

fun two(): Effect<String, Int> =
  effect { one().bind() }

suspend fun three(): Either<String, Int> =
  either { two().bind() }
d
And how is that lazier than either...?
s
suspend fun
is what makes
one
and
three
"lazy". Here they they have very different meaning however.
Copy code
val one: Either<String, Int> = 1.right()
val two: Effect<String, Int> = effect { 1 }
You can inspect
one
but you cannot do anything with
two
unless you invoke it in some
suspend fun
first.
The power of
Raise
is that it power everything in Arrow, it allows this entire mechanism of
either { }
,
ensure
,
ensureNotNull
etc to work. All these APIs and DSLs are build on-top of it.
Effect
is also not even a type anymore, it's just a
typealias
for a lambda.
d
That's a bit more clear, but the naming is a bit confusing then... so it's really a lazyEither?
Or a deferredEither (aka coroutines deferred)
s
I would actually say it's the other way around,
Either
is a computedEffect 😅 but it depends a bit on whether it was an
Effect
or
EagerEffect
since
suspend
is allowed to pass through the runtime
d
That's more a terminology that Kotliners recognize computedEffect and co. is more for the mathematicians... 😃
Deferred is a suspend fun that needs to be run
s
That's not what Deferred means in KotlinX 🙃
d
You're right... it depends on the parameters...
It runs right away and you await the result in general...
But some use it as a way to create a suspend function that can be run somewhere where there is a suspend... https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-start/
I guess naming is HARD... 🤒
One way or the other it was confusing to have EffectScope deprecated on one hand, and on the other still have effect { } around (which might even mean something a bit different...) I still don't understand why it was used in the code above. (And I don't remember from which sample of Simon's I took it... 🙈...)
But things are a bit clearer now... thanks!
s