Elements of Functional Programming
Popular Functional Languages
- Scala
- F#
- Haskell
- Clojure
- Scheme
- Ocaml
Variables
val x = 10
x = 20 // error!
var y = 30
y = 40 // OK
Case classes
case class Point(x: Int, y: Int)
val p1 = Point(10, 20)
val p2 = Point(0, p1.y)
Functions
object Utils {
def times(x: Int, n: Int): Int =
x * n
def twice(x: Int) = times(2, x)
}
Lambda Expression
def twice(f: Int => Int, x: Int): Int =
f(f(x))
twice((x: Int) => x * 3, 10)
twice(x => x * 3, 10)
twice(_ * 3, 10)
Parametric Polymorphism
def twice[T](f: T => T, x: T): T =
f(f(x))
Currying
def twice[T](f: T => T)(x: T): T =
f(f(x))
val plusFour: Int => Int = twice(_ + 2)
plusFour(10)
twice((_: Int) + 2)(10)
ADT
sealed trait MyBool
case object True extends MyBool
case object False extends MyBool
def not(b: MyBool): MyBool = b match {
case True => False
case False => True
}
ADT
sealed trait E
case class Plus(a: E, b: E) extends E
case class Const(x: Int) extends E
Pattern Matching
def eval(e: E): Int = e match {
case Const(x) => x
case Plus(a, b) => eval(a) + eval(b)
}
val e: E =
Plus(
Const(10),
Plus(Const(2), Const(3)))
println(eval(e)) // prints 15
Pattern Matching
def opt(e: E): E = e match {
case Plus(Const(0), x) => x
case Plus(x, Const(0)) => x
case Plus(x, y) =>
Plus(opt(x), opt(y))
case c@Const(_) => c
}
Pattern Matching
val e: E =
Plus(Const(10),
Plus(Const(2), Const(0)))
println(opt(e))
// Prints: Plus(Const(10),Const(2))
Implicits
trait Logger {
def output(s: String): Unit
}
def log(s: String)(implicit log: Logger) =
log.output(s)
Implicits
implicit val logger = new Logger {
override def output(s: String): Unit =
println(s)
}
def work(): Unit = {
log("Working...")
log("Done!")
}
Type classes
trait Show[T] {
def show(x: T): String
}
Type classes
def show[T](x: T)(implicit s: Show[T]): String =
s.show(x)
def mkShow[T](f: T => String): Show[T] =
new Show[T] {
override def show(x: T): String = f(x)
}
Type classes
implicit val stringShow: Show[String] =
mkShow(x => x)
implicit val intShow: Show[Int] =
mkShow(_.toString)
implicit
def ls[T](implicit s: Show[T]): Show[List[T]] =
mkShow(_.map(s.show).mkString(", "))
Type classes
println(show(10))
println(show("Hello"))
println(show(List(1, 2, 3)))
println(show('a')) // error!
Equality type class
true == true
"hello" == UserId(32)
2 == 2.0
dbConnection == dbConnection
Equality type class
trait Eq[T] {
def equal(a: T, b: T): Boolean
}
Equality operations
trait EqOps[T] {
def ===(other: T): Boolean
}
Equality operations
implicit def toEqOps[T](t: T)(implicit e: Eq[T]): EqOps[T] =
new EqOps[T] {
def ===(other: T) =
e.equal(t, other)
}
Equality instances
implicit val intEq: Eq[Int] = new Eq[Int] {
override def equal(a: Int, b: Int): Boolean =
a == b
}
Equality instances
implicit def
tupleEq[A, B](implicit eqA: Eq[A], eqB: Eq[B]): Eq[(A, B)] =
new Eq[(A, B)] {
override def equal(a: (A, B), b: (A, B)): Boolean =
eqA.equal(a._1, b._1) && eqB.equals(a._2, b._2)
}
Equality instances
import EqualityExample._
println(1 === 1) // true
println(1 === 2) // false
println((1, 2) === (1, 2)) // true
println(4 === "test") // error
Optional values
sealed trait Opt[+T]
case class Some[T](x: T) extends Opt[T]
case object None extends Opt[Nothing]
Optional values
def div(a: Int, b: Int): Opt[Int] =
if (b == 0) None else Some(a / b)
Optional values
def useDiv() = {
val a = div(10, 5)
println(a) // Some(2)
val b = div(10, 0)
println(b) // None
}
Optional values
sealed trait Opt[+T] {
def isDefined: Boolean
def get: T
}
Optional values
case class Some[T](x: T) extends Opt[T] {
override def isDefined: Boolean = true
override def get: T = x
}
Optional values
case object None extends Opt[Nothing] {
override def isDefined: Boolean = false
override def get: Nothing =
throw new RuntimeException("Error")
}
Optional values
def useDiv() = {
val a = div(10, 5)
if (a.isDefined)
println(a.get)
val b = div(10, 0)
if (b.isDefined)
println(b.get)
}
Optional values
sealed trait Opt[+T] {
def map[R](f: T => R): Opt[R] = this match {
case None => None
case Some(x) => Some(f(x))
}
}
Optional values
def useDiv() = {
val a = div(10, 5)
println(a.map(_ + 1)) // prints Some(3)
val b = div(10, 0).map(_ + 1)
println(b) // prints None
}
Optional values
def useDiv() = {
val a = div(10, 5).map(_ + 1)
println(a)
// error in next line: A is Opt[Int]
val b = div(a, 2).map(_ + 2)
println(b)
}
Optional values
sealed trait Opt[+T] {
def flatMap[R](f: T => Opt[R]): Opt[R] =
this match {
case None => None
case Some(x) => f(x)
}
}
Optional values
def useDiv() = {
val a = div(10, 5)
val b = a.flatMap(x => div(x, 2).map(_ + 1))
println(b) // None
}
Optional values
def useDiv2() = {
val res = for {
a <- div(10, 5)
b <- div(a, 2)
} yield b + 1
println(res) // Some(2)
}
Flat map chain
val a = div(10, 5)
.flatMap(x => div(x, 0))
.flatMap(x => div(x, 2))
.flatMap(x => Some(x + 1))
println(a) // None
Flat map chain
val a = div(10, 5)
.flatMap(div(_, 0))
.flatMap(div(_, 2))
.flatMap(x => Some(x + 1))
println(a) // None
Optional
sealed trait Opt[+T] {
def isDefined: Boolean // Bad
def get: T // Bad
def getOrElse[B >: T](y: => B): B =
this match {
case Some(x) => x
case None => y
}
}
Optional
div(10, 0).getOrElse(0) // 0
Optional
div(10, 0) match {
case Some(x) => println(x)
// error: missing case
}
Optional
div(10, 0) match {
case Some(x) => println(x)
case None => println("Nope.")
}
Either
sealed trait Either[A, B]
case class Left[A, B](a: A) extends Either[A, B]
case class Right[A, B](b: B) extends Either[A, B]
Either
def f(): Either[Int, String] =
Left(10)
def g() = {
f() match {
case Left(a) => println("Left " + a)
case Right(b) => println("Right " + b)
}
}