Power of Functions
in a Typed World
- A JVM Perspective
Debasish Ghosh (dghosh@acm.org)
@debasishg
functions as the primary abstraction of design
pure functions as the primary abstraction of
design
statically typed pure functions as the primary
abstraction of design
def foo[A, B](arg: A): B
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
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)
Functional Programming
Programming with
Functions
f
A B
def plus2(arg: Int): Int = arg + 2
def size(n: Int): Int = 1000 / n
def size(n: Int): Option[Int] =
if (n == 0) None else Some(1000 / n)
Pure Value
Functional Programming
• Programming with pure functions
• Output determined completely by the input
• No side-effects
• Referentially transparent
Referential Transparency
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
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
scala> val str = "Hello".reverse
str: String = olleH
scala> (str, str)
res0: (String, String) = (olleH,olleH)
scala> ("Hello".reverse, "Hello".reverse)
res1: (String, String) = (olleH,olleH)
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)
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
Referential Transparency enables
Local Reasoning
and Function Composition
Types
What is meant by the
algebra of a type ?
•Nothing
•Unit
•Boolean
•Byte
•String
What is meant by the
algebra of a type ?
•Nothing -> 0
•Unit -> 1
•Boolean -> 2
•Byte -> 256
•String -> a lot
What is meant by the
algebra of a type ?
•(Boolean, Unit)
•(Byte, Unit)
•(Byte, Boolean)
•(Byte, Byte)
•(String, String)
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
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)
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
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]
)
What is meant by the
algebra of a type ?
•Boolean or Unit
•Byte or Unit
•Byte or Boolean
•Byte or Byte
•String or String
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
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)
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
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
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
}
De-structuring with
Pattern Matching
def process(i: Instrument) = i match {
case Equity(isin, _, _, faceValue) => // ..
case FixedIncome(isin, _, issueDate, _, nominal) => // ..
case Currency(isin) => // ..
}
Exhaustiveness Check
Types
Algebra of Types
def f: A => B = // ..
def g: B => C = // ..
Algebraic Composition of
Types
def f: A => B = // ..
def g: B => C = // ..
def h: A => C = g compose f
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
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
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)
Functions are first class
scala> List(1, 2, 3).map(e => e * 2)
res0: List[Int] = List(2, 4, 6)
scala> (1 to 10).filter(e => e % 2 == 0)
res2: s.c.i.IndexedSeq[Int] = Vector(2, 4, 6, 8, 10)
scala> def even(i: Int) = i % 2 == 0
even: (i: Int)Boolean
scala> (1 to 10).filter(even)
res3: s.c.i.IndexedSeq[Int] = Vector(2, 4, 6, 8, 10)
scala> (1 to 10).foldLeft(0)((a, e) => a + e)
res4: Int = 55
Programming with Values
def size(n: Int): Option[Int] =
if (n == 0) None else Some(1000 / n)
Pure Value
def size(n: Int): Either[String,Int] =
if (n == 0) Left(“Division by zero”)
else Right(1000 / n)
Pure Value
def size(n: Int): Either[String,Int] =
if (n == 0) Left(“Division by zero”)
else Right(1000 / n)
Sum Type
Error Path
Happy Path
Types don’t lie
unless you try and subvert your type system
some languages like Haskell make subversion
difficult
Types Functionsmapping between types
closed under composition
(algebraic)
(referentially transparent)
Does this approach scale ?
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)
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)
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)
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))
)
}
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
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)
}
val sumOf123 = mapReduce(List(1,2,3), Sum)
val productOf456 = mapReduce(List(4,5,6), Product)
def mapReduce[F[_], A, B](as: F[A], f: A => B)
(implicit ff: Foldable[F], m: Monoid[B]) =
ff.foldMap(as, f)
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)
def mapReduce[F[_], A, B](as: F[A], f: A => B)
(implicit ff: Foldable[F], m: Monoid[B]) =
ff.foldMap(as, f)
Type constructor
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
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
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
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)
Types and Functions to drive our
design instead of classes as in OO
Client places order
- flexible format
1
Client places order
- flexible format
Transform to internal domain
model entity and place for execution
1 2
Client places order
- flexible format
Transform to internal domain
model entity and place for execution
Trade & Allocate to
client accounts
1 2
3
def clientOrders:
List[ClientOrder] => List[Order]
def clientOrders:
List[ClientOrder] => Either[Error,
List[Order]]
def clientOrders:
List[ClientOrder] => Either[Error, List[Order]]
def execute(market: Market, brokerAccount: Account):
List[Order] => Either[Error, List[Execution]]
def allocate(accounts: List[Account]):
List[Execution] => Either[Error, List[Trade]]
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)
def clientOrders:
List[ClientOrder] => Either[Error, List[Order]]
def execute(market: Market, brokerAccount: Account):
List[Order] => Either[Error, List[Execution]]
def allocate(accounts: List[Account]):
List[Execution] => Either[Error, List[Trade]]
Plain function composition doesn’t work here
We have those pesky Either[Error, ..]
floating around
Effects
Effectful function composition
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)
Effectful Function
Composition
final case class Kleisli[F[_], A, B](run: A => F[B]) {
def andThen[C](f: B => F[C])
: Kleisli[F, A, C] = // ..
}
type ErrorOr[A] = Either[Error, A]
def clientOrders:
Kleisli[ErrorOr, List[ClientOrder], List[ClientOrder]]
def execute(market: Market, brokerAccount: Account):
Kleisli[ErrorOr, List[Order], List[Execution]]
def allocate(accounts: List[Account]):
Kleisli[ErrorOr, List[Execution], List[Trade]]
def tradeGeneration(
market: Market,
broker: Account,
clientAccounts: List[Account]) = {
clientOrders andThen
execute(market, broker) andThen
allocate(clientAccounts)
}
type ErrorOr[A] = Either[Error, A]
def clientOrders:
Kleisli[ErrorOr, List[ClientOrder], List[ClientOrder]]
def execute(market: Market, brokerAccount: Account):
Kleisli[ErrorOr, List[Order], List[Execution]]
def allocate(accounts: List[Account]):
Kleisli[ErrorOr, List[Execution], List[Trade]]
def tradeGeneration(market: Market, broker: Account,
clientAccounts: List[Account]) = {
clientOrders andThen
execute(market, broker) andThen
allocate(clientAccounts)
}
type ErrorOr[A] = Either[Error, A]
def clientOrders:
Kleisli[ErrorOr, List[ClientOrder], List[ClientOrder]]
def execute(market: Market, brokerAccount: Account):
Kleisli[ErrorOr, List[Order], List[Execution]]
def allocate(accounts: List[Account]):
Kleisli[ErrorOr, List[Execution], List[Trade]]
def tradeGeneration(market: Market, broker: Account,
clientAccounts: List[Account]) = {
clientOrders andThen
execute(market, broker) andThen
allocate(clientAccounts)
}
trait TradingService {
}
type ErrorOr[A] = Either[Error, A]
def clientOrders:
Kleisli[ErrorOr, List[ClientOrder], List[ClientOrder]]
def execute(market: Market, brokerAccount: Account):
Kleisli[ErrorOr, List[Order], List[Execution]]
def allocate(accounts: List[Account]):
Kleisli[ErrorOr, List[Execution], List[Trade]]
def tradeGeneration(market: Market, broker: Account,
clientAccounts: List[Account]) = {
clientOrders andThen
execute(market, broker) andThen
allocate(clientAccounts)
}
trait TradingService {
}
trait TradingApplication extends TradingService
with ReferenceDataService
with ReportingService
with AuditingService {
// ..
}
object TradingApplication extends TradingApplication
Modules Compose
https://github.com/debasishg/fp-fnconf-2018
Thanks!

Power of functions in a typed world

  • 1.
    Power of Functions ina Typed World - A JVM Perspective Debasish Ghosh (dghosh@acm.org) @debasishg
  • 2.
    functions as theprimary abstraction of design
  • 3.
    pure functions asthe primary abstraction of design
  • 4.
    statically typed purefunctions as the primary abstraction of design def foo[A, B](arg: A): B
  • 5.
    Motivation • Explore ifwe 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 betweentypes) (Algebraic) • sum type • product type • ADTs • compose • based on algebra of types Modules (Grouping related functions) • compose • can be parameterized Application (Top level artifact)
  • 7.
  • 8.
  • 9.
    def plus2(arg: Int):Int = arg + 2
  • 10.
    def size(n: Int):Int = 1000 / n
  • 11.
    def size(n: Int):Option[Int] = if (n == 0) None else Some(1000 / n) Pure Value
  • 12.
    Functional Programming • Programmingwith pure functions • Output determined completely by the input • No side-effects • Referentially transparent
  • 13.
  • 14.
    Referential Transparency • Anexpression 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 substitutionmodel 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
  • 16.
    scala> val str= "Hello".reverse str: String = olleH scala> (str, str) res0: (String, String) = (olleH,olleH) scala> ("Hello".reverse, "Hello".reverse) res1: (String, String) = (olleH,olleH)
  • 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
  • 19.
  • 20.
  • 21.
  • 22.
    What is meantby the algebra of a type ? •Nothing •Unit •Boolean •Byte •String
  • 23.
    What is meantby the algebra of a type ? •Nothing -> 0 •Unit -> 1 •Boolean -> 2 •Byte -> 256 •String -> a lot
  • 24.
    What is meantby the algebra of a type ? •(Boolean, Unit) •(Byte, Unit) •(Byte, Boolean) •(Byte, Byte) •(String, String)
  • 25.
    What is meantby 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 meantby 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 • Orderedpairs 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 inScala 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 meantby the algebra of a type ? •Boolean or Unit •Byte or Unit •Byte or Boolean •Byte or Byte •String or String
  • 30.
    What is meantby 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 • Modeldata 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 inScala 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 caseobject 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 defprocess(i: Instrument) = i match { case Equity(isin, _, _, faceValue) => // .. case FixedIncome(isin, _, issueDate, _, nominal) => // .. case Currency(isin) => // .. }
  • 36.
  • 37.
  • 38.
    Algebra of Types deff: A => B = // .. def g: B => C = // ..
  • 39.
    Algebraic Composition of Types deff: A => B = // .. def g: B => C = // .. def h: A => C = g compose f
  • 40.
    scala> case classEmployee(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 • Functionscompose 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)
  • 43.
  • 44.
    scala> List(1, 2,3).map(e => e * 2) res0: List[Int] = List(2, 4, 6) scala> (1 to 10).filter(e => e % 2 == 0) res2: s.c.i.IndexedSeq[Int] = Vector(2, 4, 6, 8, 10)
  • 45.
    scala> def even(i:Int) = i % 2 == 0 even: (i: Int)Boolean scala> (1 to 10).filter(even) res3: s.c.i.IndexedSeq[Int] = Vector(2, 4, 6, 8, 10) scala> (1 to 10).foldLeft(0)((a, e) => a + e) res4: Int = 55
  • 46.
  • 47.
    def size(n: Int):Option[Int] = if (n == 0) None else Some(1000 / n) Pure Value
  • 48.
    def size(n: Int):Either[String,Int] = if (n == 0) Left(“Division by zero”) else Right(1000 / n) Pure Value
  • 49.
    def size(n: Int):Either[String,Int] = if (n == 0) Left(“Division by zero”) else Right(1000 / n) Sum Type Error Path Happy Path
  • 50.
    Types don’t lie unlessyou try and subvert your type system some languages like Haskell make subversion difficult
  • 51.
    Types Functionsmapping betweentypes closed under composition (algebraic) (referentially transparent)
  • 52.
  • 53.
    Types Functions (Mapping betweentypes) (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] { defzero: 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 analgebra 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 anAlgebra 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 betweentypes) (Algebraic) • sum type • product type • ADTs • compose • based on algebra of types Modules (Grouping related functions) • compose • can be parameterized Application (Top level artifact)
  • 67.
    Types and Functionsto drive our design instead of classes as in OO
  • 68.
    Client places order -flexible format 1
  • 69.
    Client places order -flexible format Transform to internal domain model entity and place for execution 1 2
  • 70.
    Client places order -flexible format Transform to internal domain model entity and place for execution Trade & Allocate to client accounts 1 2 3
  • 71.
  • 72.
    def clientOrders: List[ClientOrder] =>Either[Error, List[Order]]
  • 73.
    def clientOrders: List[ClientOrder] =>Either[Error, List[Order]] def execute(market: Market, brokerAccount: Account): List[Order] => Either[Error, List[Execution]] def allocate(accounts: List[Account]): List[Execution] => Either[Error, List[Trade]]
  • 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)
  • 75.
    def clientOrders: List[ClientOrder] =>Either[Error, List[Order]] def execute(market: Market, brokerAccount: Account): List[Order] => Either[Error, List[Execution]] def allocate(accounts: List[Account]): List[Execution] => Either[Error, List[Trade]]
  • 76.
    Plain function compositiondoesn’t work here
  • 77.
    We have thosepesky Either[Error, ..] floating around
  • 78.
  • 79.
  • 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)
  • 81.
    Effectful Function Composition final caseclass Kleisli[F[_], A, B](run: A => F[B]) { def andThen[C](f: B => F[C]) : Kleisli[F, A, C] = // .. }
  • 82.
    type ErrorOr[A] =Either[Error, A] def clientOrders: Kleisli[ErrorOr, List[ClientOrder], List[ClientOrder]] def execute(market: Market, brokerAccount: Account): Kleisli[ErrorOr, List[Order], List[Execution]] def allocate(accounts: List[Account]): Kleisli[ErrorOr, List[Execution], List[Trade]]
  • 83.
    def tradeGeneration( market: Market, broker:Account, clientAccounts: List[Account]) = { clientOrders andThen execute(market, broker) andThen allocate(clientAccounts) }
  • 84.
    type ErrorOr[A] =Either[Error, A] def clientOrders: Kleisli[ErrorOr, List[ClientOrder], List[ClientOrder]] def execute(market: Market, brokerAccount: Account): Kleisli[ErrorOr, List[Order], List[Execution]] def allocate(accounts: List[Account]): Kleisli[ErrorOr, List[Execution], List[Trade]] def tradeGeneration(market: Market, broker: Account, clientAccounts: List[Account]) = { clientOrders andThen execute(market, broker) andThen allocate(clientAccounts) }
  • 85.
    type ErrorOr[A] =Either[Error, A] def clientOrders: Kleisli[ErrorOr, List[ClientOrder], List[ClientOrder]] def execute(market: Market, brokerAccount: Account): Kleisli[ErrorOr, List[Order], List[Execution]] def allocate(accounts: List[Account]): Kleisli[ErrorOr, List[Execution], List[Trade]] def tradeGeneration(market: Market, broker: Account, clientAccounts: List[Account]) = { clientOrders andThen execute(market, broker) andThen allocate(clientAccounts) } trait TradingService { }
  • 86.
    type ErrorOr[A] =Either[Error, A] def clientOrders: Kleisli[ErrorOr, List[ClientOrder], List[ClientOrder]] def execute(market: Market, brokerAccount: Account): Kleisli[ErrorOr, List[Order], List[Execution]] def allocate(accounts: List[Account]): Kleisli[ErrorOr, List[Execution], List[Trade]] def tradeGeneration(market: Market, broker: Account, clientAccounts: List[Account]) = { clientOrders andThen execute(market, broker) andThen allocate(clientAccounts) } trait TradingService { }
  • 87.
    trait TradingApplication extendsTradingService with ReferenceDataService with ReportingService with AuditingService { // .. } object TradingApplication extends TradingApplication Modules Compose https://github.com/debasishg/fp-fnconf-2018
  • 88.