I'm new to functional programming and Scala, and I was checking out the Cats Effect framework and trying to understand what the IO monad does. So far what I've understood is that writing code in the IO block is just a description of what needs to be done and nothing happens until you explicitly run using the unsafe
methods provided, and also a way to make code that performs side-effects referentially transparent by actually not running it.
I tried executing the snippet below just to try to understand what it means:
object Playground extends App {
var out = 10
var state = "paused"
def changeState(newState: String): IO[Unit] = {
state = newState
IO(println("Updated state."))
}
def x(string: String): IO[Unit] = {
out += 1
IO(println(string))
}
val tuple1 = (x("one"), x("two"))
for {
_ <- x("1")
_ <- changeState("playing")
} yield ()
println(out)
println(state)
}
And the output was:
13
paused
I don't understand why the assignment state = newState
does not run, but the increment and assign expression out += 1
run. Am I missing something obvious on how this is supposed to work? I could really use some help. I understand that I can get this to run using the unsafe
methods.
In your particular example, I think what is going on is that regular imperative Scala coded is unaffected by the IO
monad--it runs when it normally would under the rules of Scala.
When you run:
for {
_ <- x("1")
_ <- changeState("playing")
} yield ()
this immediately calls x
. That has nothing to do with the IO
monad; it's just how for
comprehensions are defined. The first step is to evaluate the first statement so you can call flatMap
on it.
As you observe, you never "run" the monadic result, so the argument to flatMap
, the monadic continuation, is never invoked, resulting in no call to changeState
. This is specific to the IO
monad, as, e.g., the List
monad's flatMap
would have immediately invoked the function (unless it were an empty list).