🎓 A1Learner
🔍
0 of 45 lessons completed

Effect Patterns

Effect patterns are ways to describe work that runs later. The description itself does no work.

Creation runs nothing until run

The program creates the effect first. It prints created before it calls run.

class Eff[A](thunk: () => A):
  def run(): A = thunk()
  def map[B](f: A => B): Eff[B] = Eff(() => f(thunk()))
  def flatMap[B](f: A => Eff[B]): Eff[B] = Eff(() => f(thunk()).run())

@main def main(): Unit =
  val e = Eff(() => {
    println("ran")
    7
  })
  println("created")
  println(e.run())

Map changes the deferred result

The effect holds 10. Its map step triples the held value.

class Eff[A](thunk: () => A):
  def run(): A = thunk()
  def map[B](f: A => B): Eff[B] = Eff(() => f(thunk()))
  def flatMap[B](f: A => Eff[B]): Eff[B] = Eff(() => f(thunk()).run())

@main def main(): Unit =
  val e = Eff(() => 10).map(x => x * 3)
  println(e.run())

FlatMap chains two deferred steps

The first effect holds 5. The second effect adds 4 to the first result.

class Eff[A](thunk: () => A):
  def run(): A = thunk()
  def map[B](f: A => B): Eff[B] = Eff(() => f(thunk()))
  def flatMap[B](f: A => Eff[B]): Eff[B] = Eff(() => f(thunk()).run())

@main def main(): Unit =
  val first = Eff(() => 5)
  val chained = first.flatMap(x => Eff(() => x + 4))
  println(chained.run())

Example

class Eff[A](thunk: () => A):
  def run(): A = thunk()
  def map[B](f: A => B): Eff[B] = Eff(() => f(thunk()))
  def flatMap[B](f: A => Eff[B]): Eff[B] = Eff(() => f(thunk()).run())

@main def main(): Unit =
  val program = Eff(() => {
    println("started")
    4
  }).map(x => x * 2).flatMap(y => Eff(() => y + 10))
  println("built")
  println(program.run())

Key points

  • Effect: a value that holds work to be run later.
  • Deferred: work that waits until run starts it.
  • Use run to start the held work and get its result.
  • Use map to change the result without starting the work.
  • Use flatMap to chain a second effect that depends on the first result.

In this example, the program builds a chain without running it. It prints built first. Calling run prints started. Doubling gives 8, and adding 10 gives 18.

Note

creating an effect never runs its work, even when map or flatMap is added.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Scastie

Exercise

What prints when Eff(() => 3).map(x => x + 2).run() is called?