Functional programming in Scala. Looking at various examples of defining a program first and executing it at some later stage, separating pure functions from side effects.
17. FUNCTIONS, TYPES, PROGRAMS AND EFFECTS
Strict separation of safe and unsafe
Safe: Functions, Types, creating Programs
Unsafe: Effects, running Programs
18. WRITING FUNCTIONAL SCALA
1. Write functions, which return values
2. Choose effects (or write your own)
3. Construct a program that composes values into effects
4. Run the program (in an 'App' in the main method)
(Not necessarily in that order)
19. WRITING FUNCTIONAL SCALA
▸ You can possibly have programs of programs
▸ A program is very often defined in a for comprehension
20. A hypothetical example.
val program = for {
client <- server.connect(details)
Exchange(src, snk) = client.exchange
_ <- snk.sendRequest(request)
in = src.pipe(text.utf8Decode)
.to(io.stdOutLines)
} yield ()
program.run
Define first, run later.
25. DATA LOSS
Boolean blindness
Functions return values, discarding them constrains the Program 1
1
Think of the information you need later on in a for comprehension
26. What is 'wrong' with this?
sealed trait Error
case object UserNotFound extends Error
case object UserNameNotFound extends Error
case object AccountNotFound extends Error
def getUser(username: Username): Future[Either[Error, User]]
def getAccounts(user: User): Future[Either[Error, Account]]
27. scalaz Disjunction (left or right)
val res: String / Int = /-(5)
val res1: String / Int = 5.right
val moreVerbose: String / Int = 5.right[String]
val res2: String / Int = "Some error".left
val friendlier = res2.leftMap(error => s"$error has occured, we apologize.")
val res3 = res1.map(five => five * 2) // /-(10)
Map over the right
28. From Throwable to /
/.fromTryCatchThrowable[String, Exception] {
getDangerousString()
} // returns a Exception / String
/.fromTryCatchThrowable[String, Exception] {
getDangerousString()
}.leftMap(e=> MyError(e)) // returns a MyError / String
29. From A / B to X
val res: String / Int = "Error!".left
val res1 = res.fold(left=> 0, r => r)
val res2 = res.fold(left=> 0, identity) // this is the same
Why should you not use this?
31. def evenAndByThree(in: Int) =
for {
evenNr <- even(in)
byThree <- divByThree(evenNr)
} yield byThree
No loss of information (why the first error occurred).
32. Given
def getUser(username: Username): Future[Error / User]]
def getAccounts(user: User): Future[Error / List[Account]]
Does this compile?
val result = for {
user <- getUser(name)
accounts <- getAccounts(user)
} yield accounts
34. Combining values
Monoid - collecting / combining values into one value
//simplified / pseudo
val append: (F,F) => F
val zero: F
//actual
def append(f1: F, f2: => F): F
def zero: F
35. COMBINING EFFECTS
Monad - collecting / combining effects 2
def point[A](a: => A): F[A] // Creates an 'effects collector'
def bind[A, B](fa: F[A])(f: A => F[B]): F[B] // adds an effect
def map[A,B](fa: F[A])(f: A => B):F[B] // transforms input for next effect
Can only combine for same type of F.2
2
The Monad is a monoid in the category of endofunctors joke
36. In scalaz, flatMap is defined as:
def flatMap[B](f: A => F[B]) = F.bind(self)(f)
flatMap ~= bind
37. Monads are great, but in general NOT composable.
Monad Transformers
Effect systems (i.e. Eff from eff-cats)
In common, a Type that combines N Monad types
38. Monad Transformer EitherT
val res = for {
user <- EitherT(getUser("bla"))
accounts <- EitherT(getAccounts(user))
} yield accounts // returns EitherT[Future, Error, List[Account]]
res.run // Future[Error / List[Account]]
Scalaz provides Future Monad instance in scalaz.std.scalaFuture
39. Construct the Program
val res = for {
user <- EitherT(getUser("bla"))
accounts <- EitherT(getAccounts(user))
} yield accounts
Run the Program
res.run
58. case class Config(inputFile: Option[File], outputFile: Option[File])
def main(args: Array[String]): Unit = {
val config = parseArgs(args)
// ...
}
59. val inputFile = optional(opt[File](
ensure[File](readStr.map(str new File(str)), "INPUT_FILE must be an existing file", _.isFile),
long("input-file"),
metavar("INPUT_FILE"),
short('f'),
help("input file to read from")
))
val outputFile = optional(opt[File](
readStr.map(str new File(str)),
long("output-file"),
metavar("OUTPUT_FILE"),
short('o'),
help("output file to write to")
))
61. Define the program
val parser = (input |@| inputFile)(Config.apply(_, _))
Execute the program
execParser(args, "copy", info(parser <*> helper,
header("The awesome copy utility.")
))
67. But, separate is very useful.
def separate[G[_, _], A, B](value: F[G[A, B]])(implicit G: Bifoldable[G]): (F[A], F[B])
Remove everything, keep args and return value
(value: F[G[A, B]]): (F[A], F[B])
69. Lets substitute F, G, A and B to something we know
F[G[A, B]] => (F[A], F[B])
F = List
G[A,B] = Error / Result
4
4
/ uses infix type notation, same as /[Error,Result]
70. List[Error / Result] => (List[Error], List[Result])
It's a function to separate the errors from the results!
71. There are many of these.
How do you find a concrete function if they are defined in the abstract?
72. DIG
Remove all syntax until you are left with a function
Then find which implicits / type classes are needed for the function.
Scalaz requires you to know how the typeclasses are organized
73. cats project has more docs
An introduction to cats
http://typelevel.org/cats/
Advanced Scala with Cats book 5
http://underscore.io/blog
5
http://underscore.io/books/advanced-scala/
74. Functional Programming in Scala (the red book) 6
6
https://www.manning.com/books/functional-programming-in-scala
77. WRITING FUNCTIONAL SCALA
▸ Write functions
▸ Choose effects (or write your own)
▸ Construct a program that composes the functions and effects
▸ Run the program (in an 'App' in the main method)
78. RETURN VALUES
Use data types like A / B that do not lose information about what
happened
Boolean blindness
'Option flatMap' blindness?
79. PROGRAMS
Choose a reasonable architecture to construct your Programs
Monad Wrappers
Monad Transformers
Effect Systems