Wrap Side Effects with IO

Summary

You have imperative code that breaks referential transparency.

Wrap the imperative code with IO, which can be referenced safely without, and save the side effects for the last, outermost layer of your program.

Imperative

def multiplyM(x: Int, y: Int): Int = {
  val z: Int = x * y
  println(s"${x} * ${y} = ${z}")
  z
}
val zM: Int = {
  multiplyM(6, 7) // prints "6 * 7 = 42"
  multiplyM(6, 7) // prints "6 * 7 = 42"
  multiplyM(6, 7) // prints "6 * 7 = 42"
} // zM = 42

IO

class IO[A](a: => A) {

  def unsafePerformIO(): A = a

  def map[B](f: A => B): IO[B] =
    IO(f(a))

  def flatMap[B](f: A => IO[B]): IO[B] =
    IO(f(a).unsafePerformIO)
}

object IO {
  def apply[A](a: => A): IO[A] =
    new IO(a)
}

Pure

def multiplyI(x: Int, y: Int): IO[Int] =
  IO[Int] {
    val z: Int = x * y
    println(s"${x} * ${y} = ${z}")
    z
  }
val zI: IO[Int] = {
  multiplyI(6, 7) // does not print anything
  multiplyI(6, 7) // does not print anything
  multiplyI(6, 7) // does not print anything
} // zI = IO[=> 42]

zI.unsafePerformIO // prints "6 * 7 = 42"

Demo

This file is literate Scala, and can be run using Codedown:

$ curl https://earldouglas.com/posts/itof/side-effects-to-io.md |
  codedown scala |
  xargs -0 scala -nc -e
6 * 7 = 42
6 * 7 = 42
6 * 7 = 42
6 * 7 = 42