Functional Programming Patterns (NDC London 2014)

195
Functional Design Patterns (NDC London 2014) @ScottWlaschin fsharpforfunandprofit.com

description

(video of these slides available here http://fsharpforfunandprofit.com/fppatterns/) In object-oriented development, we are all familiar with design patterns such as the Strategy pattern and Decorator pattern, and design principles such as SOLID. The functional programming community has design patterns and principles as well. This talk will provide an overview of some of these, and present some demonstrations of FP design in practice.

Transcript of Functional Programming Patterns (NDC London 2014)

Page 1: Functional Programming Patterns (NDC London 2014)

Functional Design Patterns (NDC London 2014)

@ScottWlaschin

fsharpforfunandprofit.com

Page 2: Functional Programming Patterns (NDC London 2014)

Functional Design Patterns

@ScottWlaschin

fsharpforfunandprofit.com

X

Page 3: Functional Programming Patterns (NDC London 2014)

Functional Design Patterns

@ScottWlaschin

fsharpforfunandprofit.com

Page 4: Functional Programming Patterns (NDC London 2014)

HOW I GOT HERE

Page 5: Functional Programming Patterns (NDC London 2014)

Me

Page 6: Functional Programming Patterns (NDC London 2014)

I

Page 7: Functional Programming Patterns (NDC London 2014)

Making fun of Enterprise OO is like shooting fish in a barrel

I but...

Page 8: Functional Programming Patterns (NDC London 2014)

I but...

Making fun of Enterprise OO is like shooting IMarineVertebrates in an AbstractBarrelProxyFactory

Inspired by @sjkilleen

Page 9: Functional Programming Patterns (NDC London 2014)

I

Page 10: Functional Programming Patterns (NDC London 2014)

This is what happens when you dabble too much in functional programming

Page 11: Functional Programming Patterns (NDC London 2014)

https://www.flickr.com/photos/37511372@N08/5488671752/

Haskell programmers

F#/OCaml programmers

Visual Basic programmers

Lisp programmers

Page 12: Functional Programming Patterns (NDC London 2014)

FP DESIGN PATTERNS

Page 13: Functional Programming Patterns (NDC London 2014)

• Single Responsibility Principle

• Open/Closed principle

• Dependency Inversion

Principle

• Interface Segregation

Principle

• Factory pattern

• Strategy pattern

• Decorator pattern

• Visitor pattern

OO pattern/principle

Page 14: Functional Programming Patterns (NDC London 2014)

• Single Responsibility Principle

• Open/Closed principle

• Dependency Inversion

Principle

• Interface Segregation

Principle

• Factory pattern

• Strategy pattern

• Decorator pattern

• Visitor pattern

OO pattern/principle Borg response

Page 15: Functional Programming Patterns (NDC London 2014)

• Single Responsibility Principle

• Open/Closed principle

• Dependency Inversion

Principle

• Interface Segregation

Principle

• Factory pattern

• Strategy pattern

• Decorator pattern

• Visitor pattern

• Functions

• Functions

• Functions, also

• Functions

• You will be assimilated!

• Functions again

• Functions

• Resistance is futile!

OO pattern/principle FP equivalent

Seriously, FP patterns are different

Page 16: Functional Programming Patterns (NDC London 2014)

Functional patterns

• Apomorphisms

• Dynamorphisms

• Chronomorphisms

• Zygohistomorphic prepromorphisms

Page 17: Functional Programming Patterns (NDC London 2014)

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

Page 18: Functional Programming Patterns (NDC London 2014)

This talk

A whirlwind tour of many sights Don't worry if you don't understand everything

Page 19: Functional Programming Patterns (NDC London 2014)

(SOME) CORE PRINCIPLES OF

FUNCTIONAL PROGRAMMING

Important to understand!

Page 20: Functional Programming Patterns (NDC London 2014)

Core principles of FP

Function

Types are not classes

Functions are things

Composition everywhere

Page 21: Functional Programming Patterns (NDC London 2014)

Core principle:

Functions are things

Function

Page 22: Functional Programming Patterns (NDC London 2014)

Functions as things

The Tunnel of Transformation Function

apple -> banana

A function is a standalone thing, not attached to a class

Page 23: Functional Programming Patterns (NDC London 2014)

Functions as things

let z = 1

int-> int->int

1

let add x y = x + y

Page 24: Functional Programming Patterns (NDC London 2014)

Functions as inputs and outputs

let useFn f = (f 1) + 2

let add x = (fun y -> x + y)

int->(int->int)

int

int

let transformInt f x = (f x) + 1

int int

int->int

Function as output

Int ->int

Function as input

Function as parameter int->int

(int->int)->int

int->int

Page 25: Functional Programming Patterns (NDC London 2014)

Core principle:

Composition everywhere

Page 26: Functional Programming Patterns (NDC London 2014)

Function composition

Function 1

apple -> banana

Function 2

banana -> cherry

Page 27: Functional Programming Patterns (NDC London 2014)

Function composition

>> Function 1

apple -> banana Function 2

banana -> cherry

Page 28: Functional Programming Patterns (NDC London 2014)

Function composition

New Function

apple -> cherry

Can't tell it was built from smaller functions!

Page 29: Functional Programming Patterns (NDC London 2014)

Design paradigm:

Functions all the way down

Page 30: Functional Programming Patterns (NDC London 2014)

“Functions in the small,

objects in the large”

Page 31: Functional Programming Patterns (NDC London 2014)

Low-level operation

ToUpper string string

Page 32: Functional Programming Patterns (NDC London 2014)

Low-level operation

Service

AddressValidator

A “Service” is just like a microservice but without the "micro" in front

Validation

Result

Address

Low-level operation Low-level operation

Page 33: Functional Programming Patterns (NDC London 2014)

Service

Use-case

UpdateProfileData ChangeProfile

Result

ChangeProfile

Request

Service Service

Page 34: Functional Programming Patterns (NDC London 2014)

Use-case

Web application

Http

Response

Http

Request

Use-case Use-case

Page 35: Functional Programming Patterns (NDC London 2014)

Core principle:

Types are not classes

Page 36: Functional Programming Patterns (NDC London 2014)

X

Page 37: Functional Programming Patterns (NDC London 2014)

Types are not classes

Set of

valid inputs

Set of

valid outputs

So what is a type?

Page 38: Functional Programming Patterns (NDC London 2014)

Types separate data from behavior

Lists

Lists

List.map

List.collect

List.filter

List.etc

Page 39: Functional Programming Patterns (NDC London 2014)

Types can be composed too “algebraic types"

Page 40: Functional Programming Patterns (NDC London 2014)

Product types

× = Alice, Jan 12th

Bob, Feb 2nd

Carol, Mar 3rd

Set of people Set of dates

type Birthday = Person * Date

Page 41: Functional Programming Patterns (NDC London 2014)

Sum types

Set of Cash values

Set of Cheque values

Set of CreditCard

values

+

+

type PaymentMethod =

| Cash

| Cheque of ChequeNumber

| Card of CardType * CardNumber

Page 42: Functional Programming Patterns (NDC London 2014)

Design principle:

Strive for totality

Page 43: Functional Programming Patterns (NDC London 2014)

twelveDividedBy(x)

input x maps to 12/x

3

2

1

0

Domain (int) Codomain (int)

4

6

12

Totality

int TwelveDividedBy(int input) { switch (input) { case 3: return 4; case 2: return 6; case 1: return 12; case 0: return ??; } }

Page 44: Functional Programming Patterns (NDC London 2014)

twelveDividedBy(x)

input x maps to 12/x

3

2

1

0

Domain (int) Codomain (int)

4

6

12

Totality

int TwelveDividedBy(int input) { switch (input) { case 3: return 4; case 2: return 6; case 1: return 12; case 0: return ??; } } What happens here?

Page 45: Functional Programming Patterns (NDC London 2014)

twelveDividedBy(x)

input x maps to 12/x

3

2

1

0

Domain (int) Codomain (int)

4

6

12

Totality

int TwelveDividedBy(int input) { switch (input) { case 3: return 4; case 2: return 6; case 1: return 12; case 0: throw InvalidArgException; } }

int -> int

This type signature is a lie!

You tell me you can handle 0, and then you

complain about it?

Page 46: Functional Programming Patterns (NDC London 2014)

twelveDividedBy(x)

input x maps to 12/x

3

2

1

-1

NonZeroInteger int

4

6

12

Totality

int TwelveDividedBy(int input) { switch (input) { case 3: return 4; case 2: return 6; case 1: return 12; case -1: return -12; } }

Constrain the input

0 doesn’t have to be handled

NonZeroInteger -> int

0 is missing

Types are documentation

Page 47: Functional Programming Patterns (NDC London 2014)

twelveDividedBy(x)

input x maps to 12/x

3

2

1

0

-1

int Option<Int>

Some 4

Some 6

Some 12

None

Totality

int TwelveDividedBy(int input) { switch (input) { case 3: return Some 4; case 2: return Some 6; case 1: return Some 12; case 0: return None; } }

Extend the output

0 is valid input

int -> int option

Types are documentation

Page 48: Functional Programming Patterns (NDC London 2014)

Design principle:

Use static types for domain

modelling and documentation

Static types only! Sorry Clojure and JS

developers

Page 49: Functional Programming Patterns (NDC London 2014)

Big topic! Not enough time today !

More on DDD and designing with types at

fsharpforfunandprofit.com/ddd

Page 50: Functional Programming Patterns (NDC London 2014)

FUNCTIONS AS

PARAMETERS

Page 51: Functional Programming Patterns (NDC London 2014)

Guideline:

Parameterize all the things

Page 52: Functional Programming Patterns (NDC London 2014)

Parameterize all the things

let printList() = for i in [1..10] do printfn "the number is %i" i

Page 53: Functional Programming Patterns (NDC London 2014)

Parameterize all the things

It's second nature to parameterize the data input:

let printList aList = for i in aList do printfn "the number is %i" i

Page 54: Functional Programming Patterns (NDC London 2014)

Parameterize all the things

let printList anAction aList = for i in aList do anAction i

FPers would parameterize the action as well:

We've decoupled the behavior from the data.

Any list, any action!

Page 55: Functional Programming Patterns (NDC London 2014)

Parameterize all the things

public static int Product(int n) { int product = 1; for (int i = 1; i <= n; i++) { product *= i; } return product; } public static int Sum(int n) { int sum = 0; for (int i = 1; i <= n; i++) { sum += i; } return sum; }

Page 56: Functional Programming Patterns (NDC London 2014)

public static int Product(int n) { int product = 1; for (int i = 1; i <= n; i++) { product *= i; } return product; } public static int Sum(int n) { int sum = 0; for (int i = 1; i <= n; i++) { sum += i; } return sum; }

Parameterize all the things

Page 57: Functional Programming Patterns (NDC London 2014)

Parameterize all the things

let product n = let initialValue = 1 let action productSoFar x = productSoFar * x [1..n] |> List.fold action initialValue 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.

Page 58: Functional Programming Patterns (NDC London 2014)

Tip:

Function types are "interfaces"

Page 59: Functional Programming Patterns (NDC London 2014)

Function types are interfaces

interface IBunchOfStuff { int DoSomething(int x); string DoSomethingElse(int x); void DoAThirdThing(string x); }

Let's take the Single Responsibility Principle and the

Interface Segregation Principle to the extreme...

Every interface should have only one method!

Page 60: Functional Programming Patterns (NDC London 2014)

Function types are interfaces

interface IBunchOfStuff { int DoSomething(int x); }

An interface with one method is a just a function type

type IBunchOfStuff: int -> int

Any function with that type is compatible with it

let add2 x = x + 2 // int -> int let times3 x = x * 3 // int -> int

Page 61: Functional Programming Patterns (NDC London 2014)

Strategy pattern

class MyClass { public MyClass(IBunchOfStuff strategy) {..} int DoSomethingWithStuff(int x) { return _strategy.DoSomething(x) } }

Object-oriented strategy pattern:

Page 62: Functional Programming Patterns (NDC London 2014)

Strategy pattern

int int

int->int

int->int

let DoSomethingWithStuff strategy x = strategy x

Functional strategy pattern:

IBunchOfStuff as parameter

Page 63: Functional Programming Patterns (NDC London 2014)

Decorator pattern using function parameter:

let isEven x = (x % 2 = 0) // int -> bool

isEven int bool

logger int bool

let logger f input = let output = f input // then log input and output // return output

Page 64: Functional Programming Patterns (NDC London 2014)

Decorator pattern using function parameter:

let isEven x = (x % 2 = 0) // int -> bool

isEven int bool

logger int bool

let isEvenWithLogging = logger isEven // int -> bool

Substitutable for original isEven

Page 65: Functional Programming Patterns (NDC London 2014)

Decorator pattern using function composition:

let isEven x = (x % 2 = 0) // int -> bool

isEven int bool

isEven int bool log int int log bool bool

let isEvenWithLogging = log >> isEven >> log // int -> bool

Substitutable for original isEven

Composition!

Page 66: Functional Programming Patterns (NDC London 2014)

Bad news:

Composition patterns

only work for functions that

have one parameter!

Page 67: Functional Programming Patterns (NDC London 2014)

Good news!

Every function is a

one parameter function

Page 68: Functional Programming Patterns (NDC London 2014)

Writing functions in different ways

let add x y = x + y

let add = (fun x y -> x + y)

let add x = (fun y -> x + y)

int-> int->int

int-> int->int

int-> (int->int)

Normal (Two parameters)

Page 69: Functional Programming Patterns (NDC London 2014)

let three = 1 + 2

let three = (+) 1 2

let three = ((+) 1) 2

"+" is a two parameter function

Page 70: Functional Programming Patterns (NDC London 2014)

let three = 1 + 2

let three = (+) 1 2

let three = ((+) 1) 2

let add1 = (+) 1

let three = add1 2

No, "+" is a one param function!

Page 71: Functional Programming Patterns (NDC London 2014)

Pattern:

Partial application

Page 72: Functional Programming Patterns (NDC London 2014)

let name = "Scott" printfn "Hello, my name is %s" name

let name = "Scott" (printfn "Hello, my name is %s") name

let name = "Scott" let hello = (printfn "Hello, my name is %s") hello name

Can reuse "hello" in many places now!

Page 73: Functional Programming Patterns (NDC London 2014)

Pattern:

Use partial application when

working with lists

Page 74: Functional Programming Patterns (NDC London 2014)

let names = ["Alice"; "Bob"; "Scott"] names |> List.iter hello

let hello = printfn "Hello, my name is %s"

let add1 = (+) 1 let equals2 = (=) 2

[1..100] |> List.map add1 |> List.filter equals2

Page 75: Functional Programming Patterns (NDC London 2014)

Pattern:

Use partial application to do

dependency injection

Page 76: Functional Programming Patterns (NDC London 2014)

type GetCustomer = CustomerId -> Customer

let getCustomerFromDatabase connection (customerId:CustomerId) = // from connection // select customer // where customerId = customerId

type of getCustomerFromDatabase = DbConnection -> CustomerId -> Customer

let getCustomer1 = getCustomerFromDatabase myConnection // getCustomer1 : CustomerId -> Customer

Persistence ignorant

This function requires a connection

The partially applied function does NOT require a connection – it's baked in!

Page 77: Functional Programming Patterns (NDC London 2014)

type GetCustomer = CustomerId -> Customer

let getCustomerFromMemory dict (customerId:CustomerId) = dict.Get(customerId)

type of getCustomerFromMemory = Dictionary<Id,Customer> -> CustomerId -> Customer

let getCustomer2 = getCustomerFromMemory dict // getCustomer2 : CustomerId -> Customer

This function requires a dictionary

The partially applied function does NOT require a dictionary – it's baked in!

Page 78: Functional Programming Patterns (NDC London 2014)

Pattern:

The Hollywood principle*:

continuations

Page 79: Functional Programming Patterns (NDC London 2014)

Continuations

int Divide(int top, int bottom) { if (bottom == 0) { throw new InvalidOperationException("div by 0"); } else { return top/bottom; } }

Method has decided to throw an exception

(who put it in charge?)

Page 80: Functional Programming Patterns (NDC London 2014)

Continuations

void Divide(int top, int bottom, Action ifZero, Action<int> ifSuccess) { if (bottom == 0) { ifZero(); } else { ifSuccess( top/bottom ); } }

Let the caller decide what happens

what happens next?

Page 81: Functional Programming Patterns (NDC London 2014)

Continuations

let divide ifZero ifSuccess top bottom = if (bottom=0) then ifZero() else ifSuccess (top/bottom)

F# version

Four parameters is a lot though!

Wouldn't it be nice if we could somehow "bake in" the two behaviour functions....

Page 82: Functional Programming Patterns (NDC London 2014)

Continuations

let divide ifZero ifSuccess top bottom = if (bottom=0) then ifZero() else ifSuccess (top/bottom)

let ifZero1 () = printfn "bad" let ifSuccess1 x = printfn "good %i" x let divide1 = divide ifZero1 ifSuccess1 //test let good1 = divide1 6 3 let bad1 = divide1 6 0

setup the functions to: print a message

Partially apply the continuations

Use it like a normal function – only two parameters

Page 83: Functional Programming Patterns (NDC London 2014)

let divide ifZero ifSuccess top bottom = if (bottom=0) then ifZero() else ifSuccess (top/bottom)

Continuations

let ifZero2() = None let ifSuccess2 x = Some x let divide2 = divide ifZero2 ifSuccess2 //test let good2 = divide2 6 3 let bad2 = divide2 6 0

setup the functions to: return an Option

Use it like a normal function – only two parameters

Partially apply the continuations

Page 84: Functional Programming Patterns (NDC London 2014)

let divide ifZero ifSuccess top bottom = if (bottom=0) then ifZero() else ifSuccess (top/bottom)

Continuations

let ifZero3() = failwith "div by 0" let ifSuccess3 x = x let divide3 = divide ifZero3 ifSuccess3 //test let good3 = divide3 6 3 let bad3 = divide3 6 0

setup the functions to: throw an exception

Use it like a normal function – only two parameters

Partially apply the continuations

Page 85: Functional Programming Patterns (NDC London 2014)

Pattern:

Chaining callbacks with

continuations

Page 86: Functional Programming Patterns (NDC London 2014)

Pyramid of doom: null testing example

Let example input = let x = doSomething input if x <> null then let y = doSomethingElse x if y <> null then let z = doAThirdThing y if z <> null then let result = z result else null else null else null

I know you could do early returns, but bear with me...

Page 87: Functional Programming Patterns (NDC London 2014)

Pyramid of doom: async example

let taskExample input = let taskX = startTask input taskX.WhenFinished (fun x -> let taskY = startAnotherTask x taskY.WhenFinished (fun y -> let taskZ = startThirdTask y taskZ.WhenFinished (fun z -> z // final result

Page 88: Functional Programming Patterns (NDC London 2014)

Pyramid of doom: null example

let example input = let x = doSomething input if x <> null then let y = doSomethingElse x if y <> null then let z = doAThirdThing y if z <> null then let result = z result else null else null else null

Nulls are a code smell: replace with Option!

Page 89: Functional Programming Patterns (NDC London 2014)

Pyramid of doom: option example

let example input = let x = doSomething input if x.IsSome then let y = doSomethingElse (x.Value) if y.IsSome then let z = doAThirdThing (y.Value) if z.IsSome then let result = z.Value Some result else None else None else None

Much more elegant, yes?

No! This is fugly! But there is a pattern we can exploit...

Page 90: Functional Programming Patterns (NDC London 2014)

Pyramid of doom: option example

let example input = let x = doSomething input if x.IsSome then let y = doSomethingElse (x.Value) if y.IsSome then let z = doAThirdThing (y.Value) if z.IsSome then // do something with z.Value // in this block else None else None else None

Page 91: Functional Programming Patterns (NDC London 2014)

Pyramid of doom: option example

let example input = let x = doSomething input if x.IsSome then let y = doSomethingElse (x.Value) if y.IsSome then // do something with y.Value // in this block else None else None

Page 92: Functional Programming Patterns (NDC London 2014)

Pyramid of doom: option example

let example input = let x = doSomething input if x.IsSome then // do something with x.Value // in this block else None

Can you see the pattern?

Page 93: Functional Programming Patterns (NDC London 2014)

if opt.IsSome then //do something with opt.Value else None

Page 94: Functional Programming Patterns (NDC London 2014)

let ifSomeDo f opt = if opt.IsSome then f opt.Value else None

Page 95: Functional Programming Patterns (NDC London 2014)

let example input = doSomething input |> ifSomeDo doSomethingElse |> ifSomeDo doAThirdThing |> ifSomeDo (fun z -> Some z)

let ifSomeDo f opt = if opt.IsSome then f opt.Value else None

Page 96: Functional Programming Patterns (NDC London 2014)

MONADS

Page 97: Functional Programming Patterns (NDC London 2014)

A switch analogy

Some

None

Input ->

Page 98: Functional Programming Patterns (NDC London 2014)

Connecting switches

on Some

Bypass on None

Page 99: Functional Programming Patterns (NDC London 2014)

Connecting switches

Page 100: Functional Programming Patterns (NDC London 2014)

Connecting switches

Page 101: Functional Programming Patterns (NDC London 2014)

Composing switches

>> >>

Composing one-track functions is fine...

Page 102: Functional Programming Patterns (NDC London 2014)

Composing switches

>> >>

... and composing two-track functions is fine...

Page 103: Functional Programming Patterns (NDC London 2014)

Composing switches

... but composing switches is not allowed!

Page 104: Functional Programming Patterns (NDC London 2014)

How to combine the

mismatched functions?

Page 105: Functional Programming Patterns (NDC London 2014)

“Bind” is the answer!

Bind all the things!

Page 106: Functional Programming Patterns (NDC London 2014)

Composing switches

Two-track input Two-track input

One-track input Two-track input

Page 107: Functional Programming Patterns (NDC London 2014)

Building an adapter block

Two-track input

Slot for switch function

Two-track output

Page 108: Functional Programming Patterns (NDC London 2014)

Building an adapter block

Two-track input Two-track output

Page 109: Functional Programming Patterns (NDC London 2014)

let bind nextFunction optionInput = match optionInput with | Some s -> nextFunction s | None -> None

Building an adapter block

Two-track input Two-track output

Page 110: Functional Programming Patterns (NDC London 2014)

let bind nextFunction optionInput = match optionInput with | Some s -> nextFunction s | None -> None

Building an adapter block

Two-track input Two-track output

Page 111: Functional Programming Patterns (NDC London 2014)

let bind nextFunction optionInput = match optionInput with | Some s -> nextFunction s | None -> None

Building an adapter block

Two-track input Two-track output

Page 112: Functional Programming Patterns (NDC London 2014)

let bind nextFunction optionInput = match optionInput with | Some s -> nextFunction s | None -> None

Building an adapter block

Two-track input Two-track output

Page 113: Functional Programming Patterns (NDC London 2014)

Pattern:

Use bind to chain options

Page 114: Functional Programming Patterns (NDC London 2014)

Pyramid of doom: using bind

let bind f opt = match opt with | Some v -> f v | None -> None

let example input = let x = doSomething input if x.IsSome then let y = doSomethingElse (x.Value) if y.IsSome then let z = doAThirdThing (y.Value) if z.IsSome then let result = z.Value Some result else None else None else None

Page 115: Functional Programming Patterns (NDC London 2014)

let example input = doSomething input |> bind doSomethingElse |> bind doAThirdThing |> bind (fun z -> Some z)

Pyramid of doom: using bind

Let bind f opt = match opt with | Some v -> f v | None -> None

No pyramids! Code is linear and clear.

This pattern is called “monadic bind”

Page 116: Functional Programming Patterns (NDC London 2014)

Pattern:

Use bind to chain tasks

Page 117: Functional Programming Patterns (NDC London 2014)

Connecting tasks

When task completes Wait Wait

Page 118: Functional Programming Patterns (NDC London 2014)

Pyramid of doom: using bind for tasks

let taskBind f task = task.WhenFinished (fun taskResult -> f taskResult)

let taskExample input = let taskX = startTask input taskX.WhenFinished (fun x -> let taskY = startAnotherTask x taskY.WhenFinished (fun y -> let taskZ = startThirdTask y taskZ.WhenFinished (fun z -> z // final result

a.k.a “promise” “future”

Page 119: Functional Programming Patterns (NDC London 2014)

Pyramid of doom: using bind for tasks

let taskBind f task = task.WhenFinished (fun taskResult -> f taskResult)

let taskExample input = startTask input |> taskBind startAnotherTask |> taskBind startThirdTask |> taskBind (fun z -> z)

This pattern is also a “monadic bind”

Page 120: Functional Programming Patterns (NDC London 2014)

Pattern:

Use bind to chain error handlers

Page 121: Functional Programming Patterns (NDC London 2014)

Use case without error handling

string UpdateCustomerWithErrorHandling() { var request = receiveRequest(); validateRequest(request); canonicalizeEmail(request); db.updateDbFromRequest(request); smtpServer.sendEmail(request.Email) return "OK"; }

Page 122: Functional Programming Patterns (NDC London 2014)

Use case with error handling

string UpdateCustomerWithErrorHandling() { var request = receiveRequest(); var isValidated = validateRequest(request); if (!isValidated) { return "Request is not valid" } canonicalizeEmail(request); db.updateDbFromRequest(request); smtpServer.sendEmail(request.Email) return "OK"; }

Page 123: Functional Programming Patterns (NDC London 2014)

Use case with error handling

string UpdateCustomerWithErrorHandling() { var request = receiveRequest(); var isValidated = validateRequest(request); if (!isValidated) { return "Request is not valid" } canonicalizeEmail(request); var result = db.updateDbFromRequest(request); if (!result) { return "Customer record not found" } smtpServer.sendEmail(request.Email) return "OK"; }

Page 124: Functional Programming Patterns (NDC London 2014)

Use case with error handling

string UpdateCustomerWithErrorHandling() { var request = receiveRequest(); var isValidated = validateRequest(request); if (!isValidated) { return "Request is not valid" } canonicalizeEmail(request); try { var result = db.updateDbFromRequest(request); if (!result) { return "Customer record not found" } } catch { return "DB error: Customer record not updated" } smtpServer.sendEmail(request.Email) return "OK"; }

Page 125: Functional Programming Patterns (NDC London 2014)

Use case with error handling

string UpdateCustomerWithErrorHandling() { var request = receiveRequest(); var isValidated = validateRequest(request); if (!isValidated) { return "Request is not valid" } canonicalizeEmail(request); try { var result = db.updateDbFromRequest(request); if (!result) { return "Customer record not found" } } catch { return "DB error: Customer record not updated" } if (!smtpServer.sendEmail(request.Email)) { log.Error "Customer email not sent" } return "OK"; }

Page 126: Functional Programming Patterns (NDC London 2014)

Use case with error handling

string UpdateCustomerWithErrorHandling() { var request = receiveRequest(); var isValidated = validateRequest(request); if (!isValidated) { return "Request is not valid" } canonicalizeEmail(request); try { var result = db.updateDbFromRequest(request); if (!result) { return "Customer record not found" } } catch { return "DB error: Customer record not updated" } if (!smtpServer.sendEmail(request.Email)) { log.Error "Customer email not sent" } return "OK"; }

Page 127: Functional Programming Patterns (NDC London 2014)

A structure for managing errors

Request Success Validate

Failure

let validateInput input = if input.name = "" then Failure "Name must not be blank" else if input.email = "" then Failure "Email must not be blank" else Success input // happy path

Page 128: Functional Programming Patterns (NDC London 2014)

Connecting switches

Validate UpdateDb SendEmail

Page 129: Functional Programming Patterns (NDC London 2014)

Connecting switches

Validate UpdateDb SendEmail

Page 130: Functional Programming Patterns (NDC London 2014)

Functional flow without error handling

let updateCustomer = receiveRequest |> validateRequest |> canonicalizeEmail |> updateDbFromRequest |> sendEmail |> returnMessage

Before

One track

Page 131: Functional Programming Patterns (NDC London 2014)

let updateCustomerWithErrorHandling =

receiveRequest |> validateRequest |> canonicalizeEmail |> updateDbFromRequest |> sendEmail |> returnMessage

Functional flow with error handling

After

See my talk on Friday or

fsharpforfunandprofit.com/rop

Two track

Page 132: Functional Programming Patterns (NDC London 2014)

MAPS

Page 133: Functional Programming Patterns (NDC London 2014)

World of normal values

int string bool

World of options

int option string option bool option

Page 134: Functional Programming Patterns (NDC London 2014)

World of options

World of normal values

int string bool

int option string option bool option

Page 135: Functional Programming Patterns (NDC London 2014)

World of options

World of normal values

int option string option bool option

int string bool

Page 136: Functional Programming Patterns (NDC London 2014)

let add42ToOption opt = if opt.IsSome then let newVal = add42 opt.Value Some newVal else None

How not to code with options

Let’s say you have an int wrapped in an Option, and

you want to add 42 to it:

let add42 x = x + 42

Page 137: Functional Programming Patterns (NDC London 2014)

World of options

World of normal values

add42

Page 138: Functional Programming Patterns (NDC London 2014)

World of options

World of normal values

add42

Page 139: Functional Programming Patterns (NDC London 2014)

Lifting

World of options

World of normal values

T -> U

option<T> -> option<U>

Option.map

Page 140: Functional Programming Patterns (NDC London 2014)

World of options

World of normal values

add42

add42ToOption Some 1 |>

1 |> // 43

// Some 43

Page 141: Functional Programming Patterns (NDC London 2014)

The right way to code with options

Let’s say you have an int wrapped in an Option, and

you want to add 42 to it:

let add42 x = x + 42 1 |> add42 // 43

let add42ToOption = Option.map add42 Some 1 |> add42ToOption // Some 43

Page 142: Functional Programming Patterns (NDC London 2014)

The right way to code with options

Let’s say you have an int wrapped in an Option, and

you want to add 42 to it:

let add42 x = x + 42 1 |> add42 // 43

Some 1 |> Option.map add42 // Some 43

Page 143: Functional Programming Patterns (NDC London 2014)

Lifting to lists

World of lists

World of normal values

T -> U

List<T> -> List<U>

List.map

Page 144: Functional Programming Patterns (NDC London 2014)

The right way to code with wrapped types

[1;2;3] |> List.map add42 // [43;44;45]

Page 145: Functional Programming Patterns (NDC London 2014)

Lifting to async

World of async

World of normal values

T -> U

async<T> -> async<U>

Async.map

Page 146: Functional Programming Patterns (NDC London 2014)

Guideline:

Most wrapped generic types

have a “map”. Use it!

Page 147: Functional Programming Patterns (NDC London 2014)

Guideline:

If you create your own generic type,

create a “map” for it.

Page 148: Functional Programming Patterns (NDC London 2014)

MONOIDS

Page 149: Functional Programming Patterns (NDC London 2014)

Mathematics

Ahead

Page 150: Functional Programming Patterns (NDC London 2014)

Thinking like a mathematician

1 + 2 = 3

1 + (2 + 3) = (1 + 2) + 3

1 + 0 = 1

0 + 1 = 1

Page 151: Functional Programming Patterns (NDC London 2014)

1 + 2 = 3

Some things

A way of combining them

Page 152: Functional Programming Patterns (NDC London 2014)

2 x 3 = 6

Some things

A way of combining them

Page 153: Functional Programming Patterns (NDC London 2014)

"a" + "b" = "ab"

Some things

A way of combining them

Page 154: Functional Programming Patterns (NDC London 2014)

concat([a],[b]) = [a; b]

Some things

A way of combining them

Page 155: Functional Programming Patterns (NDC London 2014)

1 + 2

1 + 2 + 3

1 + 2 + 3 + 4

Is an integer

Is an integer

A pairwise operation has become an operation that

works on lists!

Page 156: Functional Programming Patterns (NDC London 2014)

1 + (2 + 3) = (1 + 2) + 3

Order of combining doesn’t matter

1 + 2 + 3 + 4

(1 + 2) + (3 + 4)

((1 + 2) + 3) + 4

All the same

Page 157: Functional Programming Patterns (NDC London 2014)

1 - (2 - 3) = (1 - 2) - 3

Order of combining does matter

Page 158: Functional Programming Patterns (NDC London 2014)

1 + 0 = 1

0 + 1 = 1

A special kind of thing that when you combine it with something, just

gives you back the original something

Page 159: Functional Programming Patterns (NDC London 2014)

42 * 1 = 42

1 * 42 = 42

A special kind of thing that when you combine it with something, just

gives you back the original something

Page 160: Functional Programming Patterns (NDC London 2014)

"" + "hello" = "hello"

"hello" + "" = "hello"

“Zero” for strings

Page 161: Functional Programming Patterns (NDC London 2014)

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.

• Rule 2 (Associativity): When combining more than

two things, which pairwise combination you do first

doesn't matter.

• Rule 3 (Identity element): There is a special thing

called "zero" such that when you combine any thing

with "zero" you get the original thing back.

A monoid!

Page 162: Functional Programming Patterns (NDC London 2014)

• Rule 1 (Closure): The result of combining two

things is always another one of the things.

• Benefit: converts pairwise operations into

operations that work on lists.

1 + 2 + 3 + 4 [ 1; 2; 3; 4 ] |> List.reduce (+)

Page 163: Functional Programming Patterns (NDC London 2014)

1 * 2 * 3 * 4 [ 1; 2; 3; 4 ] |> List.reduce (*)

• Rule 1 (Closure): The result of combining two

things is always another one of the things.

• Benefit: converts pairwise operations into

operations that work on lists.

Page 164: Functional Programming Patterns (NDC London 2014)

"a" + "b" + "c" + "d" [ "a"; "b"; "c"; "d" ] |> List.reduce (+)

• Rule 1 (Closure): The result of combining two

things is always another one of the things.

• Benefit: converts pairwise operations into

operations that work on lists.

Page 165: Functional Programming Patterns (NDC London 2014)

• Rule 2 (Associativity): When combining more

than two things, which pairwise combination

you do first doesn't matter.

• Benefit: Divide and conquer, parallelization, and

incremental accumulation.

1 + 2 + 3 + 4

Page 166: Functional Programming Patterns (NDC London 2014)

• Rule 2 (Associativity): When combining more

than two things, which pairwise combination

you do first doesn't matter.

• Benefit: Divide and conquer, parallelization, and

incremental accumulation.

(1 + 2) (3 + 4) 3 + 7

Page 167: Functional Programming Patterns (NDC London 2014)

• Rule 2 (Associativity): When combining more

than two things, which pairwise combination

you do first doesn't matter.

• Benefit: Divide and conquer, parallelization, and

incremental accumulation.

(1 + 2 + 3)

Page 168: Functional Programming Patterns (NDC London 2014)

• Rule 2 (Associativity): When combining more

than two things, which pairwise combination

you do first doesn't matter.

• Benefit: Divide and conquer, parallelization, and

incremental accumulation.

(1 + 2 + 3) + 4

Page 169: Functional Programming Patterns (NDC London 2014)

• Rule 2 (Associativity): When combining more

than two things, which pairwise combination

you do first doesn't matter.

• Benefit: Divide and conquer, parallelization, and

incremental accumulation.

(6) + 4

Page 170: Functional Programming Patterns (NDC London 2014)

Issues with reduce

• How can I use reduce on an empty list?

• In a divide and conquer algorithm, what should I

do if one of the "divide" steps has nothing in it?

• When using an incremental algorithm, what

value should I start with when I have no data?

Page 171: Functional Programming Patterns (NDC London 2014)

• Rule 3 (Identity element): There is a special

thing called "zero" such that when you combine

any thing with "zero" you get the original thing

back.

• Benefit: Initial value for empty or missing data

Page 172: Functional Programming Patterns (NDC London 2014)

Pattern: Simplifying aggregation code with monoids

Page 173: Functional Programming Patterns (NDC London 2014)

type OrderLine = {Qty:int; Total:float}

let orderLines = [

{Qty=2; Total=19.98}

{Qty=1; Total= 1.99}

{Qty=3; Total= 3.99} ]

let addPair line1 line2 =

let newQty = line1.Qty + line2.Qty

let newTotal = line1.Total + line2.Total

{Qty=newQty; Total=newTotal}

orderLines |> List.reduce addPair

// {Qty=6; Total= 25.96}

Any combination of monoids is also a monoid

Write a pairwise combiner

Profit!

Page 174: Functional Programming Patterns (NDC London 2014)

Pattern: Convert non-monoids to monoids

Page 175: Functional Programming Patterns (NDC London 2014)

Customer

+

Customer

+

Customer

Customer Stats

+

Customer Stats

+

Customer Stats

Reduce

Map

Not a monoid A monoid

Customer Stats

(Total)

Page 176: Functional Programming Patterns (NDC London 2014)

Hadoop make me a sandwich

https://twitter.com/daviottenheimer

/status/532661754820829185

Page 177: Functional Programming Patterns (NDC London 2014)

Guideline:

Convert expensive monoids

to cheap monoids

Page 178: Functional Programming Patterns (NDC London 2014)

Log file (Mon)

+

Log File (Tue)

+

Log File (Wed)

=

Really big file

Summary (Mon)

+

Summary (Tue)

+

Summary (Wed)

Map

Strings are monoids A monoid

Much more efficient for incremental updates

“Monoid homomorphism”

Page 179: Functional Programming Patterns (NDC London 2014)

Pattern: Seeing monoids everywhere

Page 180: Functional Programming Patterns (NDC London 2014)

Monoids in the real world

Metrics guideline:

Use counters rather than rates

Alternative metrics guideline:

Make sure your metrics are monoids • incremental updates

• can handle missing data

Page 181: Functional Programming Patterns (NDC London 2014)

Is function composition a monoid?

>> Function 1

apple -> banana Function 2

banana -> cherry

New Function

apple -> cherry

Not the same thing.

Not a monoid

Page 182: Functional Programming Patterns (NDC London 2014)

Is function composition a monoid?

>> Function 1

apple -> apple

Same thing

Function 2

apple -> apple

Function 3

apple -> apple

A monoid!

Page 183: Functional Programming Patterns (NDC London 2014)

Is function composition a monoid?

“Functions with same type of input and output”

Functions where the input and output are the same type are

monoids! What shall we call these kinds of functions?

Page 184: Functional Programming Patterns (NDC London 2014)

Is function composition a monoid?

“Functions with same type of input and output”

“Endomorphisms”

Functions where the input and output are the same type are

monoids! What shall we call these kinds of functions?

All endomorphisms are monoids

Page 185: Functional Programming Patterns (NDC London 2014)

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

Endomorphisms

Another endomorphism!

Page 186: Functional Programming Patterns (NDC London 2014)

Event sourcing

Any function containing an endomorphism can be converted into a monoid!

Event application function:

Is an endomorphism

Event -> State -> State

Page 187: Functional Programming Patterns (NDC London 2014)

apply event1 // State -> State

apply event2 // State -> State

apply event3 // State -> State

Reduce

applyAllEventsAtOnce // State -> State

Endomorphism

Another endomorphism!

Partial application of event

• incremental updates

• can handle missing events

Page 188: Functional Programming Patterns (NDC London 2014)

Monads vs. monoids?

Page 189: Functional Programming Patterns (NDC London 2014)

Series combination

= +

Result is same kind of thing

(Closure)

Page 190: Functional Programming Patterns (NDC London 2014)

Series combination

+

Order not important

(Associative) Monoid!

+ ( )

+ + ( )

Page 191: Functional Programming Patterns (NDC London 2014)

Parallel combination

= + Same thing (Closure)

Order not important

(Associative)

Monoid!

Page 192: Functional Programming Patterns (NDC London 2014)

Monad laws

• The Monad laws are just the monoid

definitions in diguise

– Closure, Associativity, Identity

• What happens if you break the monad laws?

– You go to jail

– You lose monoid benefits such as aggregation

Page 193: Functional Programming Patterns (NDC London 2014)

A monad is just a monoid in

the category of endofunctors!

Page 194: Functional Programming Patterns (NDC London 2014)

A monad is just a monoid in

the category of endofunctors!

Page 195: Functional Programming Patterns (NDC London 2014)

THANKS!