Round 2 :innocent:, I tried pasting this in a scra...
# arrow
d
Round 2 😇, I tried pasting this in a scratch file:
Copy code
fun foo() = effect<Unit, Unit> { println("effect run") }

runBlocking {
    either<Unit, Unit> {
        foo().bind()
        foo().bind()
    }
}
And I get:
Copy code
effect run
effect run
res4: arrow.core.Either<kotlin.Unit, kotlin.Unit> = Either.Right(kotlin.Unit)
That's a lazy computation only being done once? (I think I'm really NOT grasping this concept too much or not using it correctly... and to try digging into the source code isn't easy... and the kdocs are a mile long...) My real question is: what are the PRACTICAL use cases for this structure...
s
Not sure I understand your question 🤔 Here is a use-case btw, https://kotlinlang.slack.com/archives/C5UPMM0A0/p1677142547403019?thread_ts=1677093177.834299&amp;cid=C5UPMM0A0. The difference is this:
Copy code
val foo: Either<Unit, Unit> = either { println("either run") }

fun main() {
  either {
    foo.bind()
    foo.bind()
  }
}

either run
Either<Unit, Unit> = Either.Right(Unit)
vs
Copy code
val foo: Effect<Unit, Unit> = effect { println("effect run") }

fun main() {
  either {
    foo.bind()
    foo.bind()
  }
}

effect run
effect run
Either<Unit, Unit> = Either.Right(Unit)
This is a proper example of non-lazy vs lazy.
d
Either isn't the lazy one? It only runs the block once...
aka
val foo by lazy {...}
s
Yes, lazy is eager as it's often called. It computes the result immediately, and you end up with the result. In contrast Effect is lazy and returns a computation that can be invoked many times.
It's not entirely comparable with
by lazy { }
, since
by lazy
just makes the single computation lazy. You cannot invoke it many times.
You can say that
by lazy
is a lazily computed value while
Effect
is a lazy (function) value.
d
So why not just use:
Copy code
val foo = foo.bind() // with effect
// then use 
baz(foo)
// and not
baz(foo.bind())
// which is the same as using either
s
Like I said
Effect
is a simple
typealias
so in reality you're writing
val effect: suspend Raise<E>.() -> A
and calling
bind
or
invoke
on it.
It's only the same if you talk about a function returning Either, not
Either
by itself.
d
What's the difference?...
Oh, a function
s
Because:
Copy code
suspend fun one(): Either<String, Int>
fun Raise<String>.two(): Int

val one: suspend () -> Either<String, Int> = ::one
val two: Effect<String, Int> = ::two
d
But in the end what's the PRACTICAL difference... when would I use one or the other? The same thing can be accomplished with only one of them...
s
That's correct, but that is true for many things in many libraries even in the Kotlin Std 😅
d
I'm sure there's a reason why it's there... I just don't see a concrete case that would make my code better/cleaner etc... in stdlib there are overlaps, but they have one of those benefits.
s
Sometimes you want to pass an
Either
value, rather than a function. I would say it's the same as passing around an
Int
vs
() -> Int
. For example, I have written internally somewhere a library that uses
Effect<E, A>
to model database queries that are lazily composable into transactions but once the transaction is made and ran I want to return a
Either
value. Not a lazily computable transaction, that would be dangerous.
Since a lot of these things are abstract, and can apply to many different things it's often hard to give good use-case based examples. Also this might be districting depending on which domain you work in. If we give such an example, people might assume it's meant for doing these kind of things while it's much more general. KotlinX Flow (and KotlinX Coroutines) has the similar problems. They talk about generating a stream of values, but don't give concrete examples in terms of front-end or back-end applications.
d
You're right the examples might be limiting... but if examples can show the basic idea of how to use something, when coming across similar problems at least we have that. Your example is a good one (if I understood correctly), composing functions before running them, and then running the composition in the right context. So in general one would use
either { }
when it doesn't matter that the computation is run on the spot, if one doesn't want to run it again, just save that result in a
val
and re-use it. Only in specific cases one might use
effect { }
to defer computation to a correct context, while defining it before use.
(That's also probably limiting... but a bit more down to earth for those looking for "what can I do with this...")
Laziness isn't really such an advantage practically if not differing the use of it since it doesn't add to the clarity of the code (one can just cache the result of an
either { }
in a
val
)
More advanced users will always try to think "out of the box"... but the just starting ones need to be convinced why they need/want to use something for a purpose THEY want to acheive (not in abstract terms -- but with something very obvious and concrete). The more abstract/general you are for someone starting out, the more they can get confused and it takes much more time to grasp the basics and then expand from there...
j
I think that another difference between either and effect is that with the latter you can "fold" not only an error but also an exception. Not sure if that is possible with either. I'm using it to wrapp code with side effects
d
Isn't there
catch({}, {})
(in the Raise DSL) for that?
s
100% @Jorge Bo, that is because
Effect
encapsulates both
Raise<E>
and
suspend
. While
Either
only captures
E
into a value. @dave08
catch({ }, { })
is
Effect
since it only is available within
either { }
and here
{ }
is `Raise<E>.() -> A`😅
d
Ok, then I don't see how he's using it then... can you show me an example of the context @Jorge Bo? I thought he WAS using it inside a Raise scope...
j
mm..i'm using effect in order to signal side effect code and to produce a program description which i can then execute it. So whenever i see effect i know that 3 results are expected, exception thrown, application error or success. In contrast to either where it can return only application error or success without side effect (i try to write pure functions wehenever i use either) . By the way i'm still learning the framework and fp so i might be wront. I can share you some code let me get it
d
Isn't throwing in an
effect { }
going to bubble up which isn't too FPish...? Shouldn't there just be a wrapper
UnkownDomainError(val exception: Exception): DomainError
there?
s
Not perse, but then we're going more into the area of design space / domain modelling. I'm not sure what this example does but a very common technique is to let "unrecoverable", or truly unexpected" things, stay exceptions. And only things that are domain related/relevant errors be turned into types. For example
UserNotFound
vs
PSQLException(sqlState = Timeout)
. The first one should turn into a proper HttpStatusCode with a proper message, while the latter is just a
500
you cannot really do anything about. Except wrap some resilience safety around it.
It's hard to say that
UnkownDomainError
is better than this approach.
Might also depend on what kind of system/application you're building.
d
But you can also throw from an
either { }
so that really doesn't have to do with effect here, does it?
s
You can throw from anywhere.. but
effect
captures it in its signatures.
d
I don't see it in the example posted... what do you mean Simon?
Over there it's
effect<Unit, Unit> { }
s
Copy code
effect<String, Int> {
  throw RuntimeException("Boom")
}.catch { e: RuntimeException -> 1 }

either<String, Int> {
  throw RuntimeException("Boom")
} //.catch not available
Same for
fold
, etc.
j
correct, i was trying to test some kind of mutable shell/functional core with arrow. In my examples, repositories and api services are part of my functional core as they produce a program description, until you don’t exeucte it (toEither() or fold) nothing happens, you don’t have side effects, however when you execute it from the mutable shell , my test in this case, you are allow to deal with side effects. With the different keywords you can git it a try. I wasn’t able to achieve this behaviour with either, that is why i use effect. But this is just experimentation with the api.
d
So, so far we have a few examples for docs 😃: 1. Deferring running of effects, 2. Composing effects before running in proper context, 3. Handling side effects at the last possible moment, 4. Having explicit exceptions in effect signatures
Thanks for this discussion, I think things are much clearer than when we started 😅 !