of 249

Functional Programming Patterns (BuildStuff '14)

by scott-wlaschin

on

Comment: 0

103,629

views

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

• 1. Functional Design Patterns@ScottWlaschinfsharpforfunandprofit.com
• 2. Functional Design Patterns@ScottWlaschinfsharpforfunandprofit.comX
• 3. Functional Design Patterns@ScottWlaschinfsharpforfunandprofit.com
• 4. Functional patterns&#x2022;Apomorphisms&#x2022;Dynamorphisms&#x2022;Chronomorphisms&#x2022;Zygohistomorphic prepromorphisms
• 5. Functional patterns&#x2022;Core Principles of FP design&#x2013;Functions, types, composition&#x2022;Functions as parameters&#x2013;Abstraction, Dependency injection&#x2013;Partial application, Continuations, Folds,&#x2022;Chaining functions&#x2013;Error handling, Async&#x2013;Monads&#x2022;Dealing with wrapped data&#x2013;Lifting, Functors&#x2013;Validation with Applicatives&#x2022;Aggregating data and operations&#x2013;Monoids
• 6. This talkA whirlwind tour of many sightsDon't worry if you don't understand everything
• 7. Functional programming is scary
• 8. Functional programming is scary
• 9. Object oriented programming is scary
• 10. &#x2022;Single Responsibility Principle&#x2022;Open/Closed principle&#x2022;Dependency Inversion Principle&#x2022;Interface Segregation Principle&#x2022;Factory pattern&#x2022;Strategy pattern&#x2022;Decorator pattern&#x2022;Visitor pattern&#x2022;Functions&#x2022;Functions&#x2022;Functions, also&#x2022;Functions&#x2022;Yes, functions&#x2022;Oh my, functions again!&#x2022;Functions&#x2022;Functions &#xF04A;OO pattern/principleFP pattern/principleSeriously, FP patterns are different
• 11. A short and mostly wrong history of programming paradigms
• 12. &#x2022;What we envisioned:&#x2013;Smalltalk&#x2022;What we got:&#x2013;C++&#x2013;Object oriented Cobol&#x2013;PHP&#x2013;Java&#x201C;Worse is better&#x201D;Object-Oriented programming
• 13. &#x2022;What we envisioned:&#x2013;Haskell, OCaml, F#, etc&#x2022;What we got:&#x2013;??Functional programmingPlease don&#x2019;t let this happen to FP!
• 14. CORE PRINCIPLES OF FP DESIGNImportant to understand!
• 15. Core principles of FP designSteal from mathematicsTypes are not classesFunctions are thingsComposition everywhereFunction
• 16. Core principle: Steal from mathematics&#x201C;Programming is one of the most difficult branches of applied mathematics&#x201D; - E. W. Dijkstra
• 17. Why mathematicsDijkstra said:&#x2022;Mathematical assertions tend to be unusually precise.&#x2022;Mathematical assertions tend to be general. They are applicable to a large (often infinite) class of instances.&#x2022;Mathematics embodies a discipline of reasoning allowing assertions to be made with an unusually high confidence level.
• 18. &#x201C;Object-oriented programming is an exceptionally bad idea which could only have originated in California.&#x201D;E. W. Dijkstra
• 19. Mathematical functionsFunction add1(x) input x maps to x+1&#x2026;-10123&#x2026;Domain (int)Codomain (int)&#x2026;01234&#x2026;let add1 x = x + 1val add1 : int -&gt; int
• 20. Function add1(x) input x maps to x+1&#x2026;-10123&#x2026;Domain (int)Codomain (int)&#x2026;01234&#x2026;Mathematical functionsint add1(int input){switch (input){case 0: return 1;case 1: return 2;case 2: return 3;case 3: return 4;etc ad infinitum}}&#x2022;Input and output values already exist&#x2022;A function is not a calculation, just a mapping&#x2022;Input and output values are unchanged (immutable)
• 21. Mathematical functions&#x2022;A (mathematical) function always gives the same output value for a given input value&#x2022;A (mathematical) function has no side effectsFunction add1(x) input x maps to x+1&#x2026;-10123&#x2026;Domain (int)Codomain (int)&#x2026;01234&#x2026;
• 22. Functions don't have to be about arithmeticFunction CustomerName(x) input Customer maps to PersonalName&#x2026;Cust1 Cust2 Cust3 Cust3 &#x2026;Customer (domain)Name (codomain)&#x2026;Alice BobSue JohnPam&#x2026;Name CustomerName(Customer input){switch (input){ case Cust1: return &#x201C;Alice&#x201D;;case Cust2: return &#x201C;Bob&#x201D;;case Cust3: return &#x201C;Sue&#x201D;;etc}}
• 23. Functions can work on functionsFunction List.map int-&gt;int maps to int list-&gt;int list&#x2026;add1 times2 subtract3 add42 &#x2026;int-&gt;intint list -&gt; int list&#x2026;eachAdd1 eachTimes2eachSubtract3 eachAdd42&#x2026;
• 24. Guideline: Strive for purity
• 25. The practical benefits of pure functionscustomer.SetName(newName);let newCustomer = setCustomerName(aCustomer, newName)Pure functions are easy to reason aboutvar name = customer.GetName();let name,newCustomer = getCustomerName(aCustomer)Reasoning about code that might not be pure:Reasoning about code that is pure:The customer is being changed.
• 26. The practical benefits of pure functionslet x = doSomething()let y = doSomethingElse(x)return y + 1Pure functions are easy to refactor
• 27. The practical benefits of pure functionsPure functions are easy to refactorlet x = doSomething()let y = doSomethingElse(x)return y + 1
• 28. The practical benefits of pure functionsPure functions are easy to refactorlet helper() = let x = doSomething()let y = doSomethingElse(x)return yreturn helper() + 1
• 29. More practical benefits of pure functions&#x2022;Laziness&#x2013;only evaluate when I need the output&#x2022;Cacheable results&#x2013;same answer every time&#x2013;"memoization"&#x2022;No order dependencies&#x2013;I can evaluate them in any order I like&#x2022;Parallelizable&#x2013;"embarrassingly parallel"
• 30. How to design a pure functionPure Awesomeness!
• 31. How to design a pure function&#x2022;Haskell&#x2013;very pure&#x2022;F#, OCaml, Clojure, Scala&#x2013;easy to be pure&#x2022;C#, Java, JavaScript&#x2013;have to make an effort
• 32. Core principle: Types are not classes
• 33. X
• 34. Types are not classesSet of valid inputsSet of valid outputsSo what is a type?
• 35. Types separate data from behaviorListsListsList.mapList.collectList.filterList.etc
• 36. Core principle: Functions are thingsFunction
• 37. Functions as thingsThe Tunnel of TransformationFunction apple -&gt; bananaA function is a standalone thing, not attached to a class
• 38. Functions as thingslet z = 1int-&gt; int-&gt;int1let add x y = x + y
• 39. Functions as inputs and outputslet useFn f = (f 1) + 2let add x = (fun y -&gt; x + y)int-&gt;(int-&gt;int)int -&gt;intintintint-&gt;intlet transformInt f x = (f x) + 1int-&gt;intintintFunction as outputFunction as inputFunction as parameter(int-&gt;int)-&gt;intint-&gt;int
• 40. Core principle: Composition everywhere
• 41. Function compositionFunction 1 apple -&gt; bananaFunction 2 banana -&gt; cherry
• 42. Function composition&gt;&gt;Function 1 apple -&gt; bananaFunction 2 banana -&gt; cherry
• 43. Function compositionNew Function apple -&gt; cherryCan't tell it was built from smaller functions!
• 44. Types can be composed too&#x201C;algebraic types"
• 45. Product types&#xD7;=Alice, Jan 12thBob, Feb 2ndCarol, Mar 3rdSet of peopleSet of datestype Birthday = Person * Date
• 46. Sum typesSet of Cash valuesSet of Cheque valuesSet of CreditCard values++type PaymentMethod =| Cash| Cheque of ChequeNumber| Card of CardType * CardNumber
• 47. DDD &amp; DOMAIN MODELLING
• 48. Domain modelling pattern: Use types to represent constraints
• 49. Types represent constraints on input and outputtype Suit = Club | Diamond | Spade | Hearttype String50 = // non-null, not more than 50 charstype EmailAddress = // non-null, must contain &#x2018;@&#x2019;type StringTransformer = string -&gt; stringtype GetCustomer = CustomerId -&gt; Customer optionInstant mockability
• 50. Domain modelling pattern: Types are cheap
• 51. Types are cheaptype Suit = Club | Diamond | Spade | Hearttype Rank = Two | Three | Four | Five | Six | Seven | Eight| Nine | Ten | Jack | Queen | King | Acetype Card = Suit * Ranktype Hand = Card listOnly one line of code to create a type!
• 52. Design principle: Strive for totality
• 53. twelveDividedBy(x) input x maps to 12/x&#x2026;3210&#x2026;Domain (int)Codomain (int)&#x2026;4612&#x2026;Totalityint TwelveDividedBy(int input){switch (input){case 3: return 4;case 2: return 6;case 1: return 12;case 0: return ??;}}What happens here?
• 54. twelveDividedBy(x) input x maps to 12/x&#x2026;321-1&#x2026;NonZeroIntegerint&#x2026;4612&#x2026;Totalityint TwelveDividedBy(int input){switch (input){case 3: return 4;case 2: return 6;case 1: return 12;case -1: return -12;}}Constrain the input0 is doesn&#x2019;t have to be handledNonZeroInteger -&gt; int0 is missingTypes are documentation
• 55. twelveDividedBy(x) input x maps to 12/x&#x2026;3210-1&#x2026;intOption&lt;Int&gt;&#x2026;Some 4Some 6Some 12None &#x2026;Totalityint 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 output0 is valid inputint -&gt; int optionTypes are documentation
• 56. Design principle: Use types to indicate errors
• 57. Output types as error codesLoadCustomer: CustomerId -&gt; CustomerLoadCustomer: CustomerId -&gt; SuccessOrFailure&lt;Customer&gt;ParseInt: string -&gt; intParseInt: string -&gt; int optionFetchPage: Uri -&gt; StringFetchPage: Uri -&gt; SuccessOrFailure&lt;String&gt;No nullsNo exceptionsUse the signature, Luke!
• 58. Domain modelling principle: &#x201C;Make illegal states unrepresentable&#x201D;
• 59. Types can represent business rulestype EmailContactInfo =| Unverified of EmailAddress| Verified of VerifiedEmailAddresstype ContactInfo =| EmailOnly of EmailContactInfo| AddrOnly of PostalContactInfo| EmailAndAddr of EmailContactInfo * PostalContactInfo
• 60. Domain modelling principle: Use sum-types instead of inheritance
• 61. Using sum vs. inheritanceinterface IPaymentMethod {..}class Cash : IPaymentMethod {..}class Cheque : IPaymentMethod {..}class Card : IPaymentMethod {..}type PaymentMethod =| Cash| Cheque of ChequeNumber| Card of CardType * CardNumberclass Evil : IPaymentMethod {..}Definition is scattered around many locationsWhat goes in here? What is the common behaviour?OO version:
• 62. Domain modelling principle: Use sum-types for state machines
• 63. type ShoppingCart =| EmptyCartState| ActiveCartState of ActiveCartData| PaidCartState of PaidCartDataEmpty CartActive CartPaid CartAdd ItemRemove ItemPayAdd ItemRemove Item
• 64. Domain modelling principle: It&#x2019;s ok to expose public data
• 65. It&#x2019;s ok to expose public datatype PersonalName = {FirstName: String50MiddleInitial: String1 optionLastName: String50 }ImmutableCan&#x2019;t create invalid values
• 66. Domain modelling principle: Types are executable documentation
• 67. Types are executable documentationtype Suit = Club | Diamond | Spade | Hearttype Rank = Two | Three | Four | Five | Six | Seven | Eight| Nine | Ten | Jack | Queen | King | Acetype Card = Suit * Ranktype Hand = Card listtype Deck = Card listtype Player = {Name:string; Hand:Hand}type Game = {Deck:Deck; Players: Player list}type Deal = Deck &#x2013;&#x203A; (Deck * Card)type PickupCard = (Hand * Card) &#x2013;&#x203A; Hand
• 68. Types are executable documentationtype CardType = Visa | Mastercardtype CardNumber = CardNumber of stringtype ChequeNumber = ChequeNumber of inttype PaymentMethod =| Cash| Cheque of ChequeNumber| Card of CardType * CardNumber
• 69. More on DDD and designing with types at fsharpforfunandprofit.com/dddStatic types only! Sorry Clojure and JS developers &#xF04C;
• 70. HIGH-LEVEL DESIGN
• 71. Design paradigm: Functions all the way down
• 72. &#x201C;Functions in the small, objects in the large&#x201D;
• 74. Design paradigm: Transformation-oriented programming
• 75. Interacting with the outside worldtype ContactDTO = {FirstName: stringMiddleInitial: stringLastName: stringEmailAddress: stringIsEmailVerified: bool}type EmailAddress = ...type VerifiedEmail =VerifiedEmail of EmailAddresstype EmailContactInfo =| Unverified of EmailAddress| Verified of VerifiedEmailtype PersonalName = {FirstName: String50MiddleInitial: String1 optionLastName: String50 }type Contact = {Name: PersonalNameEmail: EmailContactInfo }
• 76. Transformation oriented programmingInputFunctionOutput
• 77. Transformation oriented programmingInputTransformation to internal modelInternal ModelOutputTransformation from internal modelvalidation and wrapping happens hereunwrapping happens here
• 78. Outbound tranformationFlow of control in a FP use caseInbound tranformationCustomerDomainValidatorUpdateRequest DTODomain TypeSendToDTOResponse DTOWorks well with domain events, FRP, etc
• 79. Flow of control in a OO use caseApplication ServicesCustomerDomainApp ServiceApp ServiceValidationValueEntityInfrastructureEntityCustomer Repo.ValueEmail MessageValueDatabase ServiceSMTP Service
• 80. Interacting with the outside worldNasty, unclean outside worldNasty, unclean outside worldNasty, unclean outside worldBeautiful, clean, internal modelGate with filtersGate with filtersGate with filters
• 81. Interacting with the other domainsSubdomain/ bounded contextGate with filtersSubdomain/ bounded contextGate with filters
• 82. Interacting with the other domainsBounded contextBounded contextBounded context
• 83. FUNCTIONS AS PARAMETERS
• 84. Guideline: Parameterize all the things
• 85. Parameterize all the thingslet printList() =for i in [1..10] doprintfn "the number is %i" i
• 86. Parameterize all the thingsIt's second nature to parameterize the data input:let printList aList =for i in aList doprintfn "the number is %i" i
• 87. Parameterize all the thingslet printList anAction aList =for i in aList doanAction iFPers would parameterize the action as well:We've decoupled the behavior from the data
• 88. Parameterize all the thingspublic static int Product(int n){int product = 1;for (int i = 1; i &lt;= n; i++){product *= i;}return product;}public static int Sum(int n){int sum = 0;for (int i = 1; i &lt;= n; i++){sum += i;}return sum;}
• 89. public static int Product(int n){int product = 1;for (int i = 1; i &lt;= n; i++){product *= i;}return product;}public static int Sum(int n){int sum = 0;for (int i = 1; i &lt;= n; i++){sum += i;}return sum;}Parameterize all the things
• 90. Parameterize all the thingslet product n =let initialValue = 1let action productSoFar x = productSoFar * x[1..n] |&gt; List.fold action initialValuelet sum n =let initialValue = 0let action sumSoFar x = sumSoFar+x[1..n] |&gt; List.fold action initialValueLots of collection functions like this: "fold", "map", "reduce", "collect", etc.
• 91. Guideline: Be as generic as possible
• 92. Generic codelet printList anAction aList =for i in aList doanAction i// val printList :// ('a -&gt; unit) -&gt; seq&lt;'a&gt; -&gt; unitAny kind of collection, any kind of action!F# and other functional languages make code generic automatically
• 93. Generic codeint -&gt; intHow many ways are there to implement this function?'a -&gt; 'aHow many ways are there to this function?
• 94. Generic codeint list -&gt; int listHow many ways are there to implement this function?'a list -&gt; 'a listHow many ways are there to this function?
• 95. Generic code('a -&gt; 'b) -&gt; 'a list -&gt; 'b listHow many ways are there to implement this function?
• 96. Tip: Function types are "interfaces"
• 97. Function types are interfacesinterface 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!
• 98. Function types are interfacesinterface IBunchOfStuff{int DoSomething(int x);}An interface with one method is a just a function typetype IBunchOfStuff: int -&gt; intAny function with that type is compatible with itlet add2 x = x + 2 // int -&gt; intlet times3 x = x * 3 // int -&gt; int
• 99. Strategy pattern is trivial in FPclass MyClass{public MyClass(IBunchOfStuff strategy) {..}int DoSomethingWithStuff(int x) {return _strategy.DoSomething(x)}}Object-oriented strategy pattern:Functional equivalent:let DoSomethingWithStuff strategy x =strategy x
• 100. Decorator pattern in FPFunctional equivalent of decorator patternlet add1 x = x + 1 // int -&gt; int
• 101. Decorator pattern in FPFunctional equivalent of decorator patternlet add1 x = x + 1 // int -&gt; intlet logged f x =printfn "input is %A" xlet result = f xprintfn "output is %A" resultresult
• 102. Decorator pattern in FPFunctional equivalent of decorator patternlet add1 x = x + 1 // int -&gt; intlet logged f x =printfn "input is %A" xlet result = f xprintfn "output is %A" resultresultlet add1Decorated = // int -&gt; intlogged add1[1..5] |&gt; List.map add1[1..5] |&gt; List.map add1Decorated
• 103. Tip Every function is a one parameter function
• 104. Writing functions in different wayslet add x y = x + ylet add = (fun x y -&gt; x + y)let add x = (fun y -&gt; x + y)int-&gt; int-&gt;intint-&gt; int-&gt;intint-&gt; (int-&gt;int)
• 105. let three = 1 + 2let add1 = (+) 1let three = (+) 1 2let add1ToEach = List.map add1
• 106. Pattern: Partial application
• 107. let names = ["Alice"; "Bob"; "Scott"]Names |&gt; List.iter hellolet name = "Scott"printfn "Hello, my name is %s" namelet name = "Scott"(printfn "Hello, my name is %s") namelet name = "Scott"let hello = (printfn "Hello, my name is %s")hello name
• 108. Pattern: Use partial application to do dependency injection
• 109. type GetCustomer = CustomerId -&gt; Customerlet getCustomerFromDatabase connection(customerId:CustomerId) =// from connection // select customer // where customerId = customerIdtype of getCustomerFromDatabase =DbConnection -&gt; CustomerId -&gt; Customerlet getCustomer1 = getCustomerFromDatabase myConnection// getCustomer1 : CustomerId -&gt; CustomerPersistence agnostic
• 110. type GetCustomer = CustomerId -&gt; Customerlet getCustomerFromMemory map (customerId:CustomerId) =map |&gt; Map.find customerIdtype of getCustomerFromMemory =Map&lt;Id,Customer&gt; -&gt; CustomerId -&gt; Customerlet getCustomer2 = getCustomerFromMemory inMemoryMap// getCustomer2 : CustomerId -&gt; Customer
• 111. Pattern: The Hollywood principle: continuations
• 112. Continuationsint 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
• 113. Continuationsvoid Divide(int top, int bottom,Action ifZero, Action&lt;int&gt; ifSuccess){if (bottom == 0){ifZero();}else{ifSuccess( top/bottom );}}Let the caller decide what happenswhat happens next?
• 114. Continuationslet divide ifZero ifSuccess top bottom =if (bottom=0)then ifZero()else ifSuccess (top/bottom)F# versionFour parameters is a lot though!
• 115. Continuationslet divide ifZero ifSuccess top bottom =if (bottom=0)then ifZero()else ifSuccess (top/bottom)let ifZero1 () = printfn "bad"let ifSuccess1 x = printfn "good %i" xlet divide1 = divide ifZero1 ifSuccess1//testlet good1 = divide1 6 3let bad1 = divide1 6 0setup the functions to print a messagePartially apply the continuationsUse it like a normal function &#x2013; only two parameters
• 116. Continuationslet divide ifZero ifSuccess top bottom =if (bottom=0)then ifZero()else ifSuccess (top/bottom)let ifZero2() = Nonelet ifSuccess2 x = Some xlet divide2 = divide ifZero2 ifSuccess2//testlet good2 = divide2 6 3let bad2 = divide2 6 0setup the functions to return an OptionUse it like a normal function &#x2013; only two parametersPartially apply the continuations
• 117. Continuationslet divide ifZero ifSuccess top bottom =if (bottom=0)then ifZero()else ifSuccess (top/bottom)let ifZero3() = failwith "div by 0"let ifSuccess3 x = xlet divide3 = divide ifZero3 ifSuccess3//testlet good3 = divide3 6 3let bad3 = divide3 6 0setup the functions to throw an exceptionUse it like a normal function &#x2013; only two parametersPartially apply the continuations
• 119. Pyramid of doom: null testing examplelet example input =let x = doSomething inputif x &lt;&gt; null thenlet y = doSomethingElse xif y &lt;&gt; null thenlet z = doAThirdThing yif z &lt;&gt; null thenlet result = zresultelsenullelsenullelsenullI know you could do early returns, but bear with me...
• 121. Pyramid of doom: null examplelet example input =let x = doSomething inputif x &lt;&gt; null thenlet y = doSomethingElse xif y &lt;&gt; null thenlet z = doAThirdThing yif z &lt;&gt; null thenlet result = zresultelsenullelsenullelsenullNulls are a code smell: replace with Option!
• 122. Pyramid of doom: option examplelet example input =let x = doSomething inputif x.IsSome thenlet y = doSomethingElse (x.Value)if y.IsSome thenlet z = doAThirdThing (y.Value)if z.IsSome thenlet result = z.ValueSome resultelseNoneelseNoneelseNoneMuch more elegant, yes?No! This is fugly!Let&#x2019;s do a cut &amp; paste refactoring
• 123. Pyramid of doom: option examplelet example input =let x = doSomething inputif x.IsSome thenlet y = doSomethingElse (x.Value)if y.IsSome thenlet z = doAThirdThing (y.Value)if z.IsSome thenlet result = z.ValueSome resultelseNoneelseNoneelseNone
• 124. Pyramid of doom: option examplelet doWithX x =let y = doSomethingElse xif y.IsSome thenlet z = doAThirdThing (y.Value)if z.IsSome thenlet result = z.ValueSome resultelseNoneelseNonelet example input =let x = doSomething inputif x.IsSome thendoWithX xelseNone
• 125. Pyramid of doom: option examplelet doWithX x =let y = doSomethingElse xif y.IsSome thenlet z = doAThirdThing (y.Value)if z.IsSome thenlet result = z.ValueSome resultelseNoneelseNonelet example input =let x = doSomething inputif x.IsSome thendoWithX xelseNone
• 126. Pyramid of doom: option examplelet doWithY y =let z = doAThirdThing yif z.IsSome thenlet result = z.ValueSome resultelseNonelet doWithX x =let y = doSomethingElse xif y.IsSome thendoWithY yelseNonelet example input =let x = doSomething inputif x.IsSome thendoWithX xelseNone
• 127. Pyramid of doom: option example refactoredlet doWithZ z =let result = zSome resultlet doWithY y =let z = doAThirdThing yif z.IsSome thendoWithZ z.ValueelseNonelet doWithX x =let y = doSomethingElse xif y.IsSome thendoWithY y.ValueelseNonelet optionExample input =let x = doSomething inputif x.IsSome thendoWithX x.ValueelseNoneThree small pyramids instead of one big one!This is still ugly!But the code has a pattern...
• 128. Pyramid of doom: option example refactoredlet doWithZ z =let result = zSome resultlet doWithY y =let z = doAThirdThing yif z.IsSome thendoWithZ z.ValueelseNonelet doWithX x =let y = doSomethingElse xif y.IsSome thendoWithY y.ValueelseNonelet optionExample input =let x = doSomething inputif x.IsSome thendoWithX x.ValueelseNoneBut the code has a pattern...
• 129. let doWithY y =let z = doAThirdThing yif z.IsSome thendoWithZ z.ValueelseNone
• 130. let doWithY y =let z = doAThirdThing yz |&gt; ifSomeDo doWithZlet ifSomeDo f x =if x.IsSome thenf x.ValueelseNone
• 131. let doWithY y =y|&gt; doAThirdThing|&gt; ifSomeDo doWithZlet ifSomeDo f x =if x.IsSome thenf x.ValueelseNone
• 132. let example input =doSomething input|&gt; ifSomeDo doSomethingElse|&gt; ifSomeDo doAThirdThing|&gt; ifSomeDo (fun z -&gt; Some z)let ifSomeDo f x =if x.IsSome thenf x.ValueelseNone
• 133. A switch analogySomeNoneInput -&gt;
• 134. Connecting switcheson SomeBypass on None
• 135. Connecting switches
• 136. Connecting switches
• 137. Composing switches&gt;&gt;&gt;&gt;Composing one-track functions is fine...
• 138. Composing switches&gt;&gt;&gt;&gt;... and composing two-track functions is fine...
• 139. Composing switches&#xF0FB;&#xF0FB;... but composing switches is not allowed!
• 140. Composing switchesTwo-track inputTwo-track inputOne-track inputTwo-track input&#xF0FC;&#xF0FB;
• 141. Building an adapter blockTwo-track inputSlot for switch functionTwo-track output
• 142. Building an adapter blockTwo-track inputTwo-track output
• 143. let bind nextFunction optionInput =match optionInput with| Some s -&gt; nextFunction s| None -&gt; NoneBuilding an adapter blockTwo-track inputTwo-track output
• 144. let bind nextFunction optionInput =match optionInput with| Some s -&gt; nextFunction s| None -&gt; NoneBuilding an adapter blockTwo-track inputTwo-track output
• 145. let bind nextFunction optionInput =match optionInput with| Some s -&gt; nextFunction s| None -&gt; NoneBuilding an adapter blockTwo-track inputTwo-track output
• 146. let bind nextFunction optionInput =match optionInput with| Some s -&gt; nextFunction s| None -&gt; NoneBuilding an adapter blockTwo-track inputTwo-track output
• 147. Pattern: Use bind to chain options
• 148. Pyramid of doom: using bindlet bind f opt =match opt with| Some v -&gt; f v| None -&gt; Nonelet example input =let x = doSomething inputif x.IsSome thenlet y = doSomethingElse (x.Value)if y.IsSome thenlet z = doAThirdThing (y.Value)if z.IsSome thenlet result = z.ValueSome resultelseNoneelseNoneelseNone
• 149. let example input =doSomething input|&gt; bind doSomethingElse|&gt; bind doAThirdThing|&gt; bind (fun z -&gt; Some z)Pyramid of doom: using bindlet bind f opt =match opt with| Some v -&gt; f v| None -&gt; NoneNo pyramids!Code is linear and clear.This pattern is called &#x201C;monadic bind&#x201D;
• 150. Pattern: Use bind to chain tasks
• 153. Computation expressionslet example input =maybe {let! x = doSomething inputlet! y = doSomethingElse xlet! z = doAThirdThing yreturn z}let taskExample input =task { let! x = startTask inputlet! y = startAnotherTask xlet! z = startThirdTask zreturn z}Computation expressionComputation expression
• 154. Pattern: Use bind to chain error handlers
• 155. Example use caseName is blank Email not validReceive requestValidate and canonicalize requestUpdate existing user recordSend verification emailReturn result to userUser not found Db errorAuthorization error Timeout"As a user I want to update my name and email address"type Request = { userId: int; name: string; email: string }- and see sensible error messages when something goes wrong!
• 156. Use case without error handlingstring UpdateCustomerWithErrorHandling(){var request = receiveRequest();validateRequest(request);canonicalizeEmail(request);db.updateDbFromRequest(request);smtpServer.sendEmail(request.Email)return "OK";}
• 157. Use case with error handlingstring 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";}
• 158. Use case with error handlingstring 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";}
• 159. Use case with error handlingstring 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";}
• 160. Use case with error handlingstring 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";}
• 161. Use case with error handlingstring 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";}
• 162. A structure for managing errorsRequestSuccessValidateFailurelet validateInput input =if input.name = "" thenFailure "Name must not be blank"else if input.email = "" thenFailure "Email must not be blank"elseSuccess input // happy pathtype TwoTrack&lt;'TEntity&gt; =| Success of 'TEntity| Failure of string
• 163. name50Bind examplelet nameNotBlank input =if input.name = "" thenFailure "Name must not be blank"else Success inputlet name50 input =if input.name.Length &gt; 50 thenFailure "Name must not be longer than 50 chars"else Success inputlet emailNotBlank input =if input.email = "" thenFailure "Email must not be blank"else Success inputnameNotBlankemailNotBlank
• 164. Switches againSuccess!FailureInput -&gt;
• 165. Connecting switchesValidateUpdateDbon successbypass
• 166. Connecting switchesValidateUpdateDb
• 167. Connecting switchesValidateUpdateDbSendEmail
• 168. Connecting switchesValidateUpdateDbSendEmail
• 169. Functional flow without error handlinglet updateCustomer =receiveRequest|&gt; validateRequest|&gt; canonicalizeEmail|&gt; updateDbFromRequest|&gt; sendEmail|&gt; returnMessageBeforeOne track
• 170. Functional flow with error handlinglet updateCustomerWithErrorHandling =receiveRequest|&gt; validateRequest|&gt; canonicalizeEmail|&gt; updateDbFromRequest|&gt; sendEmail|&gt; returnMessageAfterSee fsharpforfunandprofit.com/ropTwo track
• 171. MAPS AND APPLICATIVES
• 172. World of normal valuesint string boolWorld of optionsint option string option bool option
• 173. World of optionsWorld of normal valuesint string boolint option string option bool option&#xF0FB;
• 174. World of optionsWorld of normal valuesint string boolint option string option bool option&#xF0FC;
• 175. How not to code with optionsLet&#x2019;s say you have an int wrapped in an Option, and you want to add 42 to it:let add42 x = x + 42let add42ToOption opt =if opt.IsSome thenlet newVal = add42 opt.ValueSome newValelseNone&#xF0FB;
• 176. World of optionsWorld of normal valuesadd42
• 177. World of optionsWorld of normal valuesadd42
• 178. LiftingWorld of optionsWorld of normal values'a -&gt; 'b'a option -&gt; 'b optionOption.map
• 179. The right way to code with optionsLet&#x2019;s say you have an int wrapped in an Option, and you want to add 42 to it:let add42 x = x + 42let add42ToOption = Option.map add42Some 1 |&gt; add42ToOptionSome 1 |&gt; Option.map add42&#xF0FC;
• 180. Pattern: Use &#x201C;map&#x201D; to lift functions
• 181. Lifting to listsWorld of listsWorld of normal values'a -&gt; 'b'a list-&gt; 'b listList.map
• 182. Lifting to asyncWorld of asyncWorld of normal values'a -&gt; 'b'a async &gt; 'b asyncAsync.map
• 183. The right way to code with wrapped typesMost wrapped types provide a &#x201C;map&#x201D;let add42 x = x + 42Some 1 |&gt; Option.map add42[1;2;3] |&gt; List.map add42
• 184. Guideline: If you create a wrapped generic type, create a &#x201C;map&#x201D; for it.
• 185. Mapstype TwoTrack&lt;'TEntity&gt; =| Success of 'TEntity| Failure of stringlet mapTT f x =match x with| Success entity -&gt; Success (f entity)| Failure s -&gt; Failure s
• 186. Tip: Use applicatives for validation
• 187. Series validationname50emailNotBlankProblem: Validation done in series.So only one error at a time is returned
• 188. Parallel validationname50emailNotBlankSplit inputCombine outputNow we do get all errors at once!But how to combine?
• 189. Creating a valid data structuretype Request= {UserId: UserId;Name: String50;Email: EmailAddress}type RequestDTO= {UserId: int;Name: string;Email: string}
• 190. How not to do validation// do the validation of the DTOlet userIdOrError = validateUserId dto.userIdlet nameOrError = validateName dto.namelet emailOrError = validateEmail dto.emailif userIdOrError.IsSuccess&amp;&amp; nameOrError.IsSuccess&amp;&amp; emailOrError.IsSuccess then{userId = userIdOrError.SuccessValuename = nameOrError.SuccessValueemail = emailOrError.SuccessValue}else if userIdOrError.IsFailure&amp;&amp; nameOrError.IsSuccess&amp;&amp; emailOrError.IsSuccess thenuserIdOrError.Errorselse ...&#xF0FB;
• 191. Lifting to TwoTracksWorld of two-tracksWorld of normal valuescreateRequest userId name emailcreateRequestTT userIdOrError nameOrError emailOrErrorlift 3 parameter function
• 192. The right waylet createRequest userId name email ={userId = userIdOrError.SuccessValuename = nameOrError.SuccessValueemail = emailOrError.SuccessValue}let createRequestTT = lift3 createRequest
• 193. The right waylet createRequestTT = lift3 createRequestlet userIdOrError = validateUserId dto.userIdlet nameOrError = validateName dto.namelet emailOrError = validateEmail dto.emaillet requestOrError =createRequestTT userIdOrError nameOrError emailOrError&#xF0FC;
• 194. The right waylet userIdOrError = validateUserId dto.userIdlet nameOrError = validateName dto.namelet emailOrError = validateEmail dto.emaillet requestOrError =createRequest&lt;!&gt; userIdOrError&lt;*&gt; nameOrError&lt;*&gt; emailOrError&#xF0FC;
• 195. Guideline: If you use a wrapped generic type, look for a set of &#x201C;lifts&#x201D; associated with it
• 196. Guideline: If you create a wrapped generic type, also create a set of &#x201C;lifts&#x201D; for clients to use with itBut I&#x2019;m not going explain how right now!
• 197. MONOIDS
• 199. Thinking like a mathematician1 + 2 = 31 + (2 + 3) = (1 + 2) + 31 + 0 = 10 + 1 = 1
• 200. 1 + 2 = 3Some thingsA way of combining them
• 201. 2 x 3 = 6Some thingsA way of combining them
• 202. max(1,2) = 2Some thingsA way of combining them
• 203. "a" + "b" = "ab"Some thingsA way of combining them
• 204. concat([a],[b]) = [a; b]Some thingsA way of combining them
• 205. 1 + 21 + 2 + 31 + 2 + 3 + 4Is an integerIs an integerA pairwise operation has become an operation that works on lists!
• 206. 1 + (2 + 3) = (1 + 2) + 3Order of combining doesn&#x2019;t matter1 + 2 + 3 + 4 (1 + 2) + (3 + 4)((1 + 2) + 3) + 4All the same
• 207. 1 - (2 - 3) = (1 - 2) - 3Order of combining does matter
• 208. 1 + 0 = 10 + 1 = 1A special kind of thing that when you combine it with something, just gives you back the original something
• 209. 42 * 1 = 421 * 42 = 42A special kind of thing that when you combine it with something, just gives you back the original something
• 210. "" + "hello" = "hello" "hello" + "" = "hello"&#x201C;Zero&#x201D; for strings
• 211. The generalization&#x2022;You start with a bunch of things, and some way of combining them two at a time.&#x2022;Rule 1 (Closure): The result of combining two things is always another one of the things.&#x2022;Rule 2 (Associativity): When combining more than two things, which pairwise combination you do first doesn't matter.&#x2022;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!
• 212. &#x2022;Rule 1 (Closure): The result of combining two things is always another one of the things.&#x2022;Benefit: converts pairwise operations into operations that work on lists.1 + 2 + 3 + 4[ 1; 2; 3; 4 ] |&gt; List.reduce (+)
• 213. 1 * 2 * 3 * 4[ 1; 2; 3; 4 ] |&gt; List.reduce (*)&#x2022;Rule 1 (Closure): The result of combining two things is always another one of the things.&#x2022;Benefit: converts pairwise operations into operations that work on lists.
• 214. "a" + "b" + "c" + "d"[ "a"; "b"; "c"; "d" ] |&gt; List.reduce (+)&#x2022;Rule 1 (Closure): The result of combining two things is always another one of the things.&#x2022;Benefit: converts pairwise operations into operations that work on lists.
• 215. &#x2022;Rule 2 (Associativity): When combining more than two things, which pairwise combination you do first doesn't matter.&#x2022;Benefit: Divide and conquer, parallelization, and incremental accumulation.1 + 2 + 3 + 4
• 216. &#x2022;Rule 2 (Associativity): When combining more than two things, which pairwise combination you do first doesn't matter.&#x2022;Benefit: Divide and conquer, parallelization, and incremental accumulation.(1 + 2) (3 + 4)3 + 7
• 217. &#x2022;Rule 2 (Associativity): When combining more than two things, which pairwise combination you do first doesn't matter.&#x2022;Benefit: Divide and conquer, parallelization, and incremental accumulation.(1 + 2 + 3)
• 218. &#x2022;Rule 2 (Associativity): When combining more than two things, which pairwise combination you do first doesn't matter.&#x2022;Benefit: Divide and conquer, parallelization, and incremental accumulation.(1 + 2 + 3) + 4
• 219. &#x2022;Rule 2 (Associativity): When combining more than two things, which pairwise combination you do first doesn't matter.&#x2022;Benefit: Divide and conquer, parallelization, and incremental accumulation.(6) + 4
• 220. Issues with reduce&#x2022;How can I use reduce on an empty list?&#x2022;In a divide and conquer algorithm, what should I do if one of the "divide" steps has nothing in it?&#x2022;When using an incremental algorithm, what value should I start with when I have no data?
• 221. &#x2022;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.&#x2022;Benefit: Initial value for empty or missing data
• 222. Pattern: Simplifying aggregation code with monoids
• 223. type OrderLine = {Qty:int; Total:float}let orderLines = [{Qty=2; Total=19.98}{Qty=1; Total= 1.99}{Qty=3; Total= 3.99} ]let addLine line1 line2 =let newQty = line1.Qty + line2.Qtylet newTotal = line1.Total + line2.Total{Qty=newQty; Total=newTotal}orderLines |&gt; List.reduce addLineAny combination of monoids is also a monoid
• 224. Pattern: Convert non-monoids to monoids
• 225. Customer + Customer + CustomerCustomer Stats + Customer Stats + Customer StatsReduceMapNot a monoidA monoidSummary Stats
• 227. Guideline: Convert expensive monoids to cheap monoids
• 228. Log file (Mon)+Log File (Tue)+Log File (Wed)=Really big fileSummary (Mon)+Summary (Tue)+Summary (Wed)MapStrings are monoidsA monoidMuch more efficient for incremental updates&#x201C;Monoid homomorphism&#x201D;
• 229. Pattern: Seeing monoids everywhere
• 230. Monoids in the real worldMetrics guideline: Use counters rather than ratesAlternative metrics guideline: Make sure your metrics are monoids&#x2022; incremental updates&#x2022; can handle missing data
• 231. Is function composition a monoid?&gt;&gt;Function 1 apple -&gt; bananaFunction 2 banana -&gt; cherryNew Function apple -&gt; cherryNot the same thing.Not a monoid &#xF04C;
• 232. Is function composition a monoid?&gt;&gt;Function 1 apple -&gt; appleSame thingFunction 2 apple -&gt; appleFunction 3 apple -&gt; appleA monoid! &#xF04A;
• 233. Is function composition a monoid?&#x201C;Functions with same type of input and output&#x201D;Functions where the input and output are the same type are monoids! What shall we call these kinds of functions?
• 234. Is function composition a monoid?&#x201C;Functions with same type of input and output&#x201D;&#x201C;Endomorphisms&#x201D;Functions where the input and output are the same type are monoids! What shall we call these kinds of functions?All endomorphisms are monoids
• 235. Endomorphism examplelet plus1 x = x + 1 // int-&gt;intlet times2 x = x * 2 // int-&gt;intlet subtract42 x = x &#x2013; 42 // int-&gt;intlet functions = [plus1times2subtract42 ]let newFunction = // int-&gt;intfunctions |&gt; List.reduce (&gt;&gt;)newFunction 20 // =&gt; 0EndomorphismsPut them in a listReduce themAnother endomorphism
• 236. Event sourcingAny function containing an endomorphism can be converted into a monoid.For example: Event sourcingIs an endomorphismEvent-&gt; State-&gt; State
• 237. Event sourcing examplelet applyFns = [apply event1 // State -&gt; Stateapply event2 // State -&gt; Stateapply event3] // State -&gt; Statelet applyAll = // State -&gt; StateapplyFns |&gt; List.reduce (&gt;&gt;)let newState = applyAll oldState&#x2022; incremental updates&#x2022; can handle missing eventsPartial application of eventA function that can apply all the events in one step
• 238. BindAny function containing an endomorphism can be converted into a monoid.For example: Option.BindIs an endomorphism(fn param)-&gt; Option-&gt; Option
• 239. Event sourcing examplelet bindFns = [Option.bind (fun x-&gt;if x &gt; 1 then Some (x*2) else None)Option.bind (fun x-&gt;if x &lt; 10 then Some x else None)]let bindAll = // Option-&gt;OptionbindFns |&gt; List.reduce (&gt;&gt;)Some 4 |&gt; bindAll // Some 8Some 5 |&gt; bindAll // NonePartial application of Bind
• 240. Predicates as monoidstype Predicate&lt;'A&gt; = 'A -&gt; boollet predAnd pred1 pred2 x =if pred1 xthen pred2 xelse falselet predicates = [isMoreThan10Chars // string -&gt; boolisMixedCase // string -&gt; boolisNotDictionaryWord // string -&gt; bool]let combinedPredicate = // string -&gt; boolpredicates |&gt; List.reduce (predAnd)Not an endomorphismBut can still be combined
• 241. Pattern: Monads are monoids
• 242. Series combination=&gt;&gt;Result is same kind of thing (Closure)Order not important (Associative)Monoid!
• 243. Parallel combination=+Same thing (Closure)Order not important (Associative)Monoid!
• 244. Monad laws&#x2022;The Monad laws are just the monoid definitions in diguise&#x2013;Closure, Associativity, Identity&#x2022;What happens if you break the monad laws?&#x2013;You lose monoid benefits such as aggregation
• 245. A monad is just a monoid in the category of endofunctors!
• 246. THANKS!
• Fly UP