What is the benefit of effect system (e.g. ZIO)?

Viewed 353

I'm having hard time understanding what value effect systems, like ZIO or Cats Effect.

  • It does not make code readable, e.g.:
val wrappedB = for {
   a <- getA()  // : ZIO[R, E, A]
   b <- getB(a) // : ZIO[R, E, B]
} yield b

is no more readable to me than:

val a = getA()  // : A
val b = getB(a) // : B

I could even argue, that the latter is more straight forward, because calling a function executes it, instead of just creating an effect or execution pipeline.

  • Delayed execution does not sound convincing, because all examples I've encountered so far are just executing the pipeline right away anyways. Being able to execute effects in parallel or multiple time can be achieved in simpler ways IMHO, e.g. C# has Parallel.ForEach
  • Composability. Functions can be composed without using effects, e.g. by plain composition.
  • Pure functional methods. In the end the pure instructions will be executed, so it seems like it's just pretending DB access is pure. It does not help to reason, because while construction of the instructions is pure, executing them is not.

I may be missing something or just downplaying the benefits above or maybe benefits are bigger in certain situations (e.g. complex domain). What are the biggest selling points to use effect systems?

3 Answers

Because it makes it easy to deal with side effects. From your example:

a <- getA()  // ZIO[R, E, A] (doesn't have to be ZIO btw)

val a = getA(): A

The first getA accounts in the effect and the possibility of returning an error, a side effect. This would be like getting an A from some db where the said A may not exist or that you lack permission to access it. The second getA would be like a simple def getA = "A".
How do we put these methods together ? What if one throws an error ? Should we still proceed to the next method or just quit it ? What if one blocks your thread ?

Hopefully that addresses your second point about composability. To quickly address the rest:

  • Delayed execution. There are probably two reasons for this. The first is you actually don't want to accidentally start an execution. Or just because you write it it starts right away. This breaks what the cool guys refer to as referential transparency. The second is concurrent execution requires a thread pool or execution context. Normally we want to have a centralized place where we can fine tune it for the whole app. And when building a library we can't provide it ourselves. It's the users who provide it. In fact we can also defer the effect. All you do is define how the effect should behave and the users can use ZIO, Monix, etc, it's totally up to them.
  • Purity. Technically speaking wrapping a process in a pure effect doesn't necessarily mean the underlying process actually uses it. Only the implementation knows if it's really used or not. What we can do is lift it to make it compatible with the composition.

The two examples are not comparable since an error in the first statement will mark as faulty the value equal to the objectified sequence in the first form while it will halt the whole program in the second. The second form shall then be a function definition to properly encapsulate the two statements, followed by an affectation of the result of its call.

But more than that, in order to completely mimic the first form, some additional code has to be written, to catch exceptions and build a true faulty result, while all these things are made for free by ZIO...

I think that the ability to cleanly propagate the error state between successive statements is the real value of the ZIO approach. Any composite ZIO program fragment is then fully composable itself. That's the main benefit of any workflow based approach, anyway.

It is this modularity which gives to effect handling its real value. Since an effect is an action which structurally may produce errors, handling effects like this is an excellent way to handle errors in a composable way. In fact, handling effects consists in handling errors !

what makes programming with ZIO or Cats great is when it comes to concurrent programming. They are also other reasons but this one is IMHO where I got the "Ah Ah! Now I got it".

Try to write a program that monitor the content of several folders and for each files added to the folders parse their content but not more than 4 files at the same time. (Like the example in the video "What Java developpers could learn from ZIO" By Adam Fraser on youtube https://www.youtube.com/watch?v=wxpkMojvz24 .

I mean this in ZIO is really easy to write :)

The all idea behind the fact that you combine data structure (A ZIO is a data structure) in order to make bigger data structure is so easy to understand that I would not want to code without it for complex problems :)

Related