A few questions about `Effects`: 1. Can I assume t...
# arrow
d
A few questions about `Effects`: 1. Can I assume that they are what
IO
was in early versions of arrow? 2. Any advantage of using
Effect
compared to
suspended function
? 3. Where can I find some “reference” code showing how to use effects - combining small effects into larger ones, using effects in the application?
s
Hey @dnowak, 1. Yes, and no. In the latest
alpha
to be released as
1.2.0-RC
Effect
is a
typealias
.
typealias Effect<E, A> = suspend Raise<E>.() -> A
. So you can say that
suspend == IO
and
suspend Raise<E>.() -> A
is
EitherT<IO
or a polymorphic version of
suspend fun f(): Either<E, A>
where
Raise<E>
can represent any data type (
MonadError<F, E>
). 2. They're equal.
Effect
is a
suspend fun
+ the ability to work over typed errors of
E
. 3. We're working on some documentation/website still, but it's the same as working over
suspend fun f(): Either<E, A>
. If you were doing
suspend fun f(): Either<E, A> = either { }
today you can replace it with
fun f(): Effect<E, A> = effect { }
,
suspend fun Raise<E>.f(): A
or
context(Raise<E>) suspend fun f(): A
and all 3 works seamlessly together and are isomorphic with each-other. Note that
val e: Either<E, A>
is a value and
val f: Effect<E, A>
is a value of a computation. For some examples, you can check: • Ktor ExampleKtor Example with Context ReceiversGithub Alerts SubscriptionsGithub Alerts Subscriptions with Context Receivers If you were already familiar with
EffectScope<E>
and
EagerEffectScope<E>
then
Raise<E>
is simply the combination of the two. There is no more need to distinct between
suspend
and `non-suspend`(eager). We now leverage
inline
from the compiler to simplify this pattern and enable more powerful abstractions/patterns.
d
Thank you @simon.vergauwen for the explanation. If
Effect<E, A>
and
suspended fun f(): Either<E, A>
are equal why the effect was introduced?
s
Effect<E, A>
(
suspend Raise<E>.() -> A
) is a DSL that enables syntax over
Either<E, A>
in a generic/polymorphic way. Given context receivers lets consider the following:
Copy code
context(arrow.core.raise.Raise<E>)
fun <E, A> io.vavr.control.Either<E, A>.bind(): A =
  fold({ raise(it }, { it })

fun javaSdk(): io.vavr.control.Either<String, Int> =
  io.vavr.control.Either.right(1)

fun otherCode(): arrow.core.Either<String, Int> = 
  arrow.core.Either.Right(1)

fun Raise<String>.one(): Int = 1

val x: arrow.core.Either<String, Int> = either {
 javaSdk().bind() + otherCode().bind() + one()
}

fun Raise<String>.x2(): Int =
  javaSdk().bind() + otherCode().bind() + one()

val f: Effect<String, Int> = ::x2
Raise<E>
is the important piece,
Effect<E, A>
is just a
typealias
for
suspend Raise<E>.() -> A
. The
either { }
DSL is powered by
Raise<E>
, as well as many other types and offers the flexibility to bridge custom types into the DSL as well. If you're familiar with Scala, or Haskell, you can consider
Raise<E>
final tagless to work over error types. It only defines
E
but not the container
F
.
d
Thank you for all the details.
s
My pleasure @dnowak ☺️