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?