SlideShare a Scribd company logo
Functional I/O and Effects
Or, if a Monad folds in a functional forest, does it make an effect?
Dylan Forciea
Oseberg
February 28, 2017
Topics
• What does it mean for a language to be functional?
• Creating a type to represent IO operations
• Adding monoid operations to the IO type
• Adding monad operations to the IO type
• How a naïve approach for executing the monad is insufficient, and how
trampolines can fix it
What is a functional programming language?
• Functions as first class objects?
• Declarative rather than imperative?
• Recursion rather than loops?
ReferentialTransparency
• Yes!
• But ReferentialTransparency is also key to some advantages of functional
programming
• Any expression can be replaced with its value without changing the
meaning of the program
• This alludes to the fact that you should avoid side effects
Example of ReferentialTransparency
def average(x: Int, y: Int, z: Int): Int = (x + y + z) / 3
average(1, 2, 3)
 (1 + 2 + 3) / 3
 (6) / 3
 2
Running this a second time with the same input values will always return the
same result
Counterexample of ReferentialTransparency
var cumulativeValue: Int = 0
var numberSummed: Int = 0
def cumulativeAverage(x: Int): Int = {
numberSummed = numberSummed + 1
cumulativeValue += x
cumulativeValue / numberSummed
}
cumulativeAverage(10)
 cumulativeValue = 10, numberSummed = 1, result = (10/1) = 10
cumulativeAverage(20)
 cumulativeValue = 30, numberSummed = 2, result = (30/2) = 15
cumulativeAverage(20)
 cumulativeValue = 50, numberSummed = 3, result = (50/3) = 16
What does ReferentialTransparency buy you?
• Testability
• Promotes parallel programming
• Can enable optimization of code
ReferentialTransparency and IO
def addOneToUserInput(): Int = scala.io.StdIn.readInt() + 1
The return value of executing this function will be different depending on what
the user inputs!
Wrapping IO in aType
• Separate IO into a type and define actions inside
• Denotes which pieces of the code are actions
• Defer running an action until later
• Describe a program containing effects purely functionally, and then run it
Wrapping IO in aType
trait IO[A] { def run: A }
def PrintLine(msg: String): IO[Unit] = new IO[Unit] { def run: Unit =
println(msg) }
PrintLine(“Hello, world!”).run
 Hello, world!
How do we run a sequence of these actions?
• Make it a monoid, of course!
• What was a monoid, again?
Monoid Refresher
• A monoid is a combination of:
• Some type, A
• A seed (or "zero") for that type
• An associative binary operator.
• Concretely:
trait Monoid[A] {
def op(a1:A, a2: A): A
def zero: A
}
• In addition to the associativity of op, for any A the following must hold:
• op(a, zero) == a == op(zero, a)
Monoid Refresher Example
• For integer addition:
object IntMonoid extends Monoid[Int] {
def op(a: Int, b: Int): Int = a + b
def zero: Int = 0
}
• You can use operations like fold to combine these:
• List(1, 2, 3).fold(IntMonoid.zero)(IntMonoid.op)
• (((0 + 1) + 2) + 3) = 6
IO as a Monoid
trait IO[A] { self =>
def run:A
def ++[B](io: IO[B]): IO[B] = new IO[B] { def run: B = { self.run; io.run } }
}
object IO {
def zero: IO[Unit] = new IO[Unit] { def run: Unit = { } }
def ++[A, B](io1: IO[A], io2: IO[B]): IO[B] = io1 ++ io2
}
IO Monoid Example Usage
(PrintLine("Hello!") ++ PrintLine("World!")).run
Hello!
World!
val ioList = List(PrintLine("One"), PrintLine("Two"), PrintLine("Three"))
ioList.fold(IO.zero)(IO.++).run
One
Two
Three
That’s great, but…
• We can chain together output effects
• But, how can we actually perform any operations on input effects?
• Monads!
• Wait, another one of those big words…
Monad refresher
• Monads provide two operations:
• def map[B](f:A => B): IO[B]
• def flatMap[B](f:A => IO[B]): IO[B]
Monad refresher
• map transforms a value from domainA to a value from domain B inside the
monad
• flatMap transforms a value from domain A into a Monad containing a value
from domain B, and then returns a monad containing a value from domain B
How does this help?
• map takes the result of an IO monad and perform computations on the value
• flatMap takes the result of an IO monad, and then perform IO for output
• Like control flow for the IO “program”
IO as a Monad
trait IO[A] { self =>
def run:A
def ++[B](io: IO[B]): IO[B] = new IO[B] { def run: B = { self.run; io.run } }
def map[B](f: A => B): IO[B] = new IO[B] { def run: B = f(self.run) }
def flatMap[B](f: A => IO[B]): IO[B] =
new IO[B] { def run: B = f(self.run).run }
}
def PrintLine(msg: String): IO[Unit] =
new IO[Unit] { def run: Unit = println(msg) }
def GetLine(): IO[String] =
new IO[String] { def run: String = scala.io.StdIn.readLine() }
Lets put it together!
PrintLine("Type a number")
.flatMap(_ => GetLine())
.map(s => s.toInt)
.map(i => i + 1)
.flatMap(r => PrintLine(r.toString))
.run
Type a number
 1
2
Can’t mix and match
GetLine().toInt + 1
<console>:15: error: value toInt is not a member of IO[String]
GetLine().toInt + 1
Some useful operations
def doWhile[A](a: IO[A])(cond: A => IO[Boolean]): IO[Unit] = for {
a1 <- a
ok <- cond(a1)
_ <- if (ok) doWhile(a)(cond) else IO.zero
} yield ()
def forever[A, B](a: IO[A]): IO[B] = a flatMap(_ => forever(a))
Houston, we have a problem!
forever(PrintLine("Hello")).run
Hello
Hello
…
<Stack Overflow!>
• What happened?!
Not tail recursive
def flatMap[B](f: A => IO[B]): IO[B] = new IO[B] { def run: B = f(self.run).run }
def forever[B](a: IO[A]): IO[B] = a flatMap(_ => forever(a))
• forever keeps on calling flatMap
• Every time flatMap is called, we end up one level lower in recursion
• Since the function definition says we need to keep track of the results of
f(this.run) to call run on it, we keep on adding on to the call stack every time
How do we fix this?
• Use Trampolining
• Create a series ofAlgebraic DataTypes (ADTs) to describe how the
operation will run
• We can make running our IO monad operations tail recursive
• First things first…
• What is tail recursion?
• What is anADT?
Tail Recursion
• Don’t want to keep track of state after calling function recursively
• If the last function run is the recursive call, then it is in tail position
• We can skip adding a stack frame
Tail recursion example
def factorial(n: BigInt): BigInt = if (n == 0) 1 else n * factorial(n-1)
def factorial_tailrec(n: BigInt): BigInt = {
def factorial1(n: BigInt, acc: BigInt): BigInt =
if (n == 0)
acc
else
factorial1(n - 1, n * acc)
factorial1(n, 1)
}
Algebraic DataTypes
• This is a data type that can be one of several things, and each thing can
contain a defined set of data
• We can use pattern matching to perform operations on the ADT
• This is probably more apparent if we just give an example…
List ADT definition
sealed trait List[+A]
case class Cons[+A](head: A, tail: List[A]) extends List[A]
case object Nil extends List[Nothing]
List ADT Pattern Matching
val a = Cons(1, Nil)
val b = Cons(2, a)
val c = Cons(3, b)
def add(list: List[Int]): Int =
list match {
case Cons(head, tail) => head + add(tail)
case Nil => 0
}
add(c)
6 3 2 1
Describing IO monad operations with an ADT
sealed trait IO[A] { self =>
def flatMap[B](f: A => IO[B]): IO[B] = FlatMap(self, f)
def map[B](f: A => B): IO[B] = flatMap(a => (Return(f(a))))
}
case class Return[A](a: A) extends IO[A]
case class Suspend[A](resume: () =>A) extends IO[A]
case class FlatMap[A, B](sub: IO[A], k: A => IO[B]) extends IO[B]
def PrintLine(s: String): IO[Unit] = Suspend(() => Return(println(s)))
def GetLine: IO[String] = Suspend(() => scala.io.StdIn.readLine())
Our example from earlier!
PrintLine("Type a number")
.flatMap(_ => GetLine)
.map(s => s.toInt)
.map(i => i + 1)
.flatMap(r => PrintLine(r.toString))
Suspend
PrintLine
Prompt
Our example from earlier!
PrintLine("Type a number")
.flatMap(_ => GetLine)
.map(s => s.toInt)
.map(i => i + 1)
.flatMap(r => PrintLine(r.toString))
Suspend
PrintLine
Prompt
FlatMap
Suspend
GetLine
Our example from earlier!
PrintLine("Type a number")
.flatMap(_ => GetLine)
.map(s => s.toInt)
.map(i => i + 1)
.flatMap(r => PrintLine(r.toString))
Suspend
PrintLine
Prompt
FlatMap
Suspend
GetLine
Return
s.toInt
FlatMap
Our example from earlier!
PrintLine("Type a number")
.flatMap(_ => GetLine)
.map(s => s.toInt)
.map(i => i + 1)
.flatMap(r => PrintLine(r.toString))
Suspend
PrintLine
Prompt
FlatMap
Suspend
GetLine
Return
s.toInt
Return
i + 1
FlatMap
FlatMap
Our example from earlier!
PrintLine("Type a number")
.flatMap(_ => GetLine)
.map(s => s.toInt)
.map(i => i + 1)
.flatMap(r => PrintLine(r.toString))
Suspend
PrintLine
Prompt
FlatMap
Suspend
GetLine
Return
s.toInt
Return
i + 1
Suspend
PrintLine
Result
FlatMap
FlatMap
FlatMap
Now we need the Interpreter…
def run[A](io: IO[A]): A = io match {
case Return(a) => a
case Suspend(r) => r()
case FlatMap(x, f) => x match {
case Return(a) => run(f(a))
case Suspend(r) => run(f(r()))
case FlatMap(y, g) => run(y.flatMap(a => g(a).flatMap(f)))
}
}
Run the example
Suspend
PrintLine
Prompt
FlatMap
Suspend
GetLine
Return
s.toInt
Return
i + 1
Suspend
PrintLine
Result
FlatMap
FlatMap
FlatMap
PrintLine("Type a number")
.flatMap(_ => GetLine)
.map(s => s.toInt)
.map(i => i + 1)
.flatMap(r => PrintLine(r.toString))
Run the example
Suspend
PrintLine
Prompt
FlatMap
Suspend
GetLine
Return
s.toInt
Return
i + 1
Suspend
PrintLine
Result
FlatMap
FlatMap FlatMap
PrintLine("Type a number")
.flatMap(_ => GetLine)
.map(s => s.toInt)
.map(i => i + 1)
.flatMap(r => PrintLine(r.toString))
Run the example
Suspend
PrintLine
Prompt
FlatMap
Suspend
GetLine
Return
s.toInt
Return
i + 1
Suspend
PrintLine
Result
FlatMap
FlatMap
FlatMap
PrintLine("Type a number")
.flatMap(_ => GetLine)
.map(s => s.toInt)
.map(i => i + 1)
.flatMap(r => PrintLine(r.toString))
Run the example
Suspend
PrintLine
Prompt
FlatMap
Suspend
GetLine
Return
s.toInt
Return
i + 1
Suspend
PrintLine
Result
FlatMap
FlatMap
FlatMap
PrintLine("Type a number")
.flatMap(_ => GetLine)
.map(s => s.toInt)
.map(i => i + 1)
.flatMap(r => PrintLine(r.toString))
Run the example
Suspend
PrintLine
Prompt
FlatMap
Suspend
GetLine
Return
s.toInt
Return
i + 1
Suspend
PrintLine
Result
FlatMap
FlatMap
FlatMap
PrintLine("Type a number")
.flatMap(_ => GetLine)
.map(s => s.toInt)
.map(i => i + 1)
.flatMap(r => PrintLine(r.toString))
Run the example
Suspend
GetLine
Return
s.toInt
Return
i + 1
Suspend
PrintLine
Result
FlatMap
FlatMap
FlatMap
PrintLine("Type a number")
.flatMap(_ => GetLine)
.map(s => s.toInt)
.map(i => i + 1)
.flatMap(r => PrintLine(r.toString))
Run the example
Return
s.toInt
Return
i + 1
Suspend
PrintLine
Result
FlatMap
FlatMap
PrintLine("Type a number")
.flatMap(_ => GetLine)
.map(s => s.toInt)
.map(i => i + 1)
.flatMap(r => PrintLine(r.toString))
Run the example
Return
i + 1
Suspend
PrintLine
Result
FlatMap
PrintLine("Type a number")
.flatMap(_ => GetLine)
.map(s => s.toInt)
.map(i => i + 1)
.flatMap(r => PrintLine(r.toString))
Run the example
Suspend
PrintLine
Result
PrintLine("Type a number")
.flatMap(_ => GetLine)
.map(s => s.toInt)
.map(i => i + 1)
.flatMap(r => PrintLine(r.toString))
How about forever?
def forever[B](a: IO[A]): IO[B] = a flatMap(_ => forever(a))
run(forever(PrintLine("Hello")))
Suspend
PrintLine
FlatMap
Now it works!
run(forever(PrintLine("Hello")))
Hello
Hello
…
<Keeps going!>
Takeaways
• Like a Free Monad! (A story for another day…)
• You don’t have to use the same interpreter... you can represent IO as ADTs
and not perform IO at all
• Effects are purposeful and partitioned off within a type
• Bonus – it happens to be a monad!
Other libraries
• Slick
• Describe SQL operations
• Execute them once they are prepared
• Akka Streams
• Describe a dataflow with a graph
• Push data through the graph components
• In Scala, this is an advanced flavor of the IO monad described here
Any Questions?
References
• Functional Programming in Scala, Chiusano and Bjarnason
• https://www.slideshare.net/InfoQ/purely-functional-io - Nice presentation
by Runar Bjarnason
• http://blog.higher-order.com/assets/scalaio.pdf

More Related Content

PPTX
Functions in C++ (OOP)
PPTX
Function class in c++
PPTX
Highorderfunctions
PPTX
Functions in c++
PPTX
Stack data structure
PPT
C++ functions presentation by DHEERAJ KATARIA
Functions in C++ (OOP)
Function class in c++
Highorderfunctions
Functions in c++
Stack data structure
C++ functions presentation by DHEERAJ KATARIA

What's hot (17)

PPTX
C and C++ functions
PPTX
Python Programming | JNTUA | UNIT 2 | Conditionals and Recursion |
PPT
Functions in C++
PDF
Actors and functional_reactive_programming
PPTX
Learning C++ - Functions in C++ 3
PDF
Why functional programming and category theory strongly matters
PDF
Intro to Functional Reactive Programming In Scala
PPT
Operators in C++
PPT
Functions in C++
PPT
C++ Functions
PPT
16717 functions in C++
 
PPTX
stack
PPTX
Polish
PPTX
Category theory for beginners
PPT
Lecture#6 functions in c++
PPT
Functions
PPTX
Introduction to c++
C and C++ functions
Python Programming | JNTUA | UNIT 2 | Conditionals and Recursion |
Functions in C++
Actors and functional_reactive_programming
Learning C++ - Functions in C++ 3
Why functional programming and category theory strongly matters
Intro to Functional Reactive Programming In Scala
Operators in C++
Functions in C++
C++ Functions
16717 functions in C++
 
stack
Polish
Category theory for beginners
Lecture#6 functions in c++
Functions
Introduction to c++
Ad

Viewers also liked (6)

PDF
IO Resource Management on Exadata
PPTX
EZ I/O Presentation at FCA
PPTX
PPTX
Intraosseous Access and the Emergency Nurse
PPT
Async IO and Multithreading explained
PPTX
Accessing I/O Devices
IO Resource Management on Exadata
EZ I/O Presentation at FCA
Intraosseous Access and the Emergency Nurse
Async IO and Multithreading explained
Accessing I/O Devices
Ad

Similar to Functional IO and Effects (20)

PDF
Simple IO Monad in 'Functional Programming in Scala'
PDF
PDF
Algebraic Thinking for Evolution of Pure Functional Domain Models
PDF
Functional Programming Patterns for the Pragmatic Programmer
PDF
PPTX
Functional programming with FSharp
PDF
pure-functional-programming.pdf
PDF
Drinking the free kool-aid
PDF
Scalapeno18 - Thinking Less with Scala
PDF
Functor, Apply, Applicative And Monad
PDF
Introducing Monads and State Monad at PSUG
PDF
Blazing Fast, Pure Effects without Monads — LambdaConf 2018
PDF
A taste of Functional Programming
PDF
Functional Programming and Haskell - TWBR Away Day 2011
PDF
How to start functional programming (in Scala): Day1
PDF
Talk - Query monad
PDF
Advanced Tagless Final - Saying Farewell to Free
PPTX
PyData NYC 2019
PPTX
Functional programming
Simple IO Monad in 'Functional Programming in Scala'
Algebraic Thinking for Evolution of Pure Functional Domain Models
Functional Programming Patterns for the Pragmatic Programmer
Functional programming with FSharp
pure-functional-programming.pdf
Drinking the free kool-aid
Scalapeno18 - Thinking Less with Scala
Functor, Apply, Applicative And Monad
Introducing Monads and State Monad at PSUG
Blazing Fast, Pure Effects without Monads — LambdaConf 2018
A taste of Functional Programming
Functional Programming and Haskell - TWBR Away Day 2011
How to start functional programming (in Scala): Day1
Talk - Query monad
Advanced Tagless Final - Saying Farewell to Free
PyData NYC 2019
Functional programming

Recently uploaded (20)

PDF
Claude Code: Everyone is a 10x Developer - A Comprehensive AI-Powered CLI Tool
PPTX
Agentic AI Use Case- Contract Lifecycle Management (CLM).pptx
PDF
T3DD25 TYPO3 Content Blocks - Deep Dive by André Kraus
PPTX
CHAPTER 2 - PM Management and IT Context
PPTX
L1 - Introduction to python Backend.pptx
PPTX
Agentic AI : A Practical Guide. Undersating, Implementing and Scaling Autono...
PDF
Upgrade and Innovation Strategies for SAP ERP Customers
PDF
Navsoft: AI-Powered Business Solutions & Custom Software Development
PDF
How Creative Agencies Leverage Project Management Software.pdf
PPTX
history of c programming in notes for students .pptx
PDF
How to Choose the Right IT Partner for Your Business in Malaysia
PDF
Design an Analysis of Algorithms II-SECS-1021-03
PDF
Addressing The Cult of Project Management Tools-Why Disconnected Work is Hold...
PDF
Nekopoi APK 2025 free lastest update
PDF
Wondershare Filmora 15 Crack With Activation Key [2025
PDF
Raksha Bandhan Grocery Pricing Trends in India 2025.pdf
PPTX
Essential Infomation Tech presentation.pptx
PDF
wealthsignaloriginal-com-DS-text-... (1).pdf
PPTX
Reimagine Home Health with the Power of Agentic AI​
PPTX
Operating system designcfffgfgggggggvggggggggg
Claude Code: Everyone is a 10x Developer - A Comprehensive AI-Powered CLI Tool
Agentic AI Use Case- Contract Lifecycle Management (CLM).pptx
T3DD25 TYPO3 Content Blocks - Deep Dive by André Kraus
CHAPTER 2 - PM Management and IT Context
L1 - Introduction to python Backend.pptx
Agentic AI : A Practical Guide. Undersating, Implementing and Scaling Autono...
Upgrade and Innovation Strategies for SAP ERP Customers
Navsoft: AI-Powered Business Solutions & Custom Software Development
How Creative Agencies Leverage Project Management Software.pdf
history of c programming in notes for students .pptx
How to Choose the Right IT Partner for Your Business in Malaysia
Design an Analysis of Algorithms II-SECS-1021-03
Addressing The Cult of Project Management Tools-Why Disconnected Work is Hold...
Nekopoi APK 2025 free lastest update
Wondershare Filmora 15 Crack With Activation Key [2025
Raksha Bandhan Grocery Pricing Trends in India 2025.pdf
Essential Infomation Tech presentation.pptx
wealthsignaloriginal-com-DS-text-... (1).pdf
Reimagine Home Health with the Power of Agentic AI​
Operating system designcfffgfgggggggvggggggggg

Functional IO and Effects

  • 1. Functional I/O and Effects Or, if a Monad folds in a functional forest, does it make an effect? Dylan Forciea Oseberg February 28, 2017
  • 2. Topics • What does it mean for a language to be functional? • Creating a type to represent IO operations • Adding monoid operations to the IO type • Adding monad operations to the IO type • How a naïve approach for executing the monad is insufficient, and how trampolines can fix it
  • 3. What is a functional programming language? • Functions as first class objects? • Declarative rather than imperative? • Recursion rather than loops?
  • 4. ReferentialTransparency • Yes! • But ReferentialTransparency is also key to some advantages of functional programming • Any expression can be replaced with its value without changing the meaning of the program • This alludes to the fact that you should avoid side effects
  • 5. Example of ReferentialTransparency def average(x: Int, y: Int, z: Int): Int = (x + y + z) / 3 average(1, 2, 3)  (1 + 2 + 3) / 3  (6) / 3  2 Running this a second time with the same input values will always return the same result
  • 6. Counterexample of ReferentialTransparency var cumulativeValue: Int = 0 var numberSummed: Int = 0 def cumulativeAverage(x: Int): Int = { numberSummed = numberSummed + 1 cumulativeValue += x cumulativeValue / numberSummed } cumulativeAverage(10)  cumulativeValue = 10, numberSummed = 1, result = (10/1) = 10 cumulativeAverage(20)  cumulativeValue = 30, numberSummed = 2, result = (30/2) = 15 cumulativeAverage(20)  cumulativeValue = 50, numberSummed = 3, result = (50/3) = 16
  • 7. What does ReferentialTransparency buy you? • Testability • Promotes parallel programming • Can enable optimization of code
  • 8. ReferentialTransparency and IO def addOneToUserInput(): Int = scala.io.StdIn.readInt() + 1 The return value of executing this function will be different depending on what the user inputs!
  • 9. Wrapping IO in aType • Separate IO into a type and define actions inside • Denotes which pieces of the code are actions • Defer running an action until later • Describe a program containing effects purely functionally, and then run it
  • 10. Wrapping IO in aType trait IO[A] { def run: A } def PrintLine(msg: String): IO[Unit] = new IO[Unit] { def run: Unit = println(msg) } PrintLine(“Hello, world!”).run  Hello, world!
  • 11. How do we run a sequence of these actions? • Make it a monoid, of course! • What was a monoid, again?
  • 12. Monoid Refresher • A monoid is a combination of: • Some type, A • A seed (or "zero") for that type • An associative binary operator. • Concretely: trait Monoid[A] { def op(a1:A, a2: A): A def zero: A } • In addition to the associativity of op, for any A the following must hold: • op(a, zero) == a == op(zero, a)
  • 13. Monoid Refresher Example • For integer addition: object IntMonoid extends Monoid[Int] { def op(a: Int, b: Int): Int = a + b def zero: Int = 0 } • You can use operations like fold to combine these: • List(1, 2, 3).fold(IntMonoid.zero)(IntMonoid.op) • (((0 + 1) + 2) + 3) = 6
  • 14. IO as a Monoid trait IO[A] { self => def run:A def ++[B](io: IO[B]): IO[B] = new IO[B] { def run: B = { self.run; io.run } } } object IO { def zero: IO[Unit] = new IO[Unit] { def run: Unit = { } } def ++[A, B](io1: IO[A], io2: IO[B]): IO[B] = io1 ++ io2 }
  • 15. IO Monoid Example Usage (PrintLine("Hello!") ++ PrintLine("World!")).run Hello! World! val ioList = List(PrintLine("One"), PrintLine("Two"), PrintLine("Three")) ioList.fold(IO.zero)(IO.++).run One Two Three
  • 16. That’s great, but… • We can chain together output effects • But, how can we actually perform any operations on input effects? • Monads! • Wait, another one of those big words…
  • 17. Monad refresher • Monads provide two operations: • def map[B](f:A => B): IO[B] • def flatMap[B](f:A => IO[B]): IO[B]
  • 18. Monad refresher • map transforms a value from domainA to a value from domain B inside the monad • flatMap transforms a value from domain A into a Monad containing a value from domain B, and then returns a monad containing a value from domain B
  • 19. How does this help? • map takes the result of an IO monad and perform computations on the value • flatMap takes the result of an IO monad, and then perform IO for output • Like control flow for the IO “program”
  • 20. IO as a Monad trait IO[A] { self => def run:A def ++[B](io: IO[B]): IO[B] = new IO[B] { def run: B = { self.run; io.run } } def map[B](f: A => B): IO[B] = new IO[B] { def run: B = f(self.run) } def flatMap[B](f: A => IO[B]): IO[B] = new IO[B] { def run: B = f(self.run).run } } def PrintLine(msg: String): IO[Unit] = new IO[Unit] { def run: Unit = println(msg) } def GetLine(): IO[String] = new IO[String] { def run: String = scala.io.StdIn.readLine() }
  • 21. Lets put it together! PrintLine("Type a number") .flatMap(_ => GetLine()) .map(s => s.toInt) .map(i => i + 1) .flatMap(r => PrintLine(r.toString)) .run Type a number  1 2
  • 22. Can’t mix and match GetLine().toInt + 1 <console>:15: error: value toInt is not a member of IO[String] GetLine().toInt + 1
  • 23. Some useful operations def doWhile[A](a: IO[A])(cond: A => IO[Boolean]): IO[Unit] = for { a1 <- a ok <- cond(a1) _ <- if (ok) doWhile(a)(cond) else IO.zero } yield () def forever[A, B](a: IO[A]): IO[B] = a flatMap(_ => forever(a))
  • 24. Houston, we have a problem! forever(PrintLine("Hello")).run Hello Hello … <Stack Overflow!> • What happened?!
  • 25. Not tail recursive def flatMap[B](f: A => IO[B]): IO[B] = new IO[B] { def run: B = f(self.run).run } def forever[B](a: IO[A]): IO[B] = a flatMap(_ => forever(a)) • forever keeps on calling flatMap • Every time flatMap is called, we end up one level lower in recursion • Since the function definition says we need to keep track of the results of f(this.run) to call run on it, we keep on adding on to the call stack every time
  • 26. How do we fix this? • Use Trampolining • Create a series ofAlgebraic DataTypes (ADTs) to describe how the operation will run • We can make running our IO monad operations tail recursive • First things first… • What is tail recursion? • What is anADT?
  • 27. Tail Recursion • Don’t want to keep track of state after calling function recursively • If the last function run is the recursive call, then it is in tail position • We can skip adding a stack frame
  • 28. Tail recursion example def factorial(n: BigInt): BigInt = if (n == 0) 1 else n * factorial(n-1) def factorial_tailrec(n: BigInt): BigInt = { def factorial1(n: BigInt, acc: BigInt): BigInt = if (n == 0) acc else factorial1(n - 1, n * acc) factorial1(n, 1) }
  • 29. Algebraic DataTypes • This is a data type that can be one of several things, and each thing can contain a defined set of data • We can use pattern matching to perform operations on the ADT • This is probably more apparent if we just give an example…
  • 30. List ADT definition sealed trait List[+A] case class Cons[+A](head: A, tail: List[A]) extends List[A] case object Nil extends List[Nothing]
  • 31. List ADT Pattern Matching val a = Cons(1, Nil) val b = Cons(2, a) val c = Cons(3, b) def add(list: List[Int]): Int = list match { case Cons(head, tail) => head + add(tail) case Nil => 0 } add(c) 6 3 2 1
  • 32. Describing IO monad operations with an ADT sealed trait IO[A] { self => def flatMap[B](f: A => IO[B]): IO[B] = FlatMap(self, f) def map[B](f: A => B): IO[B] = flatMap(a => (Return(f(a)))) } case class Return[A](a: A) extends IO[A] case class Suspend[A](resume: () =>A) extends IO[A] case class FlatMap[A, B](sub: IO[A], k: A => IO[B]) extends IO[B] def PrintLine(s: String): IO[Unit] = Suspend(() => Return(println(s))) def GetLine: IO[String] = Suspend(() => scala.io.StdIn.readLine())
  • 33. Our example from earlier! PrintLine("Type a number") .flatMap(_ => GetLine) .map(s => s.toInt) .map(i => i + 1) .flatMap(r => PrintLine(r.toString)) Suspend PrintLine Prompt
  • 34. Our example from earlier! PrintLine("Type a number") .flatMap(_ => GetLine) .map(s => s.toInt) .map(i => i + 1) .flatMap(r => PrintLine(r.toString)) Suspend PrintLine Prompt FlatMap Suspend GetLine
  • 35. Our example from earlier! PrintLine("Type a number") .flatMap(_ => GetLine) .map(s => s.toInt) .map(i => i + 1) .flatMap(r => PrintLine(r.toString)) Suspend PrintLine Prompt FlatMap Suspend GetLine Return s.toInt FlatMap
  • 36. Our example from earlier! PrintLine("Type a number") .flatMap(_ => GetLine) .map(s => s.toInt) .map(i => i + 1) .flatMap(r => PrintLine(r.toString)) Suspend PrintLine Prompt FlatMap Suspend GetLine Return s.toInt Return i + 1 FlatMap FlatMap
  • 37. Our example from earlier! PrintLine("Type a number") .flatMap(_ => GetLine) .map(s => s.toInt) .map(i => i + 1) .flatMap(r => PrintLine(r.toString)) Suspend PrintLine Prompt FlatMap Suspend GetLine Return s.toInt Return i + 1 Suspend PrintLine Result FlatMap FlatMap FlatMap
  • 38. Now we need the Interpreter… def run[A](io: IO[A]): A = io match { case Return(a) => a case Suspend(r) => r() case FlatMap(x, f) => x match { case Return(a) => run(f(a)) case Suspend(r) => run(f(r())) case FlatMap(y, g) => run(y.flatMap(a => g(a).flatMap(f))) } }
  • 39. Run the example Suspend PrintLine Prompt FlatMap Suspend GetLine Return s.toInt Return i + 1 Suspend PrintLine Result FlatMap FlatMap FlatMap PrintLine("Type a number") .flatMap(_ => GetLine) .map(s => s.toInt) .map(i => i + 1) .flatMap(r => PrintLine(r.toString))
  • 40. Run the example Suspend PrintLine Prompt FlatMap Suspend GetLine Return s.toInt Return i + 1 Suspend PrintLine Result FlatMap FlatMap FlatMap PrintLine("Type a number") .flatMap(_ => GetLine) .map(s => s.toInt) .map(i => i + 1) .flatMap(r => PrintLine(r.toString))
  • 41. Run the example Suspend PrintLine Prompt FlatMap Suspend GetLine Return s.toInt Return i + 1 Suspend PrintLine Result FlatMap FlatMap FlatMap PrintLine("Type a number") .flatMap(_ => GetLine) .map(s => s.toInt) .map(i => i + 1) .flatMap(r => PrintLine(r.toString))
  • 42. Run the example Suspend PrintLine Prompt FlatMap Suspend GetLine Return s.toInt Return i + 1 Suspend PrintLine Result FlatMap FlatMap FlatMap PrintLine("Type a number") .flatMap(_ => GetLine) .map(s => s.toInt) .map(i => i + 1) .flatMap(r => PrintLine(r.toString))
  • 43. Run the example Suspend PrintLine Prompt FlatMap Suspend GetLine Return s.toInt Return i + 1 Suspend PrintLine Result FlatMap FlatMap FlatMap PrintLine("Type a number") .flatMap(_ => GetLine) .map(s => s.toInt) .map(i => i + 1) .flatMap(r => PrintLine(r.toString))
  • 44. Run the example Suspend GetLine Return s.toInt Return i + 1 Suspend PrintLine Result FlatMap FlatMap FlatMap PrintLine("Type a number") .flatMap(_ => GetLine) .map(s => s.toInt) .map(i => i + 1) .flatMap(r => PrintLine(r.toString))
  • 45. Run the example Return s.toInt Return i + 1 Suspend PrintLine Result FlatMap FlatMap PrintLine("Type a number") .flatMap(_ => GetLine) .map(s => s.toInt) .map(i => i + 1) .flatMap(r => PrintLine(r.toString))
  • 46. Run the example Return i + 1 Suspend PrintLine Result FlatMap PrintLine("Type a number") .flatMap(_ => GetLine) .map(s => s.toInt) .map(i => i + 1) .flatMap(r => PrintLine(r.toString))
  • 47. Run the example Suspend PrintLine Result PrintLine("Type a number") .flatMap(_ => GetLine) .map(s => s.toInt) .map(i => i + 1) .flatMap(r => PrintLine(r.toString))
  • 48. How about forever? def forever[B](a: IO[A]): IO[B] = a flatMap(_ => forever(a)) run(forever(PrintLine("Hello"))) Suspend PrintLine FlatMap
  • 50. Takeaways • Like a Free Monad! (A story for another day…) • You don’t have to use the same interpreter... you can represent IO as ADTs and not perform IO at all • Effects are purposeful and partitioned off within a type • Bonus – it happens to be a monad!
  • 51. Other libraries • Slick • Describe SQL operations • Execute them once they are prepared • Akka Streams • Describe a dataflow with a graph • Push data through the graph components • In Scala, this is an advanced flavor of the IO monad described here
  • 53. References • Functional Programming in Scala, Chiusano and Bjarnason • https://www.slideshare.net/InfoQ/purely-functional-io - Nice presentation by Runar Bjarnason • http://blog.higher-order.com/assets/scalaio.pdf

Editor's Notes

  • #8: Testability It is much easier to create test cases when the same inputs predictably result in the same output Promotes parallel programming When the results will be the same for a computation regardless of what is externally happening, parallelizing computations becomes almost trivial Can enable optimization of code For example, in Haskell, which is purely functional, execution order of functions in an expression is not defined and may not even be run at all (called laziness) This is safe to do because the compiler knows that running one function cannot change the result of running another function later if the inputs are the same
  • #10: We can separate IO into a type and define actions inside Allows us to denote which pieces of the code are actions We can also defer running an action until later Then, writing a program involves describing a program containing effects purely functionally, and then executing it
  • #19: map takes a value in domain A inside the type and performs a function to transform it to domain B Think of it like the Monad is a box that contains a value, and you are running a function to transform that value flatMap is like map, except the function returns another monad wrapping the result, and this is flattened down to one level Think of it like map, except you run a function that transforms that value into another box of the same type inside with a value inside it Then you take the item out of the inside box and make it the top level value
  • #20: map allows us to take the result of an IO monad as an input and perform computations on the value flatMap allows us to take the result of an IO monad as an input, and then perform IO for output Think of it like control flow for the IO “program” Pretty abstract, right? Let’s take a look at how it gets put together…
  • #27: We use a concept called Trampolining Rather than actually making the function call, we create a series of Algebraic Data Types (ADTs) to describe how the operation will run Using this, we can make running our IO monad operations tail recursive First things first… What is tail recursion? What is an ADT?
  • #28: We are trying to make it so when we call our function recursively, we don’t have to keep track of the result of the computation to combine it with another operation If the very last function call is the recursive call, then it is in tail position and we can skip adding a stack frame
  • #51: This approach is similar to a general concept called Free Monads, but that is a subject for another day… You don’t have to use the same interpreter... you can represent IO as ADTs and not perform IO at all Maybe just always return the same string Maybe write to a file rather than to the console instead Effects are purposeful and partitioned off within a type (which happens to be a monad!) These are not side effects! Describe the computation using an ADT purely functionally Execute the ADT with an intepreter