4. statically typed pure functions as the primary
abstraction of design
def foo[A, B](arg: A): B
5. Motivation
• Explore if we can consider functions as the primary abstraction
of design
• Function composition to build larger behaviors out of smaller
ones
• How referential transparency helps composition and local
reasoning
• Types as the substrate (algebra) of composition
6. Types
Functions (Mapping between types)
(Algebraic)
• sum type
• product type
• ADTs
• compose
• based on algebra of types
Modules (Grouping related functions) • compose
• can be parameterized
Application (Top level artifact)
14. Referential Transparency
• An expression is said to be referentially transparent if it
can be replaced with its value without changing the behavior
of a program
• The value of an expression depends only on the values of its
constituent expressions (if any) and these subexpressions
may be replaced freely by others possessing the same
value
15. Referential Transparency
expression oriented
programming
substitution model
of evaluation
• An expression is said to be referentially transparent if it
can be replaced with its value without changing the behavior
of a program
• The value of an expression depends only on the values of its
constituent expressions (if any) and these subexpressions
may be replaced freely by others possessing the same
value
17. scala> def foo(iter: Iterator[Int]) = iter.next + 2
foo: (iter: Iterator[Int])Int
scala> (1 to 20).iterator
res0: Iterator[Int] = non-empty iterator
scala> val a = foo(res0)
a: Int = 3
scala> (a, a)
res1: (Int, Int) = (3,3)
scala> (1 to 20).iterator
res3: Iterator[Int] = non-empty iterator
scala> (foo(res3), foo(res3))
res4: (Int, Int) = (3,4)
18. def foo(i: Iterator[Int]): Int = {
val a = i.next
val b = a + 1
a + b
}
scala> val i = (1 to 20).iterator
i: Iterator[Int] = non-empty iterator
scala> foo(i)
res8: Int = 3
scala> val i = (1 to 20).iterator
i: Iterator[Int] = non-empty iterator
scala> i.next + i.next + 1
res9: Int = 4
• non compositional
• hinders local understanding
• breaks RT
22. What is meant by the
algebra of a type ?
•Nothing
•Unit
•Boolean
•Byte
•String
23. What is meant by the
algebra of a type ?
•Nothing -> 0
•Unit -> 1
•Boolean -> 2
•Byte -> 256
•String -> a lot
24. What is meant by the
algebra of a type ?
•(Boolean, Unit)
•(Byte, Unit)
•(Byte, Boolean)
•(Byte, Byte)
•(String, String)
25. What is meant by the
algebra of a type ?
•(Boolean, Unit) -> 2x1 = 2
•(Byte, Unit) -> 256x1 = 256
•(Byte, Boolean) -> 256x2 = 512
•(Byte, Byte) -> 256x256 = 65536
•(String, String) -> a lot
26. What is meant by the
algebra of a type ?
• Quiz: Generically, how many inhabitants can we
have for a type (a, b)?
• Answer: 1 inhabitant for each combination of
a’s and b’s (a x b)
27. Product Types
• Ordered pairs of values one from each type in
the order specified - this and that
• Can be generalized to a finite product indexed by
a finite set of indices
28. Product Types in Scala
type Point = (Int, Int)
val p = (10, 12)
case class Account(no: String,
name: String,
address: String,
dateOfOpening: Date,
dateOfClosing: Option[Date]
)
29. What is meant by the
algebra of a type ?
•Boolean or Unit
•Byte or Unit
•Byte or Boolean
•Byte or Byte
•String or String
30. What is meant by the
algebra of a type ?
•Boolean or Unit -> 2+1 = 3
•Byte or Unit -> 256+1 = 257
•Byte or Boolean -> 256+2 = 258
•Byte or Byte -> 256+256 = 512
•String or String -> a lot
31. Sum Types
• Model data structures involving alternatives -
this or that
• A tree can have a leaf or an internal node which,
is again a tree
• In Scala, a sum type is usually referred to as an
Algebraic DataType (ADT)
32. Sum Types in Scala
sealed trait Shape
case class Circle(origin: Point,
radius: BigDecimal) extends Shape
case class Rectangle(diag_1: Point,
diag_2: Point) extends Shape
33. Sum Types are
Expressive
• Booleans - true or false
• Enumerations - sum types may be used to define finite
enumeration types, whose values are one of an explicitly
specified finite set
• Optionality - the Option data type in Scala is encoded using a
sum type
• Disjunction - this or that, the Either data type in Scala
• Failure encoding - the Try data type in Scala to indicate that
the computation may raise an exception
34. sealed trait InstrumentType
case object CCY extends InstrumentType
case object EQ extends InstrumentType
case object FI extends InstrumentType
sealed trait Instrument {
def instrumentType: InstrumentType
}
case class Equity(isin: String, name: String, issueDate: Date,
faceValue: Amount) extends Instrument {
final val instrumentType = EQ
}
case class FixedIncome(isin: String, name: String, issueDate: Date,
maturityDate: Option[Date], nominal: Amount) extends Instrument {
final val instrumentType = FI
}
case class Currency(isin: String) extends Instrument {
final val instrumentType = CCY
}
35. De-structuring with
Pattern Matching
def process(i: Instrument) = i match {
case Equity(isin, _, _, faceValue) => // ..
case FixedIncome(isin, _, issueDate, _, nominal) => // ..
case Currency(isin) => // ..
}
40. scala> case class Employee(id: String, name: String, age: Int)
defined class Employee
scala> def getEmployee: String => Employee = id => Employee("a", "abc")
getEmployee: String => Employee
scala> def getSalary: Employee => BigDecimal = e => BigDecimal(100)
getSalary: Employee => BigDecimal
scala> def computeTax: BigDecimal => BigDecimal = s => 0.3 * s
computeTax: BigDecimal => BigDecimal
scala> getEmployee andThen getSalary andThen computeTax
res3: String => BigDecimal = scala.Function1$$Lambda$1306/913564177@4ac77269
scala> res3("emp-100")
res4: BigDecimal = 30.0
41. Function Composition
• Functions compose when types align
• And we get larger functions
• Still values - composition IS NOT execution (composed
function is still a value)
• Pure and referentially transparent
42. Function Composition
f:A => B
g:B => C
(f
andThen
g):A
=>
C
id[A]:A => A id[B]:B => B
(Slide adapted from a presentation by Rob Norris at Scale by the Bay 2017)
53. Types
Functions (Mapping between types)
(Algebraic)
• sum type
• product type
• ADTs
• compose
• based on algebra of types
Modules (Grouping related functions) • compose
• can be parameterized
Application (Top level artifact)
54. trait Combiner[A] {
def zero: A
def combine(l: A, r: => A): A
}
//identity
combine(x, zero) =
combine(zero, x) = x
// associativity
combine(x, combine(y, z)) =
combine(combine(x, y), z)
55. Module with an algebra
trait Monoid[A] {
def zero: A
def combine(l: A, r: => A): A
}
//identity
combine(x, zero) =
combine(zero, x) = x
// associativity
combine(x, combine(y, z)) =
combine(combine(x, y), z)
56. Module with an Algebra
trait Foldable[F[_]] {
def foldl[A, B](as: F[A], z: B, f: (B, A) => B): B
def foldMap[A, B](as: F[A], f: A => B)
(implicit m: Monoid[B]): B =
foldl(as,
m.zero,
(b: B, a: A) => m.combine(b, f(a))
)
}
57. def mapReduce[F[_], A, B](as: F[A], f: A => B)
(implicit ff: Foldable[F], m: Monoid[B]) =
ff.foldMap(as, f)
https://stackoverflow.com/a/4765918
58. case class Sum(value: Int)
case class Product(value: Int)
implicit val sumMonoid = new Monoid[Sum] {
def zero = Sum(0)
def add(a: Sum, b: Sum) = Sum(a.value + b.value)
}
implicit val productMonoid = new Monoid[Product] {
def zero = Product(1)
def add(a: Product, b: Product) =
Product(a.value * b.value)
}
59. val sumOf123 = mapReduce(List(1,2,3), Sum)
val productOf456 = mapReduce(List(4,5,6), Product)
60. def mapReduce[F[_], A, B](as: F[A], f: A => B)
(implicit ff: Foldable[F], m: Monoid[B]) =
ff.foldMap(as, f)
61. def mapReduce[F[_], A, B](as: F[A], f: A => B)
(implicit ff: Foldable[F], m: Monoid[B]) =
ff.foldMap(as, f)
combinator
(higher order function)
62. def mapReduce[F[_], A, B](as: F[A], f: A => B)
(implicit ff: Foldable[F], m: Monoid[B]) =
ff.foldMap(as, f)
Type constructor
63. def mapReduce[F[_], A, B](as: F[A], f: A => B)
(implicit ff: Foldable[F], m: Monoid[B]) =
ff.foldMap(as, f)
F needs to honor the
algebra of a Foldable
64. def mapReduce[F[_], A, B](as: F[A], f: A => B)
(implicit ff: Foldable[F], m: Monoid[B]) =
ff.foldMap(as, f)
B needs to honor the
algebra of a Monoid
65. def mapReduce[F[_], A, B](as: F[A], f: A => B)
(implicit ff: Foldable[F], m: Monoid[B]) =
ff.foldMap(as, f)
combinator
(higher order function)
Type constructor
F needs to honor the
algebra of a Foldable
B needs to honor the
algebra of a Monoid
66. Types
Functions (Mapping between types)
(Algebraic)
• sum type
• product type
• ADTs
• compose
• based on algebra of types
Modules (Grouping related functions) • compose
• can be parameterized
Application (Top level artifact)
74. Function Composition
f:A => B
g:B => C
(f
andThen
g):A
=>
C
id[A]:A => A id[B]:B => B
(Slide adapted from a presentation by Rob Norris at Scale by the Bay 2017)
80. Effectful Function
Composition
f:A => F[B]
g:B => F[C]
(f
andThen
g):A
=>
F[C]
pure[A]:A => F[A] pure[B]:B => F[B]
(Slide adapted from a presentation by Rob Norris at Scale by the Bay 2017)