I'm a newbie to functional programming and cats effect. Started practicing cats effect 3 with the below code
package com.scalaFPLove.FabioLabellaTalks
import cats.effect.IOApp
import cats.effect.{IO, Concurrent}
import cats.effect.kernel.Deferred
import cats.implicits._
import cats.kernel
import cats.effect.kernel.Ref
import cats.effect.unsafe.IORuntime.global
object RefAndDeferredConcurrency extends IOApp.Simple {
override def run: IO[Unit] = {
for {
_ <- IO.println("Hello1")
_ <- IO.println("World1")
} yield ()
for {
_ <- IO.println("Hello2")
_ <- IO.println("World2")
} yield ()
}
}
The output I expected is
Hello1 world1 Hello2 world2
but the actual output is
Hello2 world2
Unable to understand the idea behind this, I tried many different things and only the last for comprehension is evaluated ignoring the rest. please help me understand this.
The simplest explanation is this:
IO[A]is like() => A(or() => Future[A]). Except it comes withmapandflatMapused in for-comprehension.When you have:
it's basically very similar to:
(In your program
runis called inIOApp.Simple'smain).You see what happened here? We created 2 recipes for a program, but never combined them together, and the value of block of code is the last expression.
And these expressions with recepies - by the very nature of
() => ...- are not computing side effects until you run them. It' very useful because it allow you to do things like:where our program would print only 1 thing, depending on the condition, but not 2 of them at once before even checking which is needed.
Basically what would be executed (somewhere, at some point) is the last IO expression. If you build it using
flatMapthen only the last expression in thatflatMapwould become part of the recipe, and so on. E.g. your original program can be desugared to:and when you remember that
IOis a value that someone somewhere evaluates (like a function)it should be clear what will and what will not become part of a final program.