You are on page 1of 5

Introduction to Effects

juanmanuel.gimeno@udl.cat

2023

Index
• Pure functional programming
• The Effect Pattern
– Option as an effect
– Future as an effect
• Capturing Arbitrary Side Effects as an Effect
– MyIO
– MyZIO

Pure funcional programming (recap)


• The program is a big expression that computes a value
• Referential transparency: we can replace an expression with its value
• Expressions performing side effects are not referentially transparent
• A side effect is everything not captured in the returned value
– printing to the console
– modifying a global variable
– ...

The Effect Pattern


• An effect is a data structure that represents a computational concept
such as
– absence of value
– failure
– I/O
• This data structure allows us to express computations with these concepts
without losing referential transparency

1
– instead of performing the effect we describe it

The Effect Pattern


• An effect is a data structure with the following properties:
– it describes the kind of computational concept it represents
– it describes the type of value it can produce
– it separates the description of the effect from its execution, when
externally observable side effects are involved

Option as a Effect
• Scala allows the use of null values to represent missing values
– But then, the programmer is required to check if a reference is null
before using it, or a NullPointerException will be thrown at runtime
• As we know, Scala has another way to represent optionality, which is the
Option type:
enum Option[+A]:
case Some(a: A)
case None
• Is Option[A] an effect?

Option as a Effect
• Does Option[A] describe the kind of computational concept it represents?
– Yes, it represents the absence of a value
• Does Option[A] describe the type of value it can produce?
– Yes, if there is a value it will be of type A
• Are externally visible side effects involved?
– No, there are no externally visible side effects involved.
• So, Option[A] is an effect !!!

Future as a Effect
• Scala defines the type Future[A] to represent asynchronous compu-
tations that may compute a value of type A or fail with an exception
• Is Future[A] an effect?

2
– It describe the concept of asynchronicity
– If values are produced they are of type A
– In this case, effects are required (allocating and executing threads),
but the execution of this effects is immediate not separated from its
description
• So Futures are not effects !!!

Future as a Effect
• Being not effects, they break referential transparency
• Consider this program
val twice =
Future(println("Hello"))
.flatMap(_ => Future(println("Hello")))
which prints “Hello” twice, but this simple refactoring prints “Hello” only
once
val printHello = Future(println("Hello"))
val twice = printHello.flatMap(_ => printHello)

Capturing Arbitrary Side Effects as an Effect


• Let’s defines a type to capture side-effects:
class MyIO[A](val unsafeRun: () => A):

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


new MyIO(() => f(unsafeRun()))

def flatMap[B](f: A => MyIO[B]): MyIO[B] =


new MyIO(() =>
val nextIO = f(unsafeRun())
nextIO.unsafeRun()
)

object MyIO:
def apply[A](a: => A): MyIO[A] =
new MyIO(() => a)
• Is MyIO[A] an effect?
– It describes the computational concept of side effects
– If values are produced they are of type A

3
– In this case, if effects are required we separate the description of the
computations from its execution

Capturing Arbitrary Side Effects as an Effect


• Let’s define a program to understand better this separation:
object MyIOProgram:

def twice =
MyIO(println("Hello"))
.flatMap(_ => MyIO(println("Hello")))

def alsoTwice =
val hello = MyIO(println("Hello"))
hello.flatMap(_ => hello)

@main def run() =


twice.unsafeRun()

Exercises
• Create some MyIOs which:
1. measure the current type of the system
2. measure the duration of a computation
– use exercise 1
– use map/flatMap combinations of MyIO
3. read something from console
– use scala.io.StdIn
4. print something to the console (e.g. “what’s your name”), then read,
then print a welcome message

A Simplified ZIO
• In the MyIO type we’ve defined so far, we only have control over the result
type when the computation succeeds
• When the computation fails, we do not have a functional way to communi-
cate the type of the error.
• And the computation does not have requirements, so let’s also add an
channel for the needed environment

4
case class MyZIO[R, E, A](unsafeRun: R => Either[E, A]):
def map[B](f: A => B): MyZIO[R, E, B] =
MyZIO { r => unsafeRun(r) match
case Left(e) => Left(e)
case Right(a) => Right(f(a))
}

def flatMap[B](f: A => MyZIO[R, E, B]): MyZIO[R, E, B] =


MyZIO { r => unsafeRun(r) match
case Left(e) => Left(e)
case Right(a) => f(a).unsafeRun(r)
}

A Simplified ZIO
• And, let’s consider the variances of the type parameters:
case class MyZIO[-R, +E, +A](unsafeRun: R => Either[E, A]):
def map[B](f: A => B): MyZIO[R, E, B] =
MyZIO { r => unsafeRun(r) match
case Left(e) => Left(e)
case Right(a) => Right(f(a))
}

def flatMap[R1 <: R, E1 >: E, B](f: A => MyZIO[R1, E1, B]): MyZIO[R1, E1, B] =
MyZIO { r => unsafeRun(r) match
case Left(e) => Left(e)
case Right(a) => f(a).unsafeRun(r)
}
• The real implementation is not like this, but this implementation gives us
a mental model to better understand it.

Bibliography
• J. De Goes and A. Fraser. Zionomicon. Gumroad, TBP. https://www.zi
onomicon.com/
• D. Ciocîrlan. “ZIO 2.0 Course”. https://rockthejvm.com(Access: 2022-10-
10)
• A. Rosien. Essential Effects. Gumroad, TBP. https://essentialeffects.dev/
• M. Pilquist, R. Bjarnason, and P. Chiusano. Functional Programming in
Scala, Second Edition. Manning, TBP.

You might also like