ZIO Fibers: An Introduction

Reading Time: 3 minutes

Overview

In this blog we will look at what are zio fibers and we will try to build a introductory understanding of the same. Now then, when we talk about functional programming libraries such as zio have a main objective of to deal with expressions that might produce a value along with an effect!

ZIO Data Structure

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

val aValue = {
  println("hello")
  56
}

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

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

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

incrementValue(aValue) = incrementValue(56)

Now even though these two expressions returns the same value but the 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 ZIO data structure in the ZIO library.

ZIO in-takes three arguments which looks like

ZIO[R, E, A]

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

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

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

For the same scenario above ZIO have a type alias ie UIO (Universal IO). UIO under the hood have same implementation as above.

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

Synchronous way to do things in ZIO

Now lets 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 this link. Let’s see how a synchronous 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()

Try to execute the above code and you’ll find that every task is running on the single thread.

What is a Fiber!

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 lets consider our last example that was completely synchronous and try to run “taskOne” and “taskTwo” 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. Result will store zipped result 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 seen how to implement fibers and make them work in parallel. We learned about methods like fork join and zip that comes handy when we work with fibers. To read more blogs like this one do check out https://blog.knoldus.com/.