Reputation: 28
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.
Upvotes: 1
Views: 177
Reputation: 27535
The simplest explanation is this: IO[A]
is like () => A
(or () => Future[A]
). Except it comes with map
and flatMap
used in for-comprehension.
When you have:
override def run: IO[Unit] = {
for {
_ <- IO.println("Hello1")
_ <- IO.println("World1")
} yield ()
for {
_ <- IO.println("Hello2")
_ <- IO.println("World2")
} yield ()
}
it's basically very similar to:
def IOprintln(str: String): () => Unit = _ => println(str)
def run: () => Unit = {
() => {
val _ = IOprintln("Hello1")()
val _ = IOprintln("World1")()
()
}
() => {
val _ = IOprintln("Hello2")()
val _ = IOprintln("World2")()
()
}
}
(In your program run
is called in IOApp.Simple
's main
).
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:
def program(condition: Boolean) = {
val a = IO.println("hello world 1")
val b = IO.println("hello world 2")
if (condition) a else b
}
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 flatMap
then only the last expression in that flatMap
would become part of the recipe, and so on. E.g. your original program can be desugared to:
override def run: IO[Unit] = {
IO.println("Hello1").flatMap { _ =>
IO.println("World1").map { _ =>
()
}
}
IO.println("Hello2").flatMap { _ =>
IO.println("World2").map { _ =>
()
}
}
}
and when you remember that
IO
is 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.
Upvotes: 4