The document discusses functional programming concepts in Scala including pure functions, referential transparency, algebraic data types, immutable linked lists, list folding, option types, higher order functions, and random number generation. It provides code examples for implementing immutable linked lists with pattern matching, list folding with foldLeft and foldRight, mapping, filtering and flatMapping lists, and defining monads and functors in Scala.
2. class Cafe {
def buyCoffee(cc: CreditCard): Coffee = {
val cup = Coffee(100)
cc.charge(100)
cup
}
}
3. case class Payments() {
def charge(cc: CreditCard, price: Int) {
println("doing something")
}
}
case class BetterCafe() {
def buyCoffee(cc: CreditCard, p: Payments): Coffee = {
val cup = Coffee(100)
p.charge(cc, cup.price)
cup
}
}
4. case class Charge(cc: CreditCard, amount: Int) {
def combine(other: Charge): Charge = {
if (cc == other.cc) {
Charge(this.cc, this.amount + other.amount)
} else {
throw new Exception("Error")
}
}
}
5. class FunctionalCafe {
def buyCoffee(cc: CreditCard): (Coffee, Charge) = {
val cup = new Coffee(100)
(cup, Charge(cc, cup.price))
}
def buyCoffees(cc: CreditCard, number: Int):
(List[Coffee], Charge) = {
val purchases: List[(Coffee, Charge)] =
List.fill(number)(buyCoffee(cc))
val (coffees, charges) = purchases.unzip
(coffees, charges.reduce((a, b) => a.combine(b))
}
}
6. Pure Functions:
Reassigning a variable
Modifying a data structure in place
Setting a field on an object
Throwing an exception or halting with an error*
Printing to the console or reading user input
Reading from or writing to a file
Drawing on the screen
7. Referential transparency
scala> val x = "Hello, World"
x: java.lang.String = Hello, World
scala> val r1 = x.reverse
r1: String = dlroW ,olleH
scala> val r2 = x.reverse
r2: String = dlroW ,olleH
scala> val r1 = "Hello, World".reverse
r1: String = dlroW ,olleH
val r2 = "Hello, World".reverse
r2: String = dlroW ,olleH
8. scala> val x = new StringBuilder("Hello")
x: java.lang.StringBuilder = Hello
scala> val y = x.append(", World")
y: java.lang.StringBuilder = Hello, World
scala> val r1 = y.toString
r1: java.lang.String = Hello, World
scala> val r2 = y.toString
r2: java.lang.String = Hello, World
9. scala> val x = new StringBuilder("Hello")
x: java.lang.StringBuilder = Hello
scala> val r1 = x.append(", World").toString()
r1: java.lang.String = Hello, World
scala> val r2 = x.append(", World").toString()
r2: java.lang.String = Hello, World, World
10. def factorial(n: Int): Int = {
if (n <= 0) 1
else n * factorial(n - 1)
}
def factorial(n: Int): Int = {
def go(n: Int, acc: Int): Int =
if (n <= 0) acc
else go(n - 1, n * acc)
go(n, 1)
}
11. def incBy(x: Int): Int = {
@annotation.tailrec
def go(x: Int, acc: Int): Int =
if (x <= 0) acc
else go(x - 1, acc + 1)
go(x, 1)
}
12. High Order Functions:
def highOrder(a: Int, f: Int => Int): Int = {
f(a)
}
highOrder(10, ((x: Int) => x + 1))
highOrder(10, (x => x + 1)
def isSorted[A](as: Array[A], gt: (A, A) => Boolean):
Boolean = {
@annotation.tailrec
def go(i: Int, prev: A): Boolean =
if (i == as.length) true
else if (gt(as(i), prev)) go(i + 1, as(i))
else false
if (as.length == 0) true
else go(1, as(0))
}
13. def partial1[A, B, C](f: (A, B) => C, a: A): B => C = {
(b: B) => f(a, b)
}
def curry[A,B,C](f: (A, B) => C): A => (B => C) = {
(a: A) => (b: B) => f(a, b)
}
def uncurry[A, B, C](f: A => B => C): (A, B) => C = {
(a: A, b: B) => f(a)(b)
}
def compose[A, B, C](f: A => B, g: B => C): A => C = {
(a: A) => g(f(a))
}
14. Algebraic data type:
trait Weekday
case class Monday extends Weekday
case class Tuesday extends Weekday
case class Wednesday extends Weekday
case class Thursday extends Weekday
case class Friday extends Weekday
case class Saturday extends Weekday
case class Sunday extends Weekday
trait Boolean
case class True
case class False
18. sealed trait List[+A]
case object Nil extends List[Nothing]
case class Cons[+A](head: A, tail: List[A])
extends List[A]
object List {
def apply[A](as: A*): List[A] =
if (as.isEmpty) Nil
else Cons(as.head, apply(as.tail: _*))
}
19. def sum(xs: List[Int]): Int = xs match {
case Nil
=> 0
case Cons(x, xs) => x + sum(xs)
}
def product(xs: List[Int]): Int = xs match {
case Nil
=> 1
case Cons(0, xs) => 0
case Cons(x, xs) => x * product(xs)
}
20. def tail[A](xs: List[A]): List[A] = xs match {
case Nil
=> throw new Exception("aaaa")
case Cons(x, xs) => xs
}
def setHead[A](xs: List[A], a: A): List[A] = xs match {
case Nil
=> Cons(a, Nil)
case Cons(x, xs) => Cons(a, xs)
}
def drop[A](xs: List[A], n: Int): List[A] =
(xs, n) match {
case (Nil, _)
=> xs
case (Cons(y, ys), 0) => xs
case (Cons(y, ys), n) => drop(ys, n - 1)
}
21. def dropWhile[A](xs: List[A], p: A => Boolean): List[A]
= xs match {
case Nil
=> Nil
case Cons(y, ys) if p(y) => dropWhile(ys, p)
case Cons(y, ys)
=> xs
}
def init[A](xs: List[A]): List[A] = xs match {
case Nil
=> Nil
case Cons(x, Nil) => Nil
case Cons(x, xs) => Cons(x, init(xs))
}
22. List Folding:
def foldRight[A, B](xs: List[A], acc: B)(f: (A, B) => B): B =
xs match {
case Nil
=> acc
case Cons(x, xs) => f(x, foldRight(xs, acc)(f))
}
foldRight(Cons(1, Cons(2, Cons(3, Nil))), 0)((x,y) => x + y)
1 + foldRight(Cons(2, Cons(3, Nil)), 0)((x,y) => x + y)
1 + (2 + foldRight(Cons(3, Nil), 0)((x,y) => x + y))
1 + (2 + (3 + (foldRight(Nil, 0)((x,y) => x + y))))
1 + (2 + (3 + (0)))
6
23. def foldLeft[A, B](xs: List[A], acc: B)(f: (B, A) => B): B =
xs match {
case Nil
=> acc
case Cons(x, xs) => foldLeft(xs, f(acc, x))(f)
}
foldLeft(Cons(1, Cons(2, Cons(3, Nil))), 0)((x,y) => x + y)
foldLeft(Cons(2, Cons(3, Nil)), 0 + 1)((x,y) => x + y)
foldLeft(Cons(3, Nil)), 1 + 2)((x,y) => x + y)
foldLeft(Nil, 3 + 3)((x,y) => x + y)
6
26. def unit[A](a: A): List[A] = Cons(a, Nil)
def map[A, B](xs: List[A])(f: A => B): List[B] = xs match {
case Nil
=> Nil
case Cons(x, xs) => Cons(f(x), map(xs)(f))
}
def flatMap[A, B](xs: List[A])(f: A => List[B]): List[B] =
xs match {
case Nil
=> Nil
case Cons(x, xs) => append(f(x), (flatMap(xs)(f)))
}
def filter[A](xs: List[A])(p: A => Boolean): List[A] = xs
match {
case Nil
=> Nil
case Cons(x, xs) if (p(x)) => Cons(x, filter(xs)(p))
case Cons(x, xs)
=> filter(xs)(p)
}
27. def map [A](xs: List[A])(f: A => B): List[B] =
flatMap(xs)((a: A) => unit(f(a)))
def filter[A](xs: List[A])(p: A => Boolean): List[A] =
flatMap(xs)(x => if (p(x)) List(x) else Nil)
28. Simple Binary Tree:
sealed trait Tree[+A]
case class Leaf[A](value: A) extends Tree[A]
case class Branch[A](left: Tree[A],
right: Tree[A]) extends Tree[A]
29. def size[A](tree: Tree[A]): Int = tree match {
case Leaf(_)
=> 1
case Branch(l, r) => 1 + size(l) + size(r)
}
def maximum(tree: Tree[Int]): Int = tree match {
case Leaf(x)
=> x
case Branch(l, r) => maximum(l) max maximum(r)
}
def depth[A](tree: Tree[A]): Int = tree match {
case Leaf(_)
=> 1
case Branch(l, r) => 1 + (depth(l) max depth(r))
}
30. def map[A, B](tree: Tree[A])(f: A => B): Tree[B] =
tree match {
case Leaf(x)
=> Leaf(f(x))
case Branch(l, r) => Branch(map(l)(f), map(r)(f))
}
def fold[A, B](tree: Tree[A])(f: A => B)(g: (B, B) => B): B
= tree match {
case Leaf(x)
=> f(x)
case Branch(l, r) => g(fold(l)(f)(g), fold(r)(f)(g))
}
def sizeViaFold[A](tree: Tree[A]): Int =
fold(tree)((a: A) => 1)((b1: Int, b2: Int) => 1 + b1 + b2)
def mapViaFold[A, B](tree: Tree[A])(f: A => B): Tree[B] =
fold(tree)(a => Leaf(f(a)): Tree[B])
((l, r) => Branch(l, r))
31. How to deal with non total functions:
String countryName(String userId) {
User user;
Phone phone;
String cc;
Country country;
return userid != null &&
(user = db.findUser(userid)) != null &&
(phone = user.getPhone) != null &&
(cc = phone.getCountryCode) != null &&
(country = Countries.findByCode(cc)) !=
null ? country.getName() : null
}
33. Option(Maybe):
case class Some[+A](get: A) extends Option[A]
case object None extends Option[Nothing]
def getOrElse[B >: A](default: => B): B = this match {
case None
=> default
case Some(x) => x
}
def unit[A](a: A): Option[A] = Some(a)
val a = unit(10)
val value = a match {
case None
=> “exception”
case Some(x) => x
}
val c = a + 10
34. def map[B](f: A => B): Option[B] = this match {
case None
=> None
case Some(x) => Some(f(x))
}
def flatMap[B](f: A => Option[B]): Option[B] =
this match {
case None
=> None
case Some(x) => f(x)
}
def filter(p: A => Boolean): Option[A] = this match {
case Some(x) if (p(x)) => this
case _
=> None
}
35. def f(v: Some[Int]): Some[Int] =
v.map(x => x + 1).map(x => x + 2).filter(x => x != 3)
val dept: String = employeesByName.get("Joe").
map(_.dept).
filter(_ != "Accounting").
getOrElse("Default Dept")
val a = Some(10)
val b = Some(20)
a + b ?
a.map(…
a.flatMap(x => b.map(y => x + y))
36. for {
x <- Some(10)
y <- Some(20)
} yield(x + y)
for (
user <- db.findUser(userid)
phone <- user.getPhone
cc <- phone.getCountryCode
country <- Countries.findByCode(cc)
) yield("User " + user + " is from " + country)
37. Simple pseudo-random generator:
trait RNG {
def nextInt: (Int, RNG)
}
object RNG {
case class Simple(seed: Long) extends RNG {
def nextInt: (Int, RNG) = {
val newSeed = (seed * 0x5DEECE66DL + 0xBL) &
0xFFFFFFFFFFFFL
val nextRNG = Simple(newSeed)
val n = (newSeed >>> 16).toInt
(n, nextRNG)
}
}
type Rand[+A] = RNG => (A, RNG)
val int: Rand[Int] = (x => x.nextInt)
38. def positiveInt(rng: RNG): (Int, RNG) = {
val (i, r) = rng.nextInt
if (i < 0) (-(i + 1), r)
else (i, r)
}
def double(rng: RNG): (Double, RNG) = {
val (i, r) = positiveInt(rng)
if (i == Int.MaxValue) double(r)
else (i.toDouble / Int.MaxValue, r)
}
40. def booleanMap: Rand[Boolean] =
map(positiveInt)(x => if (x % 2 == 0) true else false)
def doubleMap: Rand[Double] =
map(positiveInt)(x => x / Int.MaxValue.toDouble + 1)
val g = Rand[(Int, Boolean, Double)] =
for {
x <- int
y <- booleanMap
z <- doubleMap
} yield (x, y, z)
41. trait Functor[F[_]] {
def map[A, B](fa: F[A])(f: A => B): F[B]
def distribute[A, B](fab: F[(A, B)]): (F[A], F[B]) =
(map(fab)(x => x._1), map(fab)(x => x._2))
}
val listFunctor = new Functor[List] {
def map[A, B](fa: List[A])(f: A => B): List[B]
= fa.map(f)
}
Functor law:
map(x)(id) == x
42. A monad is an implementation of one of the minimal sets of
monadic combinators, satisfying the laws of associativity and
identity.
unit and flatMap
unit and compose
unit, map, join
trait Monad[F[_]] extends Functor[F] {
def unit[A](a: => A): F[A]
def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B]
def map[A, B](fa: F[A])(f: A => B): F[B] =
flatMap(fa)(a => unit(f(a)))
def map2[A, B, C](fa: F[A], fb: F[B])
(f: (A, B) => C): F[C] =
flatMap(fa)(a => map(fb)(b => f(a, b)))
def product[A, B](ma: F[A], mb: F[B]): F[(A, B)] =
map2(ma, mb)((a, b) => (a, b))
}