Functional Programming Patterns (BuildStuff '14)

  • CategorySoftware

  • View103634

Report
  • 1. Functional Design Patterns@ScottWlaschinfsharpforfunandprofit.com
2. Functional Design Patterns@ScottWlaschinfsharpforfunandprofit.comX 3. Functional Design Patterns@ScottWlaschinfsharpforfunandprofit.com 4. Functional patterns•Apomorphisms•Dynamorphisms•Chronomorphisms•Zygohistomorphic prepromorphisms 5. Functional patterns•Core Principles of FP design–Functions, types, composition•Functions as parameters–Abstraction, Dependency injection–Partial application, Continuations, Folds,•Chaining functions–Error handling, Async–Monads•Dealing with wrapped data–Lifting, Functors–Validation with Applicatives•Aggregating data and operations–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. •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•Yes, functions•Oh my, functions again!•Functions•Functions OO pattern/principleFP pattern/principleSeriously, FP patterns are different 11. A short and mostly wrong history of programming paradigms 12. •What we envisioned:–Smalltalk•What we got:–C++–Object oriented Cobol–PHP–Java“Worse is better”Object-Oriented programming 13. •What we envisioned:–Haskell, OCaml, F#, etc•What we got:–??Functional programmingPlease don’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“Programming is one of the most difficult branches of applied mathematics” - E. W. Dijkstra 17. Why mathematicsDijkstra said:•Mathematical assertions tend to be unusually precise.•Mathematical assertions tend to be general. They are applicable to a large (often infinite) class of instances.•Mathematics embodies a discipline of reasoning allowing assertions to be made with an unusually high confidence level. 18. “Object-oriented programming is an exceptionally bad idea which could only have originated in California.”E. W. Dijkstra 19. Mathematical functionsFunction add1(x) input x maps to x+1…-10123…Domain (int)Codomain (int)…01234…let add1 x = x + 1val add1 : int -> int 20. Function add1(x) input x maps to x+1…-10123…Domain (int)Codomain (int)…01234…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}}•Input and output values already exist•A function is not a calculation, just a mapping•Input and output values are unchanged (immutable) 21. Mathematical functions•A (mathematical) function always gives the same output value for a given input value•A (mathematical) function has no side effectsFunction add1(x) input x maps to x+1…-10123…Domain (int)Codomain (int)…01234… 22. Functions don't have to be about arithmeticFunction CustomerName(x) input Customer maps to PersonalName…Cust1 Cust2 Cust3 Cust3 …Customer (domain)Name (codomain)…Alice BobSue JohnPam…Name CustomerName(Customer input){switch (input){ case Cust1: return “Alice”;case Cust2: return “Bob”;case Cust3: return “Sue”;etc}} 23. Functions can work on functionsFunction List.map int->int maps to int list->int list…add1 times2 subtract3 add42 …int->intint list -> int list…eachAdd1 eachTimes2eachSubtract3 eachAdd42… 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•Laziness–only evaluate when I need the output•Cacheable results–same answer every time–"memoization"•No order dependencies–I can evaluate them in any order I like•Parallelizable–"embarrassingly parallel" 30. How to design a pure functionPure Awesomeness! 31. How to design a pure function•Haskell–very pure•F#, OCaml, Clojure, Scala–easy to be pure•C#, Java, JavaScript–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 -> bananaA function is a standalone thing, not attached to a class 38. Functions as thingslet z = 1int-> int->int1let add x y = x + y 39. Functions as inputs and outputslet useFn f = (f 1) + 2let add x = (fun y -> x + y)int->(int->int)int ->intintintint->intlet transformInt f x = (f x) + 1int->intintintFunction as outputFunction as inputFunction as parameter(int->int)->intint->int 40. Core principle: Composition everywhere 41. Function compositionFunction 1 apple -> bananaFunction 2 banana -> cherry 42. Function composition>>Function 1 apple -> bananaFunction 2 banana -> cherry 43. Function compositionNew Function apple -> cherryCan't tell it was built from smaller functions! 44. Types can be composed too“algebraic types" 45. Product types×=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 & 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 ‘@’type StringTransformer = string -> stringtype GetCustomer = CustomerId -> 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…3210…Domain (int)Codomain (int)…4612…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…321-1…NonZeroIntegerint…4612…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’t have to be handledNonZeroInteger -> int0 is missingTypes are documentation 55. twelveDividedBy(x) input x maps to 12/x…3210-1…intOption<Int>…Some 4Some 6Some 12None …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 -> int optionTypes are documentation 56. Design principle: Use types to indicate errors 57. Output types as error codesLoadCustomer: CustomerId -> CustomerLoadCustomer: CustomerId -> SuccessOrFailure<Customer>ParseInt: string -> intParseInt: string -> int optionFetchPage: Uri -> StringFetchPage: Uri -> SuccessOrFailure<String>No nullsNo exceptionsUse the signature, Luke! 58. Domain modelling principle: “Make illegal states unrepresentable” 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’s ok to expose public data 65. It’s ok to expose public datatype PersonalName = {FirstName: String50MiddleInitial: String1 optionLastName: String50 }ImmutableCan’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 –› (Deck * Card)type PickupCard = (Hand * Card) –› 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  70. HIGH-LEVEL DESIGN 71. Design paradigm: Functions all the way down 72. “Functions in the small, objects in the large” 73. Low-level operationToUpperServiceDomain logicHigh-level use-caseAddressValidatorVerifyEmailAddressUpdateProfileData“Service” is the new “microservice”stringstringAddressResultEmailVerificationSagaHttp ResponseAddressEmailHttp Request 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 <= 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;} 89. 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 90. Parameterize all the thingslet product n =let initialValue = 1let action productSoFar x = productSoFar * x[1..n] |> List.fold action initialValuelet sum n =let initialValue = 0let action sumSoFar x = sumSoFar+x[1..n] |> 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 -> unit) -> seq<'a> -> unitAny kind of collection, any kind of action!F# and other functional languages make code generic automatically 93. Generic codeint -> intHow many ways are there to implement this function?'a -> 'aHow many ways are there to this function? 94. Generic codeint list -> int listHow many ways are there to implement this function?'a list -> 'a listHow many ways are there to this function? 95. Generic code('a -> 'b) -> 'a list -> '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 -> intAny function with that type is compatible with itlet add2 x = x + 2 // int -> intlet times3 x = x * 3 // int -> 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 -> int 101. Decorator pattern in FPFunctional equivalent of decorator patternlet add1 x = x + 1 // int -> 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 -> intlet logged f x =printfn "input is %A" xlet result = f xprintfn "output is %A" resultresultlet add1Decorated = // int -> intlogged add1[1..5] |> List.map add1[1..5] |> 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 -> x + y)let add x = (fun y -> x + y)int-> int->intint-> int->intint-> (int->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 |> 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 -> Customerlet getCustomerFromDatabase connection(customerId:CustomerId) =// from connection // select customer // where customerId = customerIdtype of getCustomerFromDatabase =DbConnection -> CustomerId -> Customerlet getCustomer1 = getCustomerFromDatabase myConnection// getCustomer1 : CustomerId -> CustomerPersistence agnostic 110. type GetCustomer = CustomerId -> Customerlet getCustomerFromMemory map (customerId:CustomerId) =map |> Map.find customerIdtype of getCustomerFromMemory =Map<Id,Customer> -> CustomerId -> Customerlet getCustomer2 = getCustomerFromMemory inMemoryMap// getCustomer2 : CustomerId -> 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<int> 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 – 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 – 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 – only two parametersPartially apply the continuations 118. MONADS 119. Pyramid of doom: null testing examplelet example input =let x = doSomething inputif x <> null thenlet y = doSomethingElse xif y <> null thenlet z = doAThirdThing yif z <> null thenlet result = zresultelsenullelsenullelsenullI know you could do early returns, but bear with me... 120. Pyramid of doom: async examplelet taskExample input =let taskX = startTask inputtaskX.WhenFinished (fun x ->let taskY = startAnotherTask xtaskY.WhenFinished (fun y ->let taskZ = startThirdTask ytaskZ.WhenFinished (fun z ->z // final result 121. Pyramid of doom: null examplelet example input =let x = doSomething inputif x <> null thenlet y = doSomethingElse xif y <> null thenlet z = doAThirdThing yif z <> 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’s do a cut & 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 |> ifSomeDo doWithZlet ifSomeDo f x =if x.IsSome thenf x.ValueelseNone 131. let doWithY y =y|> doAThirdThing|> ifSomeDo doWithZlet ifSomeDo f x =if x.IsSome thenf x.ValueelseNone 132. let example input =doSomething input|> ifSomeDo doSomethingElse|> ifSomeDo doAThirdThing|> ifSomeDo (fun z -> Some z)let ifSomeDo f x =if x.IsSome thenf x.ValueelseNone 133. A switch analogySomeNoneInput -> 134. Connecting switcheson SomeBypass on None 135. Connecting switches 136. Connecting switches 137. Composing switches>>>>Composing one-track functions is fine... 138. Composing switches>>>>... and composing two-track functions is fine... 139. Composing switches... but composing switches is not allowed! 140. Composing switchesTwo-track inputTwo-track inputOne-track inputTwo-track input 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 -> nextFunction s| None -> NoneBuilding an adapter blockTwo-track inputTwo-track output 144. let bind nextFunction optionInput =match optionInput with| Some s -> nextFunction s| None -> NoneBuilding an adapter blockTwo-track inputTwo-track output 145. let bind nextFunction optionInput =match optionInput with| Some s -> nextFunction s| None -> NoneBuilding an adapter blockTwo-track inputTwo-track output 146. let bind nextFunction optionInput =match optionInput with| Some s -> nextFunction s| None -> 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 -> f v| None -> 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|> bind doSomethingElse|> bind doAThirdThing|> bind (fun z -> Some z)Pyramid of doom: using bindlet bind f opt =match opt with| Some v -> f v| None -> NoneNo pyramids!Code is linear and clear.This pattern is called “monadic bind” 150. Pattern: Use bind to chain tasks 151. Connecting tasksWhen task completesWaitWait 152. Pyramid of doom: using bind for taskslet taskBind f task =task.WhenFinished (fun taskResult ->f taskResult)let taskExample input =startTask input|> taskBind startAnotherTask|> taskBind startThirdTask|> taskBind (fun z -> z)a.k.a “promise” “future”This pattern is also a “monadic bind” 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<'TEntity> =| 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 > 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 -> 165. Connecting switchesValidateUpdateDbon successbypass 166. Connecting switchesValidateUpdateDb 167. Connecting switchesValidateUpdateDbSendEmail 168. Connecting switchesValidateUpdateDbSendEmail 169. Functional flow without error handlinglet updateCustomer =receiveRequest|> validateRequest|> canonicalizeEmail|> updateDbFromRequest|> sendEmail|> returnMessageBeforeOne track 170. Functional flow with error handlinglet updateCustomerWithErrorHandling =receiveRequest|> validateRequest|> canonicalizeEmail|> updateDbFromRequest|> sendEmail|> 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 174. World of optionsWorld of normal valuesint string boolint option string option bool option 175. How not to code with optionsLet’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 176. World of optionsWorld of normal valuesadd42 177. World of optionsWorld of normal valuesadd42 178. LiftingWorld of optionsWorld of normal values'a -> 'b'a option -> 'b optionOption.map 179. The right way to code with optionsLet’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 |> add42ToOptionSome 1 |> Option.map add42 180. Pattern: Use “map” to lift functions 181. Lifting to listsWorld of listsWorld of normal values'a -> 'b'a list-> 'b listList.map 182. Lifting to asyncWorld of asyncWorld of normal values'a -> 'b'a async > 'b asyncAsync.map 183. The right way to code with wrapped typesMost wrapped types provide a “map”let add42 x = x + 42Some 1 |> Option.map add42[1;2;3] |> List.map add42 184. Guideline: If you create a wrapped generic type, create a “map” for it. 185. Mapstype TwoTrack<'TEntity> =| Success of 'TEntity| Failure of stringlet mapTT f x =match x with| Success entity -> Success (f entity)| Failure s -> 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&& nameOrError.IsSuccess&& emailOrError.IsSuccess then{userId = userIdOrError.SuccessValuename = nameOrError.SuccessValueemail = emailOrError.SuccessValue}else if userIdOrError.IsFailure&& nameOrError.IsSuccess&& emailOrError.IsSuccess thenuserIdOrError.Errorselse ... 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 194. The right waylet userIdOrError = validateUserId dto.userIdlet nameOrError = validateName dto.namelet emailOrError = validateEmail dto.emaillet requestOrError =createRequest<!> userIdOrError<*> nameOrError<*> emailOrError 195. Guideline: If you use a wrapped generic type, look for a set of “lifts” associated with it 196. Guideline: If you create a wrapped generic type, also create a set of “lifts” for clients to use with itBut I’m not going explain how right now! 197. MONOIDS 198. Mathematics Ahead 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’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"“Zero” for strings 211. 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! 212. •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 (+) 213. 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. 214. "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. 215. •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 216. •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 217. •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) 218. •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 219. •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 220. 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? 221. •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 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 |> 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 226. Hadoop make me a sandwichhttps://twitter.com/daviottenheimer/status/532661754820829185 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“Monoid homomorphism” 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• incremental updates• can handle missing data 231. Is function composition a monoid?>>Function 1 apple -> bananaFunction 2 banana -> cherryNew Function apple -> cherryNot the same thing.Not a monoid  232. Is function composition a monoid?>>Function 1 apple -> appleSame thingFunction 2 apple -> appleFunction 3 apple -> appleA monoid!  233. 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? 234. 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 235. Endomorphism examplelet plus1 x = x + 1 // int->intlet times2 x = x * 2 // int->intlet subtract42 x = x – 42 // int->intlet functions = [plus1times2subtract42 ]let newFunction = // int->intfunctions |> List.reduce (>>)newFunction 20 // => 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-> State-> State 237. Event sourcing examplelet applyFns = [apply event1 // State -> Stateapply event2 // State -> Stateapply event3] // State -> Statelet applyAll = // State -> StateapplyFns |> List.reduce (>>)let newState = applyAll oldState• incremental updates• 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)-> Option-> Option 239. Event sourcing examplelet bindFns = [Option.bind (fun x->if x > 1 then Some (x*2) else None)Option.bind (fun x->if x < 10 then Some x else None)]let bindAll = // Option->OptionbindFns |> List.reduce (>>)Some 4 |> bindAll // Some 8Some 5 |> bindAll // NonePartial application of Bind 240. Predicates as monoidstype Predicate<'A> = 'A -> boollet predAnd pred1 pred2 x =if pred1 xthen pred2 xelse falselet predicates = [isMoreThan10Chars // string -> boolisMixedCase // string -> boolisNotDictionaryWord // string -> bool]let combinedPredicate = // string -> boolpredicates |> List.reduce (predAnd)Not an endomorphismBut can still be combined 241. Pattern: Monads are monoids 242. Series combination=>>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•The Monad laws are just the monoid definitions in diguise–Closure, Associativity, Identity•What happens if you break the monad laws?–You lose monoid benefits such as aggregation 245. A monad is just a monoid in the category of endofunctors! 246. THANKS!
Description
1. Functional Design Patterns@ScottWlaschinfsharpforfunandprofit.com 2. Functional Design Patterns@ScottWlaschinfsharpforfunandprofit.comX 3. Functional Design Patterns@ScottWlaschinfsharpforfunandprofit.com…