Effect Tracking Comparison
Article that references this page: Effect Tracking
Simple Comparison Link to heading
AE 1 version
|
|
monadic version
|
|
When you need to compose multiple effects, AEs become notably better than standalone monads
AE Version
|
|
Note: effects can easily be added by just doing & NewEffect
. These effects can even be inferred by what’s used in the function
MTL 2 version
|
|
You don’t generally see several layers of monad transformers stacked. Instead, many effects are relegated to (sometimes untyped) alternatives while using a single transformer:
- dependencies (the
Reader
monad) are relegated to either explicit passing as parameters, untyped effect handling (traditional dependency injection frameworks), or in Scala specifically implicits/givens. - you generally don’t see OptionT and EitherT stacked, instead one is chosen and the other is converted into it
- logging is often handled implicitly by the runtime or imperatively
- cats.effect.IO and ZIO translate to several AE effects (error handling + side effects + async for both, zio also has deps management)
Advanced Comparison Link to heading
Let’s translate a slightly more advanced version of the above (specifically jimsAgeEff
/jimsAgeT
) to other languages. In addition to the effects we managed above, we’ll also concurrently run two processes, one to get a familyId
and the other to get a name
to find the user by, then we’ll sequentially run getting the family and getting the user.
Entirely MTL Link to heading
This requires a good deal of type annotations (ReaderT.liftF
requires annotations everytime, the return type needs annotations, getFamily
needs its return type annotated). Even if the inference algorithm improved, this would still be a good deal more complicated than the next couple Scala alternatives presented.
|
|
More realistic MTL Link to heading
This still uses monads, but only has 1 transformer layer, replacing Reader
with givens, and flattening OptionT/EitherT. getFamily
also has using FamilyMap
, which is how the dependency is passed down implicitly. The return is inferred as OptionT[IO, Int]
.
|
|
Algebraic Effects Link to heading
The type of jimsAgeEff
is entirely inferred by the effects used. The inferred type would be equivalent to Int < (Async & Abort[Absent | Exception] & Env[FamilyMap])
. The order might be different but that doesn’t matter, effects can be ran in whatever order.
|
|
Swift Link to heading
Since Swift doesn’t have a language-level mechanism for injection (nor a general mechanism for effect handling), we’ll rely on a DI framework, which generally requires using classes if we want to separate the injection requirement for a set of arguments required to run the function.
Hence the use of try? FamilyInteractor.perform(familyId)
|
|