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:
this immediately calls
x
. That has nothing to do with theIO
monad; it's just howfor
comprehensions are defined. The first step is to evaluate the first statement so you can callflatMap
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 tochangeState
. This is specific to theIO
monad, as, e.g., theList
monad'sflatMap
would have immediately invoked the function (unless it were an empty list).