3. Functional Programming
• programming with pure functions
• a function’s output is solely determined by the input
(much like mathematical functions)
• no assignment, no side-effects
•(pure) mapping between values
• functions compose
• expression-oriented programming
7. What is an Algebra ?
Algebra is the study of algebraic structures
In mathematics, and more specifically in abstract
algebra, an algebraic structure is
a set (called carrier set or underlying set)
with one or more finitary operations defined on it
that satisfies a list of axioms
-Wikipedia
(https://en.wikipedia.org/wiki/Algebraic_structure)
8. Set A
ϕ : A × A → A
fo r (a, b) ∈ A
ϕ(a, b)
a ϕ b
given
a binary operation
for specific a, b
or
The Algebra of Sets
9. Algebraic Thinking
• Thinking and reasoning about code in terms of the
data types and the operations they support
without considering a bit about the underlying
implementations
• f: A => B and g: B => C, we should be able to
reason that we can compose f and g algebraically
to build a larger function h: A => C
•algebraic composition
13. What is a domain model ?
A domain model in problem solving and software engineering is a
conceptual model of all the topics related to a specific problem. It
describes the various entities, their attributes, roles, and
relationships, plus the constraints that govern the problem domain.
It does not describe the solutions to the problem.
Wikipedia (http://en.wikipedia.org/wiki/Domain_model)
20. A Bounded Context
• has a consistent vocabulary
• a set of domain behaviors modeled as
functions on domain objects
implemented as types
• each of the behaviors honor a set of
business rules
• related behaviors grouped as modules
21. Domain Model = ∪(i) Bounded Context(i)
Bounded Context = { m[T1,T2,..] | T(i) ∈ Types }
Module = { f(x,y,..) | p(x,y) ∈ Domain Rules }
• domain function
• on an object of types x, y, ..
• composes with other functions
• closed under composition
• business rules
23. explicit verifiable
• types
• type constraints
• functions between types
• type constraints
• more constraints if you have DT
• algebraic property based testing
(algebra of types, functions & laws
of the solution domain model)
Domain Model Algebra
24. What is meant by the
algebra of a type ?
•Nothing
•Unit
•Boolean
•Byte
•String
25. What is meant by the
algebra of a type ?
•Nothing -> 0
•Unit -> 1
•Boolean -> 2
•Byte -> 256
•String -> a lot
26. What is meant by the
algebra of a type ?
•(Boolean, Unit)
•(Byte, Unit)
•(Byte, Boolean)
•(Byte, Byte)
•(String, String)
27. 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
28. 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)
29. 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
30. 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]
)
31. What is meant by the
algebra of a type ?
•Boolean or Unit
•Byte or Unit
•Byte or Boolean
•Byte or Byte
•String or String
32. 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
33. 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)
34. 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
35. 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
36. 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
}
37. De-structuring with
Pattern Matching
def process(i: Instrument) = i match {
case Equity(isin, _, _, faceValue) => // ..
case FixedIncome(isin, _, issueDate, _, nominal) => // ..
case Currency(isin) => // ..
}
39. Sum Types and Domain
Models
• Models heterogeneity and heterogenous data
structures are ubiquitous in a domain model
• Allows modeling of expressive domain types in a
succinct and secure way - secure by construction
• Pattern matching makes encoding domain logic
easy and expressive
40. – Robert Harper in Practical Foundations of Programming Languages
“The absence of sums is the origin of C. A. R.
Hoare’s self-described ‘billion dollar mistake,’
the null pointer”
41. More algebra of types
• Exponentiation - f: A => B has b^a
inhabitants
• Taylor Series - Recursive Data Types
• Derivatives - Zippers
• …
42. Scaling of the Algebra
• Since a function is a mapping from the domain of types
to the co-domain of types, we can talk about the
algebra of a function
• A module is a collection of related functions - we can
think of the algebra of a module as the union of
the algebras of all functions that it encodes
• A domain model (one bounded context) can be loosely
thought of as a collection of modules, which gives rise
to the connotation of a domain model algebra
43. Algebraic Composition
• Functions compose based on types, which
means ..
• Algebras compose
• Giving rise to larger algebras / functions, which
in turn implies ..
• We can construct larger domain behaviors by
composing smaller behaviors
44. Algebras are Ubiquitous
• Generic, parametric and hence usable on an
infinite set of data types, including your domain
model’s types
45. Algebras are Ubiquitous
• Generic, parametric and hence usable on an
infinite set of data types, including your domain
model’s types
• Clear separation between the contract (the
algebra) and its implementations
(interpreters)
46. Algebras are Ubiquitous
• Generic, parametric and hence usable on an
infinite set of data types, including your domain
model’s types
• Clear separation between the contract (the
algebra) and its implementations
(interpreters)
• Standard vocabulary (like design patterns)
47. Algebras are Ubiquitous
• Generic, parametric and hence usable on an
infinite set of data types, including your domain
model’s types
• Clear separation between the contract (the
algebra) and its implementations (interpreters)
• Standard vocabulary (like design patterns)
• Existing set of reusable algebras offered by the
standard libraries
48. Roadmap to a Functional
and Algebraic Model
1. Identify domain behaviors
2. Identify the algebras of functions (not implementation)
3. Compose algebras to form larger behaviors - follow
the types depending on the semantics of
compositionality.We call this behavior a program that
models the use case
4. Plug in concrete types to complete the
implementation
49. Domain Model = ∪(i) Bounded Context(i)
Bounded Context = { m[T1,T2,..] | T(i) ∈ Types }
Module = { f(x,y,..) | p(x,y) ∈ Domain Rules }
• domain function
• on an object of types x, y
• composes with other functions
• closed under composition
• business rules
50. Domain Model = ∪(i) Bounded Context(i)
Bounded Context = { m[T1,T2,..] | T(i) ∈ Types }
Module = { f(x,y,..) | p(x,y) ∈ Domain Rules }
• domain function
• on an object of types x, y
• composes with other functions
• closed under composition
• business rules
Domain Algebra
Domain Algebra
51. Given all the properties of algebra, can we
consider algebraic composition to be the
basis of designing, implementing and modularizing
domain models ?
53. Client places order
- flexible format
Transform to internal domain
model entity and place for execution
1 2
54. Client places order
- flexible format
Transform to internal domain
model entity and place for execution
Trade & Allocate to
client accounts
1 2
3
55. def fromClientOrder: ClientOrder => Order
def execute(market: Market, brokerAccount: Account)
: Order => List[Execution]
def allocate(accounts: List[Account])
: List[Execution] => List[Trade]
trait Trading {
}
trait TradeComponent extends Trading
with Logging with Auditing
algebra of domain
behaviors / functions
functions aggregate
upwards into modules
modules aggregate
into larger modules
56. .. so we have a decent algebra of our module, the
names reflect the appropriate artifacts from the
domain (ubiquitous language), the types are
well published and we are quite explicit in what
the behaviors do ..
57. 1. Compositionality - How do we compose
the 3 behaviors that we published to
generate trade in the market and allocate
to client accounts ?
2. Side-effects - We need to compose them
alongside all side-effects that form a core
part of all non trivial domain model
implementations
58. • Error handling ?
• throw / catch exceptions is not RT
• Partiality ?
• partial functions can report runtime exceptions if invoked
with unhandled arguments (violates RT)
• Reading configuration information from environment ?
• may result in code repetition if not properly handled
• Logging ?
• side-effects
Side-effects
59. Side-effects
• Database writes
• Writing to a message queue
• Reading from stdin / files
• Interacting with any external resource
• Changing state in place
67. F[A]
The answer that the
effect computesThe additional stuff
modeling the computation
68. • The F[_] that we saw is an opaque type - it
has no denotation till we give it one
• The denotation that we give to F[_] depends
on the semantics of compositionality that we
would like to have for our domain model
behaviors
70. • Just the Algebra
• No denotation, no
concrete type
• Explicitly stating that we
have effectful functions
here
def fromClientOrder: ClientOrder => F[Order]
def execute(market: Market, brokerAccount: Account)
: Order => F[List[Execution]]
def allocate(accounts: List[Account])
: List[Execution] => F[List[Trade]]
trait Trading[F[_]] {
}
Effect Type
71. • .. we have intentionally kept the algebra open
for interpretation ..
• .. there are use cases where you would like to
have multiple interpreters for the same
algebra ..
72. The Program
def tradeGeneration[M[_]: Monad](T: Trading[M]) = for {
order <- T.fromClientOrder(cor)
executions <- T.execute(m1, ba, order)
trades <- T.allocate(List(ca1, ca2, ca3), executions)
} yield trades
73. class TradingInterpreter[F[_]]
(implicit me: MonadError[F, Throwable])
extends Trading[F] {
def fromClientOrder: ClientOrder => F[Order] = makeOrder(_) match {
case Left(dv) => me.raiseError(new Exception(dv.message))
case Right(o) => o.pure[F]
}
def execute(market: Market, brokerAccount: Account)
: Order => F[List[Execution]] = ...
def allocate(accounts: List[Account])
: List[Execution] => F[List[Trade]] = ...
}
One Sample Interpreter
74. • .. one lesson in modularity - commit to a
concrete implementation as late as
possible in the design ..
• .. we have just indicated that we want a
monadic effect - we haven’t committed to
any concrete monad type even in the
interpreter ..
75. The Program
def tradeGeneration[M[_]: Monad](T: Trading[M]) = for {
order <- T.fromClientOrder(cor)
executions <- T.execute(m1, ba, order)
trades <- T.allocate(List(ca1, ca2, ca3), executions)
} yield trades
import cats.effect.IO
object TradingComponent extends TradingInterpreter[IO]
tradeGeneration(TradingComponent).unsafeRunSync
76. The Program
def tradeGeneration[M[_]: Monad](T: Trading[M]) = for {
order <- T.fromClientOrder(cor)
executions <- T.execute(m1, ba, order)
trades <- T.allocate(List(ca1, ca2, ca3), executions)
} yield trades
import monix.eval.Task
object TradingComponent extends TradingInterpreter[Task]
tradeGeneration(TradingComponent)
77. The Program
def tradeGenerationLoggable[M[_]: Monad]
(T: Trading[M], L: Logging[M]) = for {
_ <- L.info("starting order processing")
order <- T.fromClientOrder(cor)
executions <- T.execute(m1, ba, order)
trades <- T.allocate(List(ca1, ca2, ca3), executions)
_ <- L.info("allocation done")
} yield trades
object TradingComponent extends TradingInterpreter[IO]
object LoggingComponent extends LoggingInterpreter[IO]
tradeGenerationLoggable(TradingComponent, LoggingComponent).unsafeRunSync
81. - Rob Norris at scale.bythebay.io talk - 2017 (https://www.youtube.com/
watch?v=po3wmq4S15A)
“Effects and side-effects are not the same thing. Effects are
good, side-effects are bugs.Their lexical similarity is really
unfortunate because people often conflate the two ideas”
82.
83.
84.
85. Takeaways
• Algebra scales from that of one single data type to
an entire bounded context
• Algebras compose enabling composition of
domain behaviors
• Algebras let you focus on the compositionality
without any context of implementation
• Statically typed functional programming is
programming with algebras
86. Takeaways
• Abstract early, interpret as late as possible
• Abstractions / functions compose only when they are
abstract and parametric
• Modularity in the presence of side-effects is a challenge
• Effects as algebras are pure values that can compose based
on laws
• Honor the law of using the least powerful abstraction
that works
87. From the Bible
“Name classes and operations to describe their effect and purpose,
without reference to the means by which they do what they
promise.This relieves the client developer of the need to understand
the internals.These names should conform to the UBIQUITOUS
LANGUAGE so that team members can quickly infer their meaning.
Write a test for a behavior before creating it, to force your thinking
into client developer mode.”
- Eric Evans (Domain Driven Design)
in the chapter on Supple Design, while discussing
Intention Revealing Interfaces
88. • All names are from the domain vocabulary
• Just the algebra describing the promise, no
implementation details
• Purpose and effect explicit - yes, literally
explicit with effects
def fromClientOrder: ClientOrder => F[Order]
def execute(market: Market, brokerAccount: Account)
: Order => F[List[Execution]]
def allocate(accounts: List[Account])
: List[Execution] => F[List[Trade]]
trait Trading[F[_]] {
}
89. From the Bible
“Place as much of the logic of the program as possible into
functions, operations that return results with no observable side-
effects.”
- Eric Evans (Domain Driven Design)
in the chapter on Supple Design, while discussing
Side-Effect-Free Functions
90. def tradeGeneration[M[_]: Monad](T: Trading[M]) = for {
order <- T.fromClientOrder(cor)
executions <- T.execute(m1, ba, order)
trades <- T.allocate(List(ca1, ca2, ca3), executions)
} yield trades
• The program tradeGeneration is completely side-effect free. It
generates a pure value.
• Since the program is pure, you can interpret it in many ways (as we saw
earlier).
• The side-effects occur only when you submit the program to the run time
system.
• This is also an example where using algebraic & functional approach we
get a clear separation between the building of an abstraction and
executing it.
91. From the Bible
“When it fits, define an operation whose return type is the same as
the type of its argument(s). If the implementer has state that is
used in the computation, then the implementer is effectively an
argument of the operation, so the argument(s) and return value
should be of the same type as the implementer. Such an operation
is closed under the set of instances of that type.A closed operation
provides a high-level interface without introducing any dependency
on other concepts.”
- Eric Evans (Domain Driven Design)
in the chapter on Supple Design, while discussing
Closure of Operations
92. trait Semigroup[A] {
def combine(x: A, y: A): A
}
trait Monoid[A] extends Semigroup[A] {
def empty: A
}
• With algebraic modeling, you can encode the closure of operations
through the algebra of a Monoid.
★ parametric
★ define the algebra once, implement it as many times based on the
context
★ compositionality at the algebra level
• For stateful computation, use the algebra of the State Monad and
manipulate state as a Monoid