SlideShare uma empresa Scribd logo
1 de 36
Baixar para ler offline
Pure Functional
Programming
Die letzte Meile
What is pure FP, and why bother?
● Are these the same?
● If foo is _.length, they are. If foo is println, they’re not.
● functions like _.length are called “referentially transparent”
○ function calls can always be replaced by the values that they calculate
● much easier to reason about
● so let’s make every function like that!
foo("ha") |+| foo("ha") val ha = foo("ha")
ha |+| ha
Totality
● Referential transparency implies: every function must be total
○ Must return after a finite time
○ Must not throw exceptions
○ Must not return null
Leads to:
● fewer crashes (invalid arguments ruled out by type system)
● easier refactorings (“if it compiles, it’s probably OK”)
Determinism
● Passing the same arguments to a function must lead to the same result
● Much easier to test: no need to set up state, just pass some arguments to a
function and compare the result
● everything that’s relevant shows up in the function’s signature, thus easier to
follow
Parametricity
● Generic functions must behave uniformly for all possible type arguments
○ no isInstanceOf (it’s broken anyway because of erasure)
● This means more restrictions
● But you can learn much more about a function from its signature
● This is called parametric reasoning/free theorems
Parametricity, contd.
● def f[A](a: A): A
● What does it do?
○ Is it referentially transparent, total, deterministic and parametric?
○ If so, there is only one possible implementation, the identity function
● def f[A](a: List[A]): List[A]
○ function doesn’t know what A is, therefore:
○ can’t add new elements, only use elements from input list
○ can only reorder or remove elements
○ will do that in the same way for all types (because we can’t perform any tests on the input)
● learned all of that by only looking at the signature
I/O in FP
Now what?
● Totality
○ so I can’t loop forever?
○ no exceptions, so no errors?!
● Referential transparency
○ so I can’t use println?
● Determinism
○ so I can’t read input?
● How to get anything done?!
So how do we do anything useful?
● Pure FP as defined before is useless for doing stuff
● But it’s really good at manipulating values
● So instead of writing a program that does stuff, create a value that contains
instructions of what needs to be done
● some kind of run-time takes care of executing that description
We need:
● A type for these instructions: IO
● Some primitives to create such instructions, e. g. putStrLn
● Ways to combine these primitives into larger, more complex descriptions
IO Monoid
● val unit: IO
○ instructions to do nothing
● def putStrLn(s: String): IO
○ doesn’t print anything
○ returns instructions on how to print the String s
● |+| binary operator
○ takes two IO values and creates a new one
○ returned value is an instruction to perform the two instructions after each other
● This is already useful! e. g. 99 bottles of beer
val bottles: IO = (1 to 99).reverse.map { n =>
putStrLn(s"$n bottles standing on a wall etc. pp.")
}.foldLeft(unit)(_ |+| _)
IO Monad
● IO Monoid isn’t powerful enough
○ output seems to work fine
○ no way to do input
● Need a way for later IO instructions to depend on the results of earlier
instructions
IO Monad, contd.
● Add a type parameter: IO[A]
○ a set of instructions that, when executed, will yield a value of type A
○ e. g. readLine: IO[String]
○ e. g. putStrLn(s: String): IO[Unit]
● And a new combinator flatMap
○ flatMap: (IO[A], A => IO[B]) => IO[B]
○ returns instructions to:
■ execute the IO[A]
■ feed the resulting value to the function
■ then execute the instructions returned by the function
● Also need pure[A](a: A): IO[A]
○ instructions to do nothing, just return the provided value
IO Monad, contd.
● Now we have interactivity!
● Slightly ugly due to nesting, but Scala has syntax sugar for this!
● Again: executing this code will not cause anything to happen
val helloWorld: IO[Unit] =
putStrLn("Hi, what’s your name?").flatMap { _ =>
getStrLn.flatMap { name =>
putStrLn(s"Hello, $name!")
}
}
val helloWorld: IO[Unit] = for {
_ <- putStrLn("Hi, what’s your name?")
name <- getStrLn
_ <- putStrLn(s"Hello, $name")
} yield ()
Guessing game
● That’s nice and everything, but how can we loop? Don’t we need state for
that?
○ No, use recursion
def guessingGame(i: Int): IO[Unit] =
getStrLn.flatMap { str =>
val n = str.toInt // ask Hendrik about error handling ;-)
if (n == i) {
putStrLn("You win!")
} else if (n < i) {
putStrLn("Too low!") >> guessingGame(i)
} else
putStrLn("Too high!") >> guessingGame(i)
}
a >> b is a shortcut for a.flatMap(_ => b)
Effect primitives
● so far treated putStrLn and getStrLn as “magic”
● need a way to call side-effecting functions in a referentially transparent
manner to e. g. use impure libraries
● specifics depend on the IO implementation
● several exist: scalaz-zio, cats-effect
● in cats-effect:
● other ways exist for different use cases (e. g. asynchronous IO)
def putStrLn(s: String): IO[Unit] =
IO.delay(println(s))
The end of the world
● so far we’ve only assembled instructions
● need to actually run them
● If you control the main function:
● If you don’t:
object MyApp extends IOApp {
def run(args: List[String]): IO[ExitCode] =
putStrLn("Hello, World!").as(ExitCode.Success)
}
val myIO = putStrLn("Hello, World!")
myIO.unsafeRunSync()
Why is this awesome?
● Concurrency
○ uniform treatment of synchronous and asynchronous I/O
○ very simple parallelism (e. g. parMapN)
○ light-weight threads (“Fibers”)
○ can safely interrupt threads without leaking resources
○ way more efficient than e. g. scala.concurrent.Future
○ don’t need an implicit ExecutionContext
○ built-in utilities for concurrent programming, e. g. queues, publish-subscribe, MVar, …
● I/O objects are first-class
○ can apply equational reasoning
○ transform them in arbitrary ways, e. g. write your own control structures!
○ many useful helper functions in libraries
Error handling in FP
Error handling in Functional Programming
Throwing exceptions is bad because
● Multiple exit points of a function
● Possible return values of a function are not seen in its signature
● Uncaught exceptions crash the program and there is no way for the compiler
to warn you about that
Alternative to throwing Exceptions
Dedicated data types
● Either[A,B]
○ Fail fast
○ Right(“computed value”) or Left(“failure description”)
● Validated[A,B]
○ error accumulation
○ Valid(“computed value”) or Invalid(NonEmptyList(“failure1”,
“failure2”))
Example: Get 42 divided by the min digit of a String
def minDigit(s: String): Int = s.sliding(1).map(_.toInt).min
def divide42By(i: Int): Int = 42 / i
def get42DividedByMinDigit: String => Int =
minDigit _ andThen divide42By
try {
get42DividedByMinDigit("51414")
} catch {
case x: NumberFormatException => s"Number format exception ${x.getMessage}"
case x: ArithmeticException => s"Arithmetic exception ${x.getMessage}"
case NonFatal(x) => s"Unknown exception ${x.getMessage}"
}
Link
With Either, we know that functions can fail
import cats.implicits._
def minDigit(s: String): Either[Throwable, Int] =
Either.catchNonFatal(s.sliding(1).map(_.toInt).min)
def divide42By(i: Int): Either[Throwable, Int] =
Either.catchNonFatal(42 / i)
def get42DividedByMinDigit(s: String): Either[Throwable, Int] =
for {
md <- minDigit(s)
res <- divide42By(md)
} yield res
Unknown exception case still not nice
get42DividedByMinDigit(null) match {
case Left(x: NumberFormatException) =>
s"Number format exception ${x.getMessage}"
case Left(x: ArithmeticException) =>
s"Arithmetic exception ${x.getMessage}"
case Left(x) =>
s"Unknown exception ${x.getMessage}"
case Right(x) =>
x.toString
}
Link
Error model that allows to limit the possible errors
sealed trait AppError
case object EmptyString extends AppError
case object NonDigitsInString extends AppError
case object DivisionByZero extends AppError
Function returns Either[AppError, Int]
import cats.implicits._
def minDigitSafe(s: String): Either[AppError, Int] = s match {
case null | "" => Left(EmptyString)
case _ if s.exists(!_.isDigit) => Left(NonDigitsInString)
case _ => Right(s.sliding(1).map(_.toInt).min)
}
def divide42By(i: Int): Either[AppError, Int] =
Either.catchOnly[ArithmeticException](42 / i).leftMap(_ => DivisionByZero)
def get42DividedByMinDigit(s: String): Either[AppError, Int] =
for {
md <- minDigitSafe(s)
i <- divide42By(md)
} yield i
Compiler checks if all error cases are handled
get42DividedByMinDigit("203") match {
case Right(i) => s"Min digit is $i"
case Left(EmptyString) => s"Empty string"
case Left(DivisionByZero) => s"Division by zero"
case Left(NonDigitsInString) => s"Non digits in string"
}
Link
How to combine IO with Either
● Throwables in IO
● MonadTransformers (EitherT)
● BifunctorIO
Resources in FP
Problem
● Closing allocated resources is easily forgotten
○ especially in case of an error or
○ with nested resources
● try-catch-finally has its own problems
● Often, allocation and clean-up code end up in different places
Example
def openStream(filename: String): Option[FileInputStream] =
try {
Some(new FileInputStream(filename))
} catch {
case _: FileNotFoundException => None
}
def openReader(is: InputStream): Option[InputStreamReader] =
try {
Some(new InputStreamReader(is))
} catch {
case NonFatal(_) => None
}
def doSomething(reader: InputStreamReader): Result = ???
Example (contd.)
/* allocate resources */
val resources: Option[(InputStream, Option[InputStreamReader])] =
openStream("foo").map(is => (is, openReader(is)))
try {
resources.map(_._2.map(doSomething))
} catch {
case NonFatal(_) => /* handle error */
} finally {
resources.map(_._2.map(_.close))
resources.map(_._1.close)
}
Close in reverse
order!
Used anywhere else?
A functional solution
● Resources need to be closed in any case
● Resources should be composable
● No try-catch-finally; no throw
● Resources should be usable, modifiable w/o losing the above properties
cats.effect.Resource
● Create a Resource:
def make[F[_], A](acquire: F[A])(release: A => F[Unit]): Resource[F, A] = ...
● Composition:
def flatMap[B](f: A => Resource[F, B]): Resource[F, B] = ...
● Use a Resource:
def use[B](f: A => F[B]): F[B] = ...
Example revisited
def openStream(filename: String): Resource[IO, FileInputStream] =
Resource.make { IO(new FileInputStream(filename)) } { r =>
IO(r.close)
}
def openReader(is: InputStream): Resource[IO, InputStreamReader] = ...
val r: Resource[IO, InputStreamReader] = for {
is <- openStream("foo")
reader <- openReader(is)
} yield reader
r.use(r => IO(doSomething(r)))
/* or even shorter: */
val p: IO[Result] = openStream("foo").use { ra =>
openReader(ra).use { rb =>
IO(doSomething(rb))
}
}
Summary
● Resources are guaranteed to be closed if:
○ program using the Resource is completed
○ acquisition of nested resources fails
○ acquisition of composed resources fails
○ using the resource produces an error
○ computation using the resource is cancelled (by another thread!)
Questions?
Matthias.Berndt@cognotekt.com
Hendrik.Guhlich@cognotekt.com
Daniel.Beck@cognotekt.com

Mais conteúdo relacionado

Semelhante a pure-functional-programming.pdf

uom-2552-what-is-python-presentation (1).pptx
uom-2552-what-is-python-presentation (1).pptxuom-2552-what-is-python-presentation (1).pptx
uom-2552-what-is-python-presentation (1).pptx
Prabha Karan
 
How to start functional programming (in Scala): Day1
How to start functional programming (in Scala): Day1How to start functional programming (in Scala): Day1
How to start functional programming (in Scala): Day1
Taisuke Oe
 
JavaScript blast
JavaScript blastJavaScript blast
JavaScript blast
dominion
 

Semelhante a pure-functional-programming.pdf (20)

What is Paython.pptx
What is Paython.pptxWhat is Paython.pptx
What is Paython.pptx
 
uom-2552-what-is-python-presentation.pptx
uom-2552-what-is-python-presentation.pptxuom-2552-what-is-python-presentation.pptx
uom-2552-what-is-python-presentation.pptx
 
uom-2552-what-is-python-presentation (1).pptx
uom-2552-what-is-python-presentation (1).pptxuom-2552-what-is-python-presentation (1).pptx
uom-2552-what-is-python-presentation (1).pptx
 
python-presentation.pptx
python-presentation.pptxpython-presentation.pptx
python-presentation.pptx
 
Twins: Object Oriented Programming and Functional Programming
Twins: Object Oriented Programming and Functional ProgrammingTwins: Object Oriented Programming and Functional Programming
Twins: Object Oriented Programming and Functional Programming
 
Ruby Functional Programming
Ruby Functional ProgrammingRuby Functional Programming
Ruby Functional Programming
 
What are monads?
What are monads?What are monads?
What are monads?
 
Python functional programming
Python functional programmingPython functional programming
Python functional programming
 
Scala qq
Scala qqScala qq
Scala qq
 
How to start functional programming (in Scala): Day1
How to start functional programming (in Scala): Day1How to start functional programming (in Scala): Day1
How to start functional programming (in Scala): Day1
 
Introduction to Kotlin.pptx
Introduction to Kotlin.pptxIntroduction to Kotlin.pptx
Introduction to Kotlin.pptx
 
01 Introduction to Kotlin - Programming in Kotlin.pptx
01 Introduction to Kotlin - Programming in Kotlin.pptx01 Introduction to Kotlin - Programming in Kotlin.pptx
01 Introduction to Kotlin - Programming in Kotlin.pptx
 
(3) cpp procedural programming
(3) cpp procedural programming(3) cpp procedural programming
(3) cpp procedural programming
 
JavaScript blast
JavaScript blastJavaScript blast
JavaScript blast
 
Code optimization
Code optimization Code optimization
Code optimization
 
Code optimization
Code optimization Code optimization
Code optimization
 
Functional IO and Effects
Functional IO and EffectsFunctional IO and Effects
Functional IO and Effects
 
Function
FunctionFunction
Function
 
Dart workshop
Dart workshopDart workshop
Dart workshop
 
Go Lang Tutorial
Go Lang TutorialGo Lang Tutorial
Go Lang Tutorial
 

Último

Call for Papers - African Journal of Biological Sciences, E-ISSN: 2663-2187, ...
Call for Papers - African Journal of Biological Sciences, E-ISSN: 2663-2187, ...Call for Papers - African Journal of Biological Sciences, E-ISSN: 2663-2187, ...
Call for Papers - African Journal of Biological Sciences, E-ISSN: 2663-2187, ...
Christo Ananth
 
Call Girls In Bangalore ☎ 7737669865 🥵 Book Your One night Stand
Call Girls In Bangalore ☎ 7737669865 🥵 Book Your One night StandCall Girls In Bangalore ☎ 7737669865 🥵 Book Your One night Stand
Call Girls In Bangalore ☎ 7737669865 🥵 Book Your One night Stand
amitlee9823
 
Call Girls in Ramesh Nagar Delhi 💯 Call Us 🔝9953056974 🔝 Escort Service
Call Girls in Ramesh Nagar Delhi 💯 Call Us 🔝9953056974 🔝 Escort ServiceCall Girls in Ramesh Nagar Delhi 💯 Call Us 🔝9953056974 🔝 Escort Service
Call Girls in Ramesh Nagar Delhi 💯 Call Us 🔝9953056974 🔝 Escort Service
9953056974 Low Rate Call Girls In Saket, Delhi NCR
 
Call for Papers - Educational Administration: Theory and Practice, E-ISSN: 21...
Call for Papers - Educational Administration: Theory and Practice, E-ISSN: 21...Call for Papers - Educational Administration: Theory and Practice, E-ISSN: 21...
Call for Papers - Educational Administration: Theory and Practice, E-ISSN: 21...
Christo Ananth
 
Call Now ≽ 9953056974 ≼🔝 Call Girls In New Ashok Nagar ≼🔝 Delhi door step de...
Call Now ≽ 9953056974 ≼🔝 Call Girls In New Ashok Nagar  ≼🔝 Delhi door step de...Call Now ≽ 9953056974 ≼🔝 Call Girls In New Ashok Nagar  ≼🔝 Delhi door step de...
Call Now ≽ 9953056974 ≼🔝 Call Girls In New Ashok Nagar ≼🔝 Delhi door step de...
9953056974 Low Rate Call Girls In Saket, Delhi NCR
 

Último (20)

University management System project report..pdf
University management System project report..pdfUniversity management System project report..pdf
University management System project report..pdf
 
Call for Papers - African Journal of Biological Sciences, E-ISSN: 2663-2187, ...
Call for Papers - African Journal of Biological Sciences, E-ISSN: 2663-2187, ...Call for Papers - African Journal of Biological Sciences, E-ISSN: 2663-2187, ...
Call for Papers - African Journal of Biological Sciences, E-ISSN: 2663-2187, ...
 
Double Revolving field theory-how the rotor develops torque
Double Revolving field theory-how the rotor develops torqueDouble Revolving field theory-how the rotor develops torque
Double Revolving field theory-how the rotor develops torque
 
Generative AI or GenAI technology based PPT
Generative AI or GenAI technology based PPTGenerative AI or GenAI technology based PPT
Generative AI or GenAI technology based PPT
 
Call Girls In Bangalore ☎ 7737669865 🥵 Book Your One night Stand
Call Girls In Bangalore ☎ 7737669865 🥵 Book Your One night StandCall Girls In Bangalore ☎ 7737669865 🥵 Book Your One night Stand
Call Girls In Bangalore ☎ 7737669865 🥵 Book Your One night Stand
 
data_management_and _data_science_cheat_sheet.pdf
data_management_and _data_science_cheat_sheet.pdfdata_management_and _data_science_cheat_sheet.pdf
data_management_and _data_science_cheat_sheet.pdf
 
Booking open Available Pune Call Girls Pargaon 6297143586 Call Hot Indian Gi...
Booking open Available Pune Call Girls Pargaon  6297143586 Call Hot Indian Gi...Booking open Available Pune Call Girls Pargaon  6297143586 Call Hot Indian Gi...
Booking open Available Pune Call Girls Pargaon 6297143586 Call Hot Indian Gi...
 
chapter 5.pptx: drainage and irrigation engineering
chapter 5.pptx: drainage and irrigation engineeringchapter 5.pptx: drainage and irrigation engineering
chapter 5.pptx: drainage and irrigation engineering
 
KubeKraft presentation @CloudNativeHooghly
KubeKraft presentation @CloudNativeHooghlyKubeKraft presentation @CloudNativeHooghly
KubeKraft presentation @CloudNativeHooghly
 
The Most Attractive Pune Call Girls Budhwar Peth 8250192130 Will You Miss Thi...
The Most Attractive Pune Call Girls Budhwar Peth 8250192130 Will You Miss Thi...The Most Attractive Pune Call Girls Budhwar Peth 8250192130 Will You Miss Thi...
The Most Attractive Pune Call Girls Budhwar Peth 8250192130 Will You Miss Thi...
 
Bhosari ( Call Girls ) Pune 6297143586 Hot Model With Sexy Bhabi Ready For ...
Bhosari ( Call Girls ) Pune  6297143586  Hot Model With Sexy Bhabi Ready For ...Bhosari ( Call Girls ) Pune  6297143586  Hot Model With Sexy Bhabi Ready For ...
Bhosari ( Call Girls ) Pune 6297143586 Hot Model With Sexy Bhabi Ready For ...
 
Call Girls in Ramesh Nagar Delhi 💯 Call Us 🔝9953056974 🔝 Escort Service
Call Girls in Ramesh Nagar Delhi 💯 Call Us 🔝9953056974 🔝 Escort ServiceCall Girls in Ramesh Nagar Delhi 💯 Call Us 🔝9953056974 🔝 Escort Service
Call Girls in Ramesh Nagar Delhi 💯 Call Us 🔝9953056974 🔝 Escort Service
 
ONLINE FOOD ORDER SYSTEM PROJECT REPORT.pdf
ONLINE FOOD ORDER SYSTEM PROJECT REPORT.pdfONLINE FOOD ORDER SYSTEM PROJECT REPORT.pdf
ONLINE FOOD ORDER SYSTEM PROJECT REPORT.pdf
 
CCS335 _ Neural Networks and Deep Learning Laboratory_Lab Complete Record
CCS335 _ Neural Networks and Deep Learning Laboratory_Lab Complete RecordCCS335 _ Neural Networks and Deep Learning Laboratory_Lab Complete Record
CCS335 _ Neural Networks and Deep Learning Laboratory_Lab Complete Record
 
Call for Papers - Educational Administration: Theory and Practice, E-ISSN: 21...
Call for Papers - Educational Administration: Theory and Practice, E-ISSN: 21...Call for Papers - Educational Administration: Theory and Practice, E-ISSN: 21...
Call for Papers - Educational Administration: Theory and Practice, E-ISSN: 21...
 
(INDIRA) Call Girl Bhosari Call Now 8617697112 Bhosari Escorts 24x7
(INDIRA) Call Girl Bhosari Call Now 8617697112 Bhosari Escorts 24x7(INDIRA) Call Girl Bhosari Call Now 8617697112 Bhosari Escorts 24x7
(INDIRA) Call Girl Bhosari Call Now 8617697112 Bhosari Escorts 24x7
 
Roadmap to Membership of RICS - Pathways and Routes
Roadmap to Membership of RICS - Pathways and RoutesRoadmap to Membership of RICS - Pathways and Routes
Roadmap to Membership of RICS - Pathways and Routes
 
Booking open Available Pune Call Girls Koregaon Park 6297143586 Call Hot Ind...
Booking open Available Pune Call Girls Koregaon Park  6297143586 Call Hot Ind...Booking open Available Pune Call Girls Koregaon Park  6297143586 Call Hot Ind...
Booking open Available Pune Call Girls Koregaon Park 6297143586 Call Hot Ind...
 
Call Now ≽ 9953056974 ≼🔝 Call Girls In New Ashok Nagar ≼🔝 Delhi door step de...
Call Now ≽ 9953056974 ≼🔝 Call Girls In New Ashok Nagar  ≼🔝 Delhi door step de...Call Now ≽ 9953056974 ≼🔝 Call Girls In New Ashok Nagar  ≼🔝 Delhi door step de...
Call Now ≽ 9953056974 ≼🔝 Call Girls In New Ashok Nagar ≼🔝 Delhi door step de...
 
VIP Model Call Girls Kothrud ( Pune ) Call ON 8005736733 Starting From 5K to ...
VIP Model Call Girls Kothrud ( Pune ) Call ON 8005736733 Starting From 5K to ...VIP Model Call Girls Kothrud ( Pune ) Call ON 8005736733 Starting From 5K to ...
VIP Model Call Girls Kothrud ( Pune ) Call ON 8005736733 Starting From 5K to ...
 

pure-functional-programming.pdf

  • 2. What is pure FP, and why bother? ● Are these the same? ● If foo is _.length, they are. If foo is println, they’re not. ● functions like _.length are called “referentially transparent” ○ function calls can always be replaced by the values that they calculate ● much easier to reason about ● so let’s make every function like that! foo("ha") |+| foo("ha") val ha = foo("ha") ha |+| ha
  • 3. Totality ● Referential transparency implies: every function must be total ○ Must return after a finite time ○ Must not throw exceptions ○ Must not return null Leads to: ● fewer crashes (invalid arguments ruled out by type system) ● easier refactorings (“if it compiles, it’s probably OK”)
  • 4. Determinism ● Passing the same arguments to a function must lead to the same result ● Much easier to test: no need to set up state, just pass some arguments to a function and compare the result ● everything that’s relevant shows up in the function’s signature, thus easier to follow
  • 5. Parametricity ● Generic functions must behave uniformly for all possible type arguments ○ no isInstanceOf (it’s broken anyway because of erasure) ● This means more restrictions ● But you can learn much more about a function from its signature ● This is called parametric reasoning/free theorems
  • 6. Parametricity, contd. ● def f[A](a: A): A ● What does it do? ○ Is it referentially transparent, total, deterministic and parametric? ○ If so, there is only one possible implementation, the identity function ● def f[A](a: List[A]): List[A] ○ function doesn’t know what A is, therefore: ○ can’t add new elements, only use elements from input list ○ can only reorder or remove elements ○ will do that in the same way for all types (because we can’t perform any tests on the input) ● learned all of that by only looking at the signature
  • 8. Now what? ● Totality ○ so I can’t loop forever? ○ no exceptions, so no errors?! ● Referential transparency ○ so I can’t use println? ● Determinism ○ so I can’t read input? ● How to get anything done?!
  • 9. So how do we do anything useful? ● Pure FP as defined before is useless for doing stuff ● But it’s really good at manipulating values ● So instead of writing a program that does stuff, create a value that contains instructions of what needs to be done ● some kind of run-time takes care of executing that description We need: ● A type for these instructions: IO ● Some primitives to create such instructions, e. g. putStrLn ● Ways to combine these primitives into larger, more complex descriptions
  • 10. IO Monoid ● val unit: IO ○ instructions to do nothing ● def putStrLn(s: String): IO ○ doesn’t print anything ○ returns instructions on how to print the String s ● |+| binary operator ○ takes two IO values and creates a new one ○ returned value is an instruction to perform the two instructions after each other ● This is already useful! e. g. 99 bottles of beer val bottles: IO = (1 to 99).reverse.map { n => putStrLn(s"$n bottles standing on a wall etc. pp.") }.foldLeft(unit)(_ |+| _)
  • 11. IO Monad ● IO Monoid isn’t powerful enough ○ output seems to work fine ○ no way to do input ● Need a way for later IO instructions to depend on the results of earlier instructions
  • 12. IO Monad, contd. ● Add a type parameter: IO[A] ○ a set of instructions that, when executed, will yield a value of type A ○ e. g. readLine: IO[String] ○ e. g. putStrLn(s: String): IO[Unit] ● And a new combinator flatMap ○ flatMap: (IO[A], A => IO[B]) => IO[B] ○ returns instructions to: ■ execute the IO[A] ■ feed the resulting value to the function ■ then execute the instructions returned by the function ● Also need pure[A](a: A): IO[A] ○ instructions to do nothing, just return the provided value
  • 13. IO Monad, contd. ● Now we have interactivity! ● Slightly ugly due to nesting, but Scala has syntax sugar for this! ● Again: executing this code will not cause anything to happen val helloWorld: IO[Unit] = putStrLn("Hi, what’s your name?").flatMap { _ => getStrLn.flatMap { name => putStrLn(s"Hello, $name!") } } val helloWorld: IO[Unit] = for { _ <- putStrLn("Hi, what’s your name?") name <- getStrLn _ <- putStrLn(s"Hello, $name") } yield ()
  • 14. Guessing game ● That’s nice and everything, but how can we loop? Don’t we need state for that? ○ No, use recursion def guessingGame(i: Int): IO[Unit] = getStrLn.flatMap { str => val n = str.toInt // ask Hendrik about error handling ;-) if (n == i) { putStrLn("You win!") } else if (n < i) { putStrLn("Too low!") >> guessingGame(i) } else putStrLn("Too high!") >> guessingGame(i) } a >> b is a shortcut for a.flatMap(_ => b)
  • 15. Effect primitives ● so far treated putStrLn and getStrLn as “magic” ● need a way to call side-effecting functions in a referentially transparent manner to e. g. use impure libraries ● specifics depend on the IO implementation ● several exist: scalaz-zio, cats-effect ● in cats-effect: ● other ways exist for different use cases (e. g. asynchronous IO) def putStrLn(s: String): IO[Unit] = IO.delay(println(s))
  • 16. The end of the world ● so far we’ve only assembled instructions ● need to actually run them ● If you control the main function: ● If you don’t: object MyApp extends IOApp { def run(args: List[String]): IO[ExitCode] = putStrLn("Hello, World!").as(ExitCode.Success) } val myIO = putStrLn("Hello, World!") myIO.unsafeRunSync()
  • 17. Why is this awesome? ● Concurrency ○ uniform treatment of synchronous and asynchronous I/O ○ very simple parallelism (e. g. parMapN) ○ light-weight threads (“Fibers”) ○ can safely interrupt threads without leaking resources ○ way more efficient than e. g. scala.concurrent.Future ○ don’t need an implicit ExecutionContext ○ built-in utilities for concurrent programming, e. g. queues, publish-subscribe, MVar, … ● I/O objects are first-class ○ can apply equational reasoning ○ transform them in arbitrary ways, e. g. write your own control structures! ○ many useful helper functions in libraries
  • 19. Error handling in Functional Programming Throwing exceptions is bad because ● Multiple exit points of a function ● Possible return values of a function are not seen in its signature ● Uncaught exceptions crash the program and there is no way for the compiler to warn you about that
  • 20. Alternative to throwing Exceptions Dedicated data types ● Either[A,B] ○ Fail fast ○ Right(“computed value”) or Left(“failure description”) ● Validated[A,B] ○ error accumulation ○ Valid(“computed value”) or Invalid(NonEmptyList(“failure1”, “failure2”))
  • 21. Example: Get 42 divided by the min digit of a String def minDigit(s: String): Int = s.sliding(1).map(_.toInt).min def divide42By(i: Int): Int = 42 / i def get42DividedByMinDigit: String => Int = minDigit _ andThen divide42By try { get42DividedByMinDigit("51414") } catch { case x: NumberFormatException => s"Number format exception ${x.getMessage}" case x: ArithmeticException => s"Arithmetic exception ${x.getMessage}" case NonFatal(x) => s"Unknown exception ${x.getMessage}" } Link
  • 22. With Either, we know that functions can fail import cats.implicits._ def minDigit(s: String): Either[Throwable, Int] = Either.catchNonFatal(s.sliding(1).map(_.toInt).min) def divide42By(i: Int): Either[Throwable, Int] = Either.catchNonFatal(42 / i) def get42DividedByMinDigit(s: String): Either[Throwable, Int] = for { md <- minDigit(s) res <- divide42By(md) } yield res
  • 23. Unknown exception case still not nice get42DividedByMinDigit(null) match { case Left(x: NumberFormatException) => s"Number format exception ${x.getMessage}" case Left(x: ArithmeticException) => s"Arithmetic exception ${x.getMessage}" case Left(x) => s"Unknown exception ${x.getMessage}" case Right(x) => x.toString } Link
  • 24. Error model that allows to limit the possible errors sealed trait AppError case object EmptyString extends AppError case object NonDigitsInString extends AppError case object DivisionByZero extends AppError
  • 25. Function returns Either[AppError, Int] import cats.implicits._ def minDigitSafe(s: String): Either[AppError, Int] = s match { case null | "" => Left(EmptyString) case _ if s.exists(!_.isDigit) => Left(NonDigitsInString) case _ => Right(s.sliding(1).map(_.toInt).min) } def divide42By(i: Int): Either[AppError, Int] = Either.catchOnly[ArithmeticException](42 / i).leftMap(_ => DivisionByZero) def get42DividedByMinDigit(s: String): Either[AppError, Int] = for { md <- minDigitSafe(s) i <- divide42By(md) } yield i
  • 26. Compiler checks if all error cases are handled get42DividedByMinDigit("203") match { case Right(i) => s"Min digit is $i" case Left(EmptyString) => s"Empty string" case Left(DivisionByZero) => s"Division by zero" case Left(NonDigitsInString) => s"Non digits in string" } Link
  • 27. How to combine IO with Either ● Throwables in IO ● MonadTransformers (EitherT) ● BifunctorIO
  • 29. Problem ● Closing allocated resources is easily forgotten ○ especially in case of an error or ○ with nested resources ● try-catch-finally has its own problems ● Often, allocation and clean-up code end up in different places
  • 30. Example def openStream(filename: String): Option[FileInputStream] = try { Some(new FileInputStream(filename)) } catch { case _: FileNotFoundException => None } def openReader(is: InputStream): Option[InputStreamReader] = try { Some(new InputStreamReader(is)) } catch { case NonFatal(_) => None } def doSomething(reader: InputStreamReader): Result = ???
  • 31. Example (contd.) /* allocate resources */ val resources: Option[(InputStream, Option[InputStreamReader])] = openStream("foo").map(is => (is, openReader(is))) try { resources.map(_._2.map(doSomething)) } catch { case NonFatal(_) => /* handle error */ } finally { resources.map(_._2.map(_.close)) resources.map(_._1.close) } Close in reverse order! Used anywhere else?
  • 32. A functional solution ● Resources need to be closed in any case ● Resources should be composable ● No try-catch-finally; no throw ● Resources should be usable, modifiable w/o losing the above properties
  • 33. cats.effect.Resource ● Create a Resource: def make[F[_], A](acquire: F[A])(release: A => F[Unit]): Resource[F, A] = ... ● Composition: def flatMap[B](f: A => Resource[F, B]): Resource[F, B] = ... ● Use a Resource: def use[B](f: A => F[B]): F[B] = ...
  • 34. Example revisited def openStream(filename: String): Resource[IO, FileInputStream] = Resource.make { IO(new FileInputStream(filename)) } { r => IO(r.close) } def openReader(is: InputStream): Resource[IO, InputStreamReader] = ... val r: Resource[IO, InputStreamReader] = for { is <- openStream("foo") reader <- openReader(is) } yield reader r.use(r => IO(doSomething(r))) /* or even shorter: */ val p: IO[Result] = openStream("foo").use { ra => openReader(ra).use { rb => IO(doSomething(rb)) } }
  • 35. Summary ● Resources are guaranteed to be closed if: ○ program using the Resource is completed ○ acquisition of nested resources fails ○ acquisition of composed resources fails ○ using the resource produces an error ○ computation using the resource is cancelled (by another thread!)