Functional Design Patterns
Total Page:16
File Type:pdf, Size:1020Kb
Functional Design Patterns @ScottWlaschin fsharpforfunandprofit.com Functional Design PatternsX @ScottWlaschin fsharpforfunandprofit.com Functional Design Patterns @ScottWlaschin fsharpforfunandprofit.com Functional patterns • Apomorphisms • Dynamorphisms • Chronomorphisms • Zygohistomorphic prepromorphisms 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 This talk Functional programming is scary Functional programming is scary Object oriented programming is scary OO pattern/principle FP pattern/principle • Single Responsibility Principle • Functions • Open/Closed principle • Functions • Dependency Inversion • Functions, also Principle • Interface Segregation • Functions Principle • Factory pattern • Yes, functions • Strategy pattern • Oh my, functions again! • Decorator pattern • Functions • Visitor pattern • Functions Seriously, FP patterns are different A short and mostly wrong history of programming paradigms Object-Oriented programming • What we envisioned: – Smalltalk • What we got: – C++ “Worse is better” – Object oriented Cobol – PHP – Java Functional programming • What we envisioned: – Haskell, OCaml, F#, etc • What we got: – ?? Please don’t let this happen to FP! Important to understand! CORE PRINCIPLES OF FP DESIGN Core principles of FP design Steal from mathematics Types are not classes Functions are things Function Composition everywhere Core principle: Steal from mathematics “Programming is one of the most difficult branches of applied mathematics” - E. W. Dijkstra Why mathematics Dijkstra 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. “Object-oriented programming is an exceptionally bad idea which could only have originated in California.” E. W. Dijkstra Mathematical functions Domain (int) Codomain (int) … … -1 0 0 1 1 2 2 3 3 4 … Function add1(x) … input x maps to x+1 let add1 x = x + 1 val add1 : int -> int Mathematical functions Domain (int) Codomain (int) … int add1(int input) … -1 { 0 0 switch (input) 1 1 { 2 2 case 0: return 1; 3 3 case 1: return 2; 4 … caseFunction 2: return add1(x) 3; … input case x 3: maps return to 4; x+1 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) Mathematical functions Domain (int) Codomain (int) … … -1 0 0 1 1 2 2 3 3 4 … Function add1(x) … input x maps to x+1 • A (mathematical) function always gives the same output value for a given input value • A (mathematical) function has no side effects Functions don't have to be about arithmetic Customer (domain) Name (codomain) … Name CustomerName(Customer input) … Cust1 { Alice Cust2 switch (input) Bob Cust3 { Sue Cust3 case Cust1: return “Alice”; John … case Cust2: return “Bob”; Pam caseFunction Cust3: CustomerName return “Sue”;(x) … input etc Customer maps to PersonalName } } Functions can work on functions int->int int list -> int list … … add1 eachAdd1 times2 eachTimes2 subtract3 eachSubtract3 add42 eachAdd42 … … Function List.map int->int maps to int list->int list Guideline: Strive for purity The practical benefits of pure functions Pure functions are easy to reason about Reasoning about code that might not be pure: customer.SetName(newName); var name = customer.GetName(); Reasoning about code that is pure: let newCustomer = setCustomerName(aCustomer, newName) let name,newCustomer = getCustomerName(aCustomer) The customer is being changed. The practical benefits of pure functions Pure functions are easy to refactor let x = doSomething() let y = doSomethingElse(x) return y + 1 The practical benefits of pure functions Pure functions are easy to refactor let x = doSomething() let y = doSomethingElse(x) return y + 1 The practical benefits of pure functions Pure functions are easy to refactor let helper() = let x = doSomething() let y = doSomethingElse(x) return y return helper() + 1 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" How to design a pure function Pure Awesomeness! 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 Core principle: Types are not classes X Types are not classes So what is a type? Set of Set of valid inputs valid outputs Types separate data from behavior Lists Lists List.map List.collect List.filter List.etc Core principle: Functions are things Function Functions as things FunctionThe Tunnel of Transformationapple -> banana A function is a standalone thing, not attached to a class Functions as things let z = 1 1 let add x y = x + y int-> int->int Functions as inputs and outputs let add x = (fun y -> x + y) int int->(int->int) int->int Function as output let useFn f = f() + 1 int->int (int->int)->int int Function as input let transformInt f x = (f x) + 1 int->int Function as parameter int int->int int Core principle: Composition everywhere Function composition Function 1 Function 2 apple -> banana banana -> cherry Function composition Function 1 Function 2 apple -> banana >> banana -> cherry Function composition New Function apple -> cherry Can't tell it was built from smaller functions! Types can be composed too “algebraic types" Product types Alice, Jan 12th Set of people Set of dates Bob, Feb 2nd × = Carol, Mar 3rd type Birthday = Person * Date Sum types Set of Cash values + type PaymentMethod = | Cash Set of Cheque values | Cheque of ChequeNumber + | Card of CardType * CardNumber Set of CreditCard values DDD & DOMAIN MODELLING Domain modelling pattern: Use types to represent constraints Types represent constraints on input and output type Suit = Club | Diamond | Spade | Heart type String50 = // non-null, not more than 50 chars type EmailAddress = // non-null, must contain ‘@’ type StringTransformer = string -> string Instant mockability type GetCustomer = CustomerId -> Customer option Domain modelling pattern: Types are cheap Types are cheap type Suit = Club | Diamond | Spade | Heart type Rank = Two | Three | Four | Five | Six | Seven | Eight | Nine | Ten | Jack | Queen | King | Ace type Card = Suit * Rank type Hand = Card list Only one line of code to create a type! Design principle: Strive for totality Totality Domain (int) Codomain (int) … int TwelveDividedBy(int input) … 3 { 4 2 switch (input) 6 1 { 12 0 case 3: return 4; … … case 2: return 6; casetwelveDividedBy 1: return 12; (x) caseinput x0: return maps to ??; 12/x } } What happens here? Totality Constrain the input NonZeroInteger int … int TwelveDividedBy(int input) … 3 { 4 2 switch (input) 6 1 { 12 -1 case 3: return 4; … … case 2: return 6; casetwelveDividedBy 1: return 12; (x) caseinput x- 1: returnmaps to - 12; 12/x 0 is missing } } 0 is doesn’t have to be handled NonZeroInteger -> int Types are documentation Totality Extend the output int Option<Int> … int TwelveDividedBy(int input) … 3 { Some 4 2 switch (input) Some 6 1 { Some 12 0 case 3: return Some 4; None -1 case 2: return Some 6; … … casetwelveDividedBy 1: return Some(x) 12; caseinput x0: return maps to None; 12/x } } 0 is valid input int -> int option Types are documentation Design principle: Use types to indicate errors Output types as error codes ParseInt: string -> int No nulls ParseInt: string -> int option No exceptions FetchPage: Uri -> String FetchPage: Uri -> SuccessOrFailure<String> LoadCustomer: CustomerId -> Customer LoadCustomer: CustomerId -> SuccessOrFailure<Customer> Use the signature, Luke! Domain modelling principle: “Make illegal states unrepresentable” Types can represent business rules type EmailContactInfo = | Unverified of EmailAddress | Verified of VerifiedEmailAddress type ContactInfo = | EmailOnly of EmailContactInfo | AddrOnly of PostalContactInfo | EmailAndAddr of EmailContactInfo * PostalContactInfo Domain modelling principle: Use sum-types instead of inheritance Using sum vs. inheritance type PaymentMethod = | Cash | Cheque of ChequeNumber | Card of CardType * CardNumber OO version: What goes in here? What interface IPaymentMethod {..} is the common behaviour? class Cash : IPaymentMethod {..} Definition is scattered around class Cheque : IPaymentMethod {..} many locations class Card : IPaymentMethod {..} class Evil : IPaymentMethod {..} Domain modelling principle: Use sum-types for state machines Add Item Add Item Empty Cart Active Cart Paid Cart Pay Remove Item Remove Item type ShoppingCart = | EmptyCartState | ActiveCartState of ActiveCartData | PaidCartState of PaidCartData Domain modelling principle: It’s ok to expose public data It’s ok to expose public data Immutable type PersonalName = { FirstName: String50 Can’t create MiddleInitial: String1 option invalid values LastName: String50 } Domain modelling principle: Types are executable documentation Types are executable documentation type Suit = Club | Diamond | Spade | Heart type Rank = Two | Three | Four | Five | Six |