Functional Refactoring
James Earl Douglas
May 28, 2016
Motivation
- Functional programming is rad
- Not all code is functional
How can I learn to convert imperative code to functional
code?
Functional refactoring
- Making code easier to understand
- Making code cheaper to modify
- Maintaining code's observable behavior*
...functionally!
Refactorings in this talk
- Dependency injection → reader monad
- Exception → sum type
- Mutable variable → state monad
- Loop → fold
- Mutator method → deep copy
Dependency injection → reader monad
Dependency injection is:
Clean
Self-documenting
Justification for a framework
More than simply argument passing
Dependency injection → reader monad
Example: Cylindrical volume
def area(pi: Double, r: Int): Double = pi * r * r
Dependency injection → reader monad
Example: Cylindrical volume
def volume(pi: Double, r: Int, h: Int): Double = {
val a = area(pi, r)
val v = a * h
v
}
Dependency injection → reader monad
Step 0: Our reader monad
case class Reader[E,A](run: E => A) {
def flatMap[B](f: A => Reader[E,B]): Reader[E,B] =
Reader[E,B] { e => f(run(e)).run(e) }
def map[B](f: A => B): Reader[E,B] =
Reader[E,B] { e => f(run(e)) }
}
Dependency injection → reader monad
// def area(pi: Double, r: Int): Double = pi * r * r
def areaR(r: Int): Reader[Double,Double] =
Reader { pi => pi * r * r }
Dependency injection → reader monad
// def volume(pi: Double, r: Int, h: Int): Double = ...
def volumeR(r: Int, h: Int): Reader[Double,Double] =
areaR(r) map { a => a * h }
Dependency injection → reader monad
Usage: Pi injection
val vR: Double = volumeR(6, 7) run 3.14
Dependency injection → reader monad
// def areaR(r: Int): Reader[Double,Double] =
val areaRR: Reader[Int,Reader[Double,Double]] =
Reader { r =>
Reader { pi => pi * r * r }
}
Dependency injection → reader monad
// def volumeR(r: Int, h: Int): Reader[Double,Double] = ...
def volumeRR(h: Int): Reader[Int,Reader[Double,Double]] =
areaRR map { areaR =>
areaR map { a => a * h }
}
Dependency injection → reader monad
Usage: Radius and Pi
injection
val vRR: Double = volumeRR(7) run 6 run 3.14
Dependency injection → reader monad
Questions
Exception → sum type
Exceptions are:
Clean
Composable
Self-documenting
Exception → sum type
Example: Integer division
def div(x: Int, y: Int): Int = x / y
Exception → sum type
Example: Division by zero
val quotient: Int =
try {
div(42, 0)
} catch {
case e: ArithmeticException => -999
}
Exception → sum type
Step 0: Type disjunction
sealed trait Either[A,B]
case class Left[A,B](x: A) extends Either[A,B]
case class Right[A,B](x: B) extends Either[A,B]
Exception → sum type
Step 1: Return
either a quotient or an exception
// def div(x: Int, y: Int): Int = x / y
def divE(x: Int, y: Int): Either[Exception, Int] =
try {
Right(x / y)
} catch {
case e: Exception => Left(e)
}
Exception → sum type
Usage: Division by zero
// val quotient: Int = ...
val quotientE: Either[Exception,Int] = divE(42, 0)
Exception → sum type
Step 2: Make it composable
implicit class RightBiasedEither[A,B](e: Either[A,B]) {
def flatMap[C](f: B => Either[A,C]): Either[A,C] =
e match {
case Left(a) => Left(a)
case Right(b) => f(b)
}
def map[C](f: B => C): Either[A,C] =
flatMap { b => Right(f(b)) }
}
Exception → sum type
Usage: Chained divisions
val quotientE2: Either[Exception,Int] =
for {
a <- divE(42, 7)
b <- divE(a, 0)
c <- divE(b, 2)
} yield c
Exception → sum type
Questions
Mutable variable → state monad
Mutable variables are:
Thread safe
Parallelizable
Time invariance
Referentially transparent
Mutable variable → state monad
Example: Six equals forty-two
var x = 6
println(s"x = ${x}")
// x = 6
x = x * 7
println(s"x = ${x}")
// x = 42
Mutable variable → state monad
Step 0: Our state monad
case class State[A,S](run: S => (A,S)) {
def flatMap[B](f: A => State[B,S]): State[B,S] =
State { s =>
val (a,s2) = run(s)
f(a).run(s2)
}
}
Mutable variable → state monad
Step 1: Some handy extras
implicit class StateExtras[A,S](s: State[A,S]) {
def andThen[B](x: State[B,S]): State[B,S] =
s.flatMap { _ => x }
def map[B](f: A => B): State[B,S] =
s.flatMap { a =>
State { s => (f(a),s) }
}
}
Mutable variable → state monad
Step 2: Replace var
with State
// var x = 6
val six: State[Unit,Int] =
State { _ => ((),6) }
Mutable variable → state monad
Step 3: Replace reference
with State
// println(s"x = ${x}")
val print: State[Unit,Int] =
State { x => (println(s"x = ${x}"),x) }
Mutable variable → state monad
Step 4: Replace mutation
with State
// x = x * 7
val times7: State[Unit,Int] =
State { x => ((),x*7) }
Mutable variable → state monad
Step 5: Combine as needed
// var x = 6
// println(s"x = ${x}") // x = 6
// x = x * 7
// println(s"x = ${x}") // x = 42
val sixBy7: State[Unit,Int] =
six andThen print andThen times7 andThen print
Mutable variable → state monad
Usage: Throwaway initial
state
sixBy7 run -999
// x = 6
// x = 42
Mutable variable → state monad
Questions
Loop → fold
Loops are:
Free of mutability
Declarative
Concise
Loop → fold
Example: Sum of a list of
numbers
var i = 0
var sum = 0
while (i < xs.length) {
val x = xs(i)
sum = sum + x
i = i + 1
}
Loop → fold
Step 0: Left fold
scala> :t xs.foldLeft[B] _
B => (((B, Int) => B) => B)
We'll need:
- An initial value
B
- A reducing function
(B, Int) => B
Loop → fold
Step 1: Finalize the initial
value
// var sum = 0
val sumInit = 0
Loop → fold
Step 2: Extract the loop body
// while (i < xs.length) {
// val x = xs(i)
// sum = sum + x
// i = i + 1
// }
val sumOp: (Int,Int) => Int = { (sum, x) => sum + x }
Loop → fold
Usage: Fold over the list
val foldSum: Int = xs.foldLeft(sumInit)(sumOp)
Loop → fold
Questions
Mutator method → deep copy
Mutable variables are:
Thread safe
Parallelizable
Time invariance
Referentially transparent
Mutator method → deep copy
Example: A mutable employee
class MutableEmployee(var name: String, var title: String) {
def setName(name: String): Unit = {
this.name = name
}
def setTitle(title: String): Unit = {
this.title = title
}
}
Mutator method → deep copy
Example: Changing state over
time
val employee0 = new MutableEmployee("George Michael", "Employee")
println(s"employee0: ${employee0.name}, ${employee0.title}")
// employee0: George Michael, Employee
employee0.setTitle("Mr. Manager")
println(s"employee0: ${employee0.name}, ${employee0.title}")
// employee0: George Michael, Mr. Manager
Mutator method → deep copy
Step 1: Replace var
with val
// class MutableEmployee(var name: String, var title: String) {
class ImmutableEmployee(val name: String, val title: String) {
Mutator method → deep copy
Step 2: Return a deep copy
// def setName(name: String): Unit = ...
def setName(name: String): ImmutableEmployee =
new ImmutableEmployee(name, title)
// def setTitle(title: String): Unit = ...
def setTitle(title: String): ImmutableEmployee =
new ImmutableEmployee(name, title)
}
This is a functional take on the builder
pattern.
Mutator method → deep copy
val employee1 = new ImmutableEmployee("George Michael", "Employee")
val employee2 = employee1.setTitle("Mr. Manager")
println(s"employee1: ${employee1.name}, ${employee1.title}")
// employee1: George Michael, Employee
println(s"employee2: ${employee2.name}, ${employee2.title}")
// employee2: George Michael, Mr. Manager
Mutator method → deep copy
Questions