Académique Documents
Professionnel Documents
Culture Documents
@ScottWlaschin
fsharpforfunandprofit.com
@ScottWlaschin
fsharpforfunandprofit.com
@ScottWlaschin
fsharpforfunandprofit.com
Me
but...
Making fun of Enterprise OO
is like shooting fish in a barrel
but...
Inspired by @sjkilleen
Haskell programmers
Lisp
programmers
F#/OCaml programmers
https://www.flickr.com/photos/37511372@N08/5488671752/
FP DESIGN PATTERNS
OO pattern/principle
Single Responsibility Principle
Open/Closed principle
Dependency Inversion
Principle
Interface Segregation
Principle
Factory pattern
Strategy pattern
Decorator pattern
Visitor pattern
OO pattern/principle
Single Responsibility Principle
Open/Closed principle
Dependency Inversion
Principle
Interface Segregation
Principle
Factory pattern
Strategy pattern
Decorator pattern
Visitor pattern
Borg response
OO pattern/principle
Single Responsibility Principle
Open/Closed principle
Dependency Inversion
Principle
Interface Segregation
Principle
Factory pattern
Strategy pattern
Decorator pattern
Visitor pattern
FP equivalent
Functions
Functions
Functions, also
Functions
Functional patterns
Apomorphisms
Dynamorphisms
Chronomorphisms
Zygohistomorphic prepromorphisms
Functional patterns
Core Principles of FP design
Functions, types, composition
Functions as parameters
Functions as interfaces
Partial application & dependency injection
Continuations, chaining & the pyramid of doom
Monads
Error handling, Async
Maps
Dealing with wrapped data
Functors
Monoids
Aggregating data and operations
This talk
Important to understand!
Core principles of FP
Composition everywhere
Types are not classes
Function
Core principle:
Functions are things
Function
Functions as things
The Tunnel of
Function
Transformation
apple
-> banana
Functions as things
let z = 1
1
let add x y = x + y
int-> int->int
int
int->(int->int)
int->int
Function as output
(int->int)->int
int
let useFn f = (f 1) + 2
Int ->int
Function as input
let transformInt f x = (f x) + 1
int->int
int
int->int
Function as parameter
int
Core principle:
Composition everywhere
Function composition
Function 1
apple -> banana
Function 2
banana -> cherry
Function composition
Function 1
apple -> banana
>>
Function 2
banana -> cherry
Function composition
New Function
apple -> cherry
Design paradigm:
Functions all the way down
Low-level operation
string
ToUpper
string
Low-level operation
Low-level operation
Low-level operation
Service
Address
AddressValidator
Validation
Result
Service
Service
Service
Use-case
ChangeProfile
Request
UpdateProfileData
ChangeProfile
Result
Use-case
Use-case
Use-case
Web application
Http
Request
Http
Response
Core principle:
Types are not classes
So what is a type?
Set of
valid inputs
Set of
valid outputs
Lists
Lists
List.map
List.collect
List.filter
List.etc
Product types
Set of people
Set of dates
Sum types
+
Set of Cheque values
+
Set of CreditCard
values
type PaymentMethod =
| Cash
| Cheque of ChequeNumber
| Card of CardType * CardNumber
Design principle:
Strive for totality
Totality
Domain (int)
3
2
1
0
Codomain (int)
4
6
12
Totality
Domain (int)
3
2
1
0
Codomain (int)
4
6
12
Totality
Domain (int)
3
2
1
0
Codomain (int)
4
6
12
Totality
Constrain the input
NonZeroInteger
int TwelveDividedBy(int input)
{
3
switch (input)
2
{
1
case 3: return 4;
-1
case 2: return 6;
casetwelveDividedBy(x)
1: return 12;
input x
maps to
12/x
case -1: return -12;
}
0 is missing
}
int
4
6
12
0 doesnt have to
be handled
Totality
Extend the output
int
3
2
1
0
-1
Option<Int>
Some 4
Some 6
Some 12
None
0 is valid input
Design principle:
Use static types for domain
modelling and documentation
Static types only!
Sorry Clojure and JS
developers
FUNCTIONS AS
PARAMETERS
Guideline:
Parameterize all the things
let printList() =
for i in [1..10] do
printfn "the number is %i" i
let sum n =
let initialValue = 0
let action sumSoFar x = sumSoFar+x
[1..n] |> List.fold action initialValue
Lots of collection functions like this:
"fold", "map", "reduce", "collect", etc.
Tip:
Function types are "interfaces"
Strategy pattern
Object-oriented strategy pattern:
class MyClass
{
public MyClass(IBunchOfStuff strategy) {..}
int DoSomethingWithStuff(int x)
{
return _strategy.DoSomething(x)
}
}
Strategy pattern
Functional strategy pattern:
let DoSomethingWithStuff strategy x =
strategy x
int->int
int
int->int
IBunchOfStuff as parameter
int
int
bool
logger
let logger f input =
let output = f input
// then log input and output
// return output
bool
int
logger
bool
bool
bool
Composition!
int
log
int
isEven
bool
log
bool
Bad news:
Composition patterns
only work for functions that
have one parameter!
Good news!
Every function is a
one parameter function
int-> int->int
let add = (fun x y -> x + y)
int-> int->int
let add x = (fun y -> x + y)
int-> (int->int)
let three = 1 + 2
let three = (+) 1 2
let three = ((+) 1) 2
let three = 1 + 2
let three = (+) 1 2
let three = ((+) 1) 2
Pattern:
Partial application
Pattern:
Use partial application when
working with lists
Pattern:
Use partial application to do
dependency injection
Persistence ignorant
type GetCustomer = CustomerId -> Customer
let getCustomerFromDatabase connection
(customerId:CustomerId) =
// from connection
// select customer
// where customerId = customerId
This function requires a
connection
type of getCustomerFromDatabase =
DbConnection -> CustomerId -> Customer
Pattern:
The Hollywood principle*:
continuations
Continuations
int Divide(int top, int bottom)
{
Method has decided to throw
if (bottom == 0)
an exception
{
throw new InvalidOperationException("div by 0");
}
else
(who put it in charge?)
{
return top/bottom;
}
}
Continuations
Let the caller decide what
happens
Continuations
F# version
Continuations
let divide ifZero ifSuccess top bottom =
if (bottom=0)
then ifZero()
else ifSuccess (top/bottom)
let divide1
//test
let good1 = divide1 6 3
let bad1 = divide1 6 0
Continuations
let divide ifZero ifSuccess top bottom =
if (bottom=0)
then ifZero()
else ifSuccess (top/bottom)
let divide2
//test
let good2 = divide2 6 3
let bad2 = divide2 6 0
Continuations
let divide ifZero ifSuccess top bottom =
if (bottom=0)
then ifZero()
else ifSuccess (top/bottom)
let divide3
//test
let good3 = divide3 6 3
let bad3 = divide3 6 0
Pattern:
Chaining callbacks with
continuations
else
None
else
None
else
None
if opt.IsSome then
//do something with opt.Value
else
None
MONADS
A switch analogy
Input ->
Some
None
Connecting switches
on Some
Bypass on None
Connecting switches
Connecting switches
Composing switches
>>
>>
Composing switches
>>
>>
Composing switches
Composing switches
One-track input
Two-track input
Two-track input
Two-track input
Two-track output
Two-track input
Two-track input
Two-track output
Two-track input
Two-track output
Two-track input
Two-track output
Two-track input
Two-track output
Two-track input
Two-track output
Pattern:
Use bind to chain options
No pyramids!
Code is linear and clear.
Pattern:
Use bind to chain tasks
Connecting tasks
Wait
When task
completes
Wait
Pattern:
Use bind to chain error handlers
return "OK";
}
return "OK";
}
Request
Validate
Success
Failure
Connecting switches
Validate
UpdateDb
SendEmail
Connecting switches
Validate
UpdateDb
SendEmail
let updateCustomer =
receiveRequest
|> validateRequest
|> canonicalizeEmail
|> updateDbFromRequest
|> sendEmail
|> returnMessage
One track
let updateCustomerWithErrorHandling =
receiveRequest
|> validateRequest
|> canonicalizeEmail
|> updateDbFromRequest
|> sendEmail
|> returnMessage
Two track
MAPS
int option
string option
bool option
World of options
string
bool
int option
string option
bool option
World of options
string
bool
int option
string option
bool option
World of options
string
bool
World of options
add42
World of normal values
World of options
add42
Lifting
World of options
World of options
1 |>
add42
World of normal values
// Some 43
// 43
// 43
// 43
// Some 43
Lifting to lists
World of lists
Lifting to async
World of async
Guideline:
Most wrapped generic types
have a map. Use it!
Guideline:
If you create your own generic type,
create a map for it.
MONOIDS
Mathematics
Ahead
1+2=3
1 + (2 + 3) = (1 + 2) + 3
1+0=1
0+1=1
A way of combining
them
1+2=3
Some things
A way of combining
them
2x3=6
Some things
A way of combining
them
A way of combining
them
concat([a],[b]) = [a; b]
Some things
Is an integer
1+2
Is an integer
1+2+3
1+2+3+4
1 + (2 + 3) = (1 + 2) + 3
1+2+3+4
(1 + 2) + (3 + 4)
((1 + 2) + 3) + 4
1 - (2 - 3) = (1 - 2) - 3
1+0=1
0+1=1
42 * 1 = 42
1 * 42 = 42
The generalization
You start with a bunch of things, and some way of
combining them two at a time.
Rule 1 (Closure): The result of combining two things is
always another one of the things.
1+2+3+4
[ 1; 2; 3; 4 ] |> List.reduce (+)
1*2*3*4
[ 1; 2; 3; 4 ] |> List.reduce (*)
1+2+3+4
(1 + 2)
(3 + 4)
3+7
(1 + 2 + 3)
(1 + 2 + 3) + 4
(6) + 4
Pattern:
Simplifying aggregation code with monoids
Any combination
of monoids is
also a monoid
Pattern:
Convert non-monoids to monoids
Not a monoid
Customer
+
Customer
+
Customer
Map
A monoid
Customer Stats
+
Customer Stats
+
Customer Stats
Reduce
Customer Stats
(Total)
https://twitter.com/daviottenheimer
/status/532661754820829185
Guideline:
Convert expensive monoids
to cheap monoids
Map
A monoid
Summary (Mon)
+
Summary (Tue)
+
Summary (Wed)
Much more efficient for
incremental updates
Monoid homomorphism
Pattern:
Seeing monoids everywhere
Metrics guideline:
Use counters rather than rates
Alternative metrics guideline:
Make sure your metrics are monoids
incremental updates
can handle missing data
Function 1
apple -> banana
Function 2
banana -> cherry
>>
New Function
apple -> cherry
Not a monoid
Function 1
apple -> apple
>>
Function 2
apple -> apple
Function 3
apple -> apple
Same thing
A monoid!
Endomorphisms
let plus1 x = x + 1
// int->int
let times2 x = x * 2
// int->int
let subtract42 x = x 42
// int->int
Reduce
plus1ThenTimes2ThenSubtract42 // int->int
Another endomorphism!
Event sourcing
Event application function:
Is an endomorphism
Endomorphism
apply event1
apply event2
apply event3
Reduce
applyAllEventsAtOnce
incremental updates
can handle missing events
Another endomorphism!
Series combination
=
Result is same
kind of thing
(Closure)
Series combination
)+
+
Order not
important
(Associative)
Monoid!
Parallel combination
Order not
important
(Associative)
=
Same thing
(Closure)
Monoid!
Monad laws
The Monad laws are just the monoid
definitions in diguise
Closure, Associativity, Identity
THANKS!