Everything you need to Know About ZIO

Reading Time: 3 minutes

Overview

In this blog, we will look at ZIO Effects and Fibers including how to implement fibers and make them work in parallel. First of all, let’s briefly know What is ZIO?

What Is ZIO?

ZIO is a purely functional, type-safe, composable library for asynchronous, concurrent programming in Scala. It is perfect for mid to large-scale projects that require a lot of concurrency and speed. Zio solves complex, modern business problems using simple, testable, and composable code. It is a functional effect system in Scala.

The Effect Pattern

Before talking about ZIO fibers, we need to have an idea of the Effect Pattern. The Effect Pattern is a framework that aims to model side effects that are generated by statements. These are typically defined in terms of the “effect”, not the result. When we create an effect, we are only describing the code that will perform the effect once it’s executed.

ZIO Effect Data Structure

Let us consider the following code block that is producing a value and a side effect. Here “increaseValue” is a function that is simply increasing the value by one:

val aValue = {
  println("Hello World")
  32
}

def increaseValue(x: Int): Int = x + 1

Now if I call the function with “aValue” as an argument it will produce 33 and that too along with an effect! But according to the functional programming principle, the following expressions should compute the same values but they are not!

def increaseValue(x: Int): Int = x + 1

increaseValue(aValue) = increaseValue(32)

Now even though these two expressions return the same value but they don’t do the same thing. Now let’s see how this library helps us encapsulate the whole thing i.e. returning value and production of an effect. And this is called an IO Monad which is a ZIO data structure in the ZIO library.

ZIO in-takes three arguments which looks like this :

ZIO[R, E, A]

Here R represents the environment, the other is E which is generic to any type of exception that might be thrown, and A is the success value

Now if we take the simplest case that does not require any environment that means type “Any”, gives no exception that is “Nothing” and just has a real value “int” in our case. So the simplest way to denote one would be.

val zioVal : ZIO[Any, Nothing, Int] = ZIO.succeed(32)

For the same scenario above ZIO has a type alias i.e., UIO (Universal IO). UIO under the hood has the same implementation as above.

UIO[+A] = ZIO[Any, Nothing, +A]

Synchronous way to do things in ZIO

Now let’s move on to the main topic that how can we work with ZIO fibers. If you want to know more about ZIO-type aliases you can refer to ZIO Documentation. Let’s see what asynchronous execution will look like in ZIO.

val firstTask = ZIO.succeed("first")
  val secondTask = ZIO.succeed("Second")
  val thirdTask = ZIO.succeed("Third")

  val printTask = s"[${Thread.currentThread().getName}]"

  def synchronousTasks = for{
    _ <- firstTask.debug(printTask)
    _ <- secondTask.debug(printTask)
    _ <- thirdTask.debug(printTask)

  }yield()

When we execute the above code we will find out that every task is working on a single fiber.

What is Fibers?

A fiber in simple words is a scheduled computation that is very light weight. This results in some heavy parallelism. Fiber is a data type can be denoted as follows. Here “E” means fiber can fail with an exception “E” and “A” denotes a success value.

Fiber[E, A]

Now, consider our last example that was completely synchronous and try to run “firstTask” and “secondTask” in parallel.

def concurrentTaskOneAndTaskTwo() = for{
  _ <- firstTask.debug(printTask).fork
  _ <- secondTask.debug(printTask)
  _ <- thirdTask.debug(printTask)
}yield()

Here if you notice I used a method “fork” on “firstTask”, that simply means that now that task will run in parallel. To make it more organized lets try to implement a better method to illustrate and schedule these tasks.

def concurrentTaskOneAndTaskTwo() = for{
firstFiber <- firstTask.debug(printTask).fork
secondFiber <- secondTask.debug(printTask).fork
combinedFiber = firstFiber.zip(secondFiber)
result <- combinedFiber.join.debug(printTask)
_ <- ZIO.succeed(s"${result} done!").debug(printTask) *> thirdTask.debug(printTask)
}yield()

In the above code, the “firstFiber” and the “secondFiber” will execute in parallel. Then combined fiber will zip the both of them together in a single effect. Results will store zipped results after they are finished, to achieve this “join” function is used as you can see in the above code snippet. Once it prints the result to the console after that it is going to do the “thirdTask” as you can see I’ve used “*>” to achieve this chaining.

Conclusion

In this blog, we’ve learned about ZIO Effects and Fibers including how to implement fibers and make them work in parallel. We learned about methods like fork-join and zip that comes in handy when we work with fibers. Do check out my other blogs related to Scala.

References

ZIO.Dev

Written by 

My name is Harshal Dubey. I am working as a Software Intern in Scala Studio at Knoldus. My hobbies are playing volleyball, football and Travelling.

1 thought on “Everything you need to Know About ZIO4 min read

Comments are closed.