Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

The Power of Composition

2,243 views

Published on

(Video and code at http://fsharpforfunandprofit.com/composition)

Composition is a fundamental principle of functional programming, but how is it different from an object-oriented approach, and how do you use it in practice?

In this talk for beginners, we'll start by going over the basic concepts of functional programming, and then look at some different ways that composition can be used to build large things from small things.

After that, we'll see how composition is used in practice, beginning with a simple FizzBuzz example, and ending with a complete (object-free!) web application.

Published in: Software

The Power of Composition

  1. 1. The Power Of Composition @ScottWlaschin fsharpforfunandprofit.com ^ for beginners in FP
  2. 2. The Power Of Composition • The philosophy of composition • Functional programming principles – Functions and how to compose them – Types and how to compose them • Composition in practice – Two simple examples – FizzBuzz gone carbonated – A web service
  3. 3. PREREQUISITES
  4. 4. How to learn functional programming • Have a "beginner's mind" • Forget everything you know about object- oriented programming – No loops! – No variables! – No objects!
  5. 5. The "Are you ready to learn FP?" test • What is a class? • What is a method? • What is a for-loop? • What is inheritance? You *must* pass this test before continuing!
  6. 6. Answers! • What is a class? – Correct answer: "I don't know" • What is a method? – Correct answer: "No idea" • What is a for-loop? – Correct answer: "I couldn't tell you" • What is inheritance? – Correct answer: "Search me"
  7. 7. THE PHILOSOPHY OF COMPOSITION
  8. 8. Prerequisites for understanding composition • You must have been a child at some point • You must have played with Lego • You must have played with toy trains
  9. 9. Lego Philosophy
  10. 10. Lego Philosophy 1. All pieces are designed to be connected 2. Connect two pieces together and get another "piece" that can still be connected 3. The pieces are reusable in many contexts
  11. 11. All pieces are designed to be connected
  12. 12. Connect two pieces together and get another "piece" that can still be connected
  13. 13. The pieces are reusable in different contexts
  14. 14. Make big things from small things in the same way
  15. 15. Wooden RailwayTrack Philosophy 1. All pieces are designed to be connected 2. Connect two pieces together and get another "piece" that can still be connected 3. The pieces are reusable in many contexts
  16. 16. All pieces are designed to be connected
  17. 17. Connect two pieces together and get another "piece" that can still be connected You can keep adding and adding.
  18. 18. The pieces are reusable in different contexts
  19. 19. Make big things from small things in the same way
  20. 20. Unix Philosophy • Write programs that do one thing well. – To do a new job, build afresh rather than complicate old programs by adding new "features". • Write programs to work together. – Expect the output of every program to become the input to another, as yet unknown, program. • Write programs to handle text streams, because that is a universal interface. All pieces are designed to be connected The pieces are reusable You don't need to create a special adapter to make connections.
  21. 21. THE PRINCIPLES OF FUNCTIONAL PROGRAMMING
  22. 22. Core principles of FP Function Types are not classes Functions are things Composition everywhere
  23. 23. Core FP principle: Functions are things Function
  24. 24. Functions as things The Tunnel of Transformation Function apple -> banana A function is a thing which transforms inputs to outputs
  25. 25. A function is a standalone thing, not attached to a class It can be used for inputs and outputs of other functions
  26. 26. input A function can be an output A function is a standalone thing
  27. 27. output A function can be an input A function is a standalone thing
  28. 28. input output A function can be a parameter A function is a standalone thing
  29. 29. Core FP principle: Composition everywhere
  30. 30. Function composition Function 1 apple -> banana Function 2 banana -> cherry
  31. 31. Function composition >> Function 1 apple -> banana Function 2 banana -> cherry
  32. 32. Function composition New Function apple -> cherry Can't tell it was built from smaller functions! Where did the banana go? (abstraction)
  33. 33. Function composition New Function apple -> cherry A Very Important Point: For composition to work properly: • Data must be immutable • Functions must be self-contained, with no strings attached: no side-effects, no I/O, no globals, etc
  34. 34. let add1 x = x + 1 let double x = x + x let add1_double = add1 >> double let x = add1_double 5 // 12 Composition DoubleAdd1
  35. 35. let add1_double_square = add1 >> double >> square let x = add1_double_square 5 // 144 Composition DoubleAdd1 Square
  36. 36. Piping
  37. 37. add1 5 // = 6 double (add1 5) // = 12 square (double (add1 5)) // = 144
  38. 38. 5 |> add1 // = 6 5 |> add1 |> double // = 12 5 |> add1 |> double |> square // = 144 add1 double square5 6 12 144
  39. 39. Building big things from functions It's compositions all the way up
  40. 40. Low-level operation ToUpper stringstring
  41. 41. 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
  42. 42. Service Use-case UpdateProfileData ChangeProfile Result ChangeProfile Request Service Service
  43. 43. Use-case Web application Http Response Http Request Use-case Use-case
  44. 44. Http Response Http Request
  45. 45. • "monoids" – for combining strings, lists, etc. • "monads" – for composing functions with effects • CategoryTheory – or, "Composition Theory" More kinds of composition for functional programmers…
  46. 46. Core FP principle: Types are not classes
  47. 47. So, what is a type then? A type is a just a name for a set of things Set of valid inputs Set of valid outputs Function
  48. 48. Set of valid inputs Set of valid outputs Function 1 2 3 4 5 6 This is type "integer" A type is a just a name for a set of things
  49. 49. Set of valid inputs Set of valid outputs Function This is type "string" "abc" "but" "cobol" "double" "end" "float" A type is a just a name for a set of things
  50. 50. Set of valid inputs Set of valid outputs Function This is type "Person" Donna Roy Javier Mendoza Nathan Logan Shawna Ingram Abel Ortiz Lena Robbins GordonWood A type is a just a name for a set of things
  51. 51. Set of valid inputs Set of valid outputs Function This is type "Fruit" A type is a just a name for a set of things
  52. 52. Set of valid inputs Set of valid outputs Function This is a type of Fruit->Fruit functions A type is a just a name for a set of things
  53. 53. Composition everywhere: Types can be composed too
  54. 54. Algebraic type system
  55. 55. New types are built from smaller types by: Composing with “AND” Composing with “OR”
  56. 56. Example: pairs, tuples, records FruitSalad = One each of and and Compose with “AND” type FruitSalad = { Apple: AppleVariety Banana: BananaVariety Cherry: CherryVariety }
  57. 57. Snack = or or Compose with “OR” type Snack = | Apple of AppleVariety | Banana of BananaVariety | Cherry of CherryVariety
  58. 58. Real world example of type composition
  59. 59. Example of some requirements: We accept three forms of payment: Cash, Check, or Card. For Cash we don't need any extra information For Checks we need a check number For Cards we need a card type and card number
  60. 60. interface IPaymentMethod {..} class Cash() : IPaymentMethod {..} class Check(int checkNo): IPaymentMethod {..} class Card(string cardType, string cardNo) : IPaymentMethod {..} In OO design you would probably implement it as an interface and a set of subclasses, like this:
  61. 61. type CheckNumber = int type CardNumber = string In F# you would probably implement by composing types, like this:
  62. 62. type CheckNumber = ... type CardNumber = … type CardType = Visa | Mastercard type CreditCardInfo = { CardType : CardType CardNumber : CardNumber }
  63. 63. type CheckNumber = ... type CardNumber = ... type CardType = ... type CreditCardInfo = ... type PaymentMethod = | Cash | Check of CheckNumber | Card of CreditCardInfo
  64. 64. type CheckNumber = ... type CardNumber = ... type CardType = ... type CreditCardInfo = ... type PaymentMethod = | Cash | Check of CheckNumber | Card of CreditCardInfo type PaymentAmount = decimal type Currency = EUR | USD
  65. 65. type CheckNumber = ... type CardNumber = ... type CardType = ... type CreditCardInfo = ... type PaymentMethod = | Cash | Check of CheckNumber | Card of CreditCardInfo type PaymentAmount = decimal type Currency = EUR | USD type Payment = { Amount : PaymentAmount Currency: Currency Method: PaymentMethod }
  66. 66. FP design principle: Types are executable documentation
  67. 67. type Deal = Deck –› (Deck * Card) type PickupCard = (Hand * Card) –› Hand Types are executable documentation 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 type Deck = Card list type Player = {Name:string; Hand:Hand} type Game = {Deck:Deck; Players: Player list} The domain on one screen!
  68. 68. Types are executable documentation type CardType =Visa | Mastercard type CardNumber = CardNumber of string type CheckNumber = CheckNumber of int type PaymentMethod = | Cash | Check of CheckNumber | Card of CardType * CardNumber
  69. 69. A big topic and not enough time   More on DDD and designing with types at fsharpforfunandprofit.com/ddd
  70. 70. BASIC COMPOSITION: THINK OF A NUMBER
  71. 71. Think of a number • Think of a number. • Add one to it. • Square it. • Subtract one. • Divide by the number you first thought of. • Subtract the number you first thought of. • The answer is TWO!
  72. 72. Think of a number AddOne SquareItSubtractOne DivideByTheNumberYouThoughtOf SubtractTheNumberYouThoughtOf TheNumberYouThoughtOf Answer
  73. 73. let thinkOfANumber numberYouThoughtOf = // define a function for each step let addOne x = x + 1 let squareIt x = x * x let subtractOne x = x - 1 let divideByTheNumberYouThoughtOf x = x / numberYouThoughtOf let subtractTheNumberYouThoughtOf x = x - numberYouThoughtOf // then combine them using piping numberYouThoughtOf |> addOne |> squareIt |> subtractOne |> divideByTheNumberYouThoughtOf |> subtractTheNumberYouThoughtOf
  74. 74. IT'S NOT ALWAYSTHIS EASY…
  75. 75. function A function BCompose
  76. 76. function A function B
  77. 77. function A and B Easy!
  78. 78. ... But here is a challenge
  79. 79. function AInput Output function BInput Output 1 Output 2 function C Input 1 Output Input 2
  80. 80. function AInput Output function BInput Output 1 Output 2 Challenge: How can we compose these?
  81. 81. function AInput Output function C Input 1 Output Input 2 Challenge: How can we compose these?
  82. 82. COMPOSING MULTIPLE INPUTS: ROMAN NUMERALS
  83. 83. To Roman Numerals • Task: convert an integer to Roman Numerals • V = 5, X = 10, C = 100 etc
  84. 84. To Roman Numerals • Use the "tally" approach – Start with N copies of "I" – Replace five "I"s with a "V" – Replace two "V"s with a "X" – Replace five "X"s with a "L" – Replace two "L"s with a "C" – Replace five "C"s with a "D" – Replace two "D"s with a "M"
  85. 85. The Replace function oldValue outputString newValue inputString Replace
  86. 86. Uh-oh! Composition problem Replace I / V Replace V / X Replace X / L 
  87. 87. Bad news: Composition patterns only work for functions that have one parameter! 
  88. 88. Good news! Every function can be turned into a one parameter function 
  89. 89. Haskell Curry We named this technique after him
  90. 90. Input A Uncurried Function Input B Output C Curried Function Input A Intermediate Function Output CInput B What is currying? after currying Currying means that *every* function can be converted to a series of one input functions
  91. 91. Replace Before currying let replace oldValue newValue inputStr = inputStr.Replace(oldValue,newValue)
  92. 92. Replace Old New Old New After currying let replace oldValue newValue = fun inputStr -> inputStr.Replace(oldValue,newValue)
  93. 93. Partial Application Replace ReplaceOldNew Old New Old New let replace_IIIII_V = replace "IIIII" "V" // replace_IIIII_V is a function let replace_VV_X = replace "VV" "X" // replace_VV_X is a function Only 2 parameters passed in
  94. 94. To Roman Numerals integer etc Replicate "I" Replace_IIIII_V Replace_VV_X Replace_XXXXX_L
  95. 95. let toRomanNumerals number = let replace_IIIII_V = replace "IIIII" "V" let replace_VV_X = replace "VV" "X" let replace_XXXXX_L = replace "XXXXX" "L" let replace_LL_C = replace "LL" "C" let replace_CCCCC_D = replace "CCCCC" "D" let replace_DD_M = replace "DD" "M" String.replicate number "I" |> replace_IIIII_V |> replace_VV_X |> replace_XXXXX_L |> replace_LL_C |> replace_CCCCC_D |> replace_DD_M
  96. 96. Inline Partial Application let add x y = x + y let multiply x y = x * y 5 |> add 2 |> multiply 2 Piping "inline" partial application
  97. 97. Inline Partial Application let add x y = x + y let multiply x y = x * y [1..10] |> List.map (add 2) |> List.map (multiply 2) Two "inline" partial applications
  98. 98. let toRomanNumerals number = String.replicate number "I" |> replace "IIIII" "V" |> replace "VV" "X" |> replace "XXXXX" "L" |> replace "LL" "C" |> replace "CCCCC" "D" |> replace "DD" "M" With inline partial application
  99. 99. let toRomanNumerals number = String.replicate number "I" |> replace "IIIII" "V" |> replace "VV" "X" |> replace "XXXXX" "L" |> replace "LL" "C" |> replace "CCCCC" "D" |> replace "DD" "M" With inline partial application // can easily add new segments to the pipeline |> replace "VIIII" "IX" |> replace "IIII" "IV" |> replace "LXXXX" "XC"
  100. 100. functionInput Output function Input 1 Output Input 2 Challenge: How can we compose these?
  101. 101. COMPOSING MULTIPLE OUTPUTS: FIZZBUZZ
  102. 102. FizzBuzz definition • Write a program that prints the numbers from 1 to 100 • But: – For multiples of three print "Fizz" instead – For multiples of five print "Buzz" instead – For multiples of both three and five print "FizzBuzz" instead.
  103. 103. let fizzBuzz max = for n in [1..max] do if (isDivisibleBy n 15) then printfn "FizzBuzz" else if (isDivisibleBy n 3) then printfn "Fizz" else if (isDivisibleBy n 5) then printfn "Buzz" else printfn "%i" n let isDivisibleBy n divisor = (n % divisor) = 0 A simple implementation
  104. 104. Pipeline implementation Handle 3 case Handle 5 case number Answer Handle 15 case Handle remaining
  105. 105. number Handle case Carbonated (e.g. "Fizz", "Buzz") Uncarbonated (e.g. 2, 7, 13)
  106. 106. Uncarbonated Carbonated Input ->
  107. 107. type CarbonationResult = | Uncarbonated of int // unprocessed | Carbonated of string // "Fizz", Buzz", etc FizzBuzz core let carbonate divisor label n = if (isDivisibleBy n divisor) then Carbonated label else Uncarbonated n Idea from http://weblog.raganwald.com/2007/01/dont-overthink-fizzbuzz.html
  108. 108. 12 |> carbonate 3 "Fizz" // Carbonated "Fizz" 10 |> carbonate 3 "Fizz" // Uncarbonated 10 10 |> carbonate 5 "Buzz" // Carbonated "Buzz"
  109. 109. If Uncarbonated If Carbonated bypass How to we compose them?
  110. 110. How do we compose these?
  111. 111. >> >> Composing one-track functions is fine...
  112. 112. >> >> ... and composing two-track functions is fine...
  113. 113.   ... but composing points/switches is not allowed!
  114. 114. let fizzbuzz n = let result15 = n |> carbonate 15 "FizzBuzz" match result15 with | Carbonated str -> str | Uncarbonated n -> let result3 = n |> carbonate 3 "Fizz" match result3 with | Carbonated str -> str | Uncarbonated n -> let result5 = n |> carbonate 5 "Buzz" match result5 with | Carbonated str -> str | Uncarbonated n -> string n // convert to string First implementation attempt
  115. 115. let fizzbuzz n = let result15 = n |> carbonate 15 "FizzBuzz" match result15 with | Carbonated str -> str | Uncarbonated n -> let result3 = n |> carbonate 3 "Fizz" match result3 with | Carbonated str -> str | Uncarbonated n -> let result5 = n |> carbonate 5 "Buzz" match result5 with | Carbonated str -> str | Uncarbonated n -> // do something with Uncarbonated value
  116. 116. let fizzbuzz n = let result15 = n |> carbonate 15 "FizzBuzz" match result15 with | Carbonated str -> str | Uncarbonated n -> let result3 = n |> carbonate 3 "Fizz" match result3 with | Carbonated str -> str | Uncarbonated n -> // do something with Uncarbonated value
  117. 117. let fizzbuzz n = let result15 = n |> carbonate 15 "FizzBuzz" match result15 with | Carbonated str -> str | Uncarbonated n -> // do something with Uncarbonated value
  118. 118. if Carbonated // return the string if Uncarbonated then // do something with the number
  119. 119. let ifUncarbonatedDo f result = match result with | Carbonated str -> Carbonated str | Uncarbonated n -> f n
  120. 120. let fizzbuzz n = n |> carbonate 15 "FizzBuzz" |> ifUncarbonatedDo (carbonate 3 "Fizz") |> ifUncarbonatedDo (carbonate 5 "Buzz") |> carbonateRemaining let carbonateRemaining result = match result with | Carbonated str -> str | Uncarbonated n -> string(n)
  121. 121. MAKING LOOPS COMPOSABLE
  122. 122. Another composition problem List of numbers FizzBizz for one number 
  123. 123. Solution: the "map" transformer List input List outputFizzBizz for lists FizzBizz for one number List.map
  124. 124. // final version of FizzBuzz let fizzbuzz100 = [1..100] |> List.map fizzBuzz |> List.iter (printfn "%s") // I/O only at end let fizzbuzz n = n |> carbonate 15 "FizzBuzz" |> ifUncarbonatedDo (carbonate 3 "Fizz") |> ifUncarbonatedDo (carbonate 5 "Buzz") |> carbonateRemaining
  125. 125. THE M-WORD
  126. 126. Is there a general solution to handling functions like this?
  127. 127. Yes! “Bind” is the answer! Bind all the things!
  128. 128. Two-track input Two-track output One-track input Two-track output  
  129. 129. Two-track input Two-track output
  130. 130. Two-track input Two-track output
  131. 131. let bind nextFunction result = match result with | Uncarbonated n -> nextFunction n | Carbonated str -> Carbonated str Two-track input Two-track output
  132. 132. let bind nextFunction result = match result with | Uncarbonated n -> nextFunction n | Carbonated str -> Carbonated str Two-track input Two-track output
  133. 133. let bind nextFunction result = match result with | Uncarbonated n -> nextFunction n | Carbonated str -> Carbonated str Two-track input Two-track output
  134. 134. let bind nextFunction result = match result with | Uncarbonated n -> nextFunction n | Carbonated str -> Carbonated str Two-track input Two-track output
  135. 135. FP terminology • A monad is – A data type – With an associated "bind" function – (and some other stuff) • A monadic function is – A switch/points function – "bind" is used to compose them
  136. 136. Example: Using bind to chain tasks
  137. 137. When task completesWait Wait a.k.a "promise", "future"
  138. 138. 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 -> etc
  139. 139. let bind f task = task.WhenFinished (fun taskResult -> f taskResult) let taskExample input = startTask input |> bind startAnotherTask |> bind startThirdTask |> bind ... Rewritten using bind:
  140. 140. let ( >>= ) x f = x |> bind let taskExample input = startTask input >>= startAnotherTask >>= startThirdTask >>= ... Rewritten using >>= A common symbol for "bind"
  141. 141. Example: Composing error-generating functions
  142. 142. Example of scenario with errors Name is blank Email not valid Validate request Canonicalize email Fetch existing user record Update existing user record User not found Db error Authorization error Timeout
  143. 143. Validate Generates a possible error
  144. 144. Validate Canonicalize Generates a possible error Always succeeds
  145. 145. Validate Canonicalize DbFetch Generates a possible error Generates a possible errorAlways succeeds
  146. 146. Validate Canonicalize DbFetch DbUpdate Generates a possible error Generates a possible errorAlways succeeds Doesn't return How can we glue these mismatched functions together?
  147. 147. map Converting everything to two-track
  148. 148. bind map Converting everything to two-track
  149. 149. bind map tee map Converting everything to two-track
  150. 150. Validate Canonicalize DbFetch DbUpdate Now we *can* compose them easily! map bind tee, then map
  151. 151. KLEISLI COMPOSITION: WEB SERVICE
  152. 152. =+ Result is same kind of thing Kleisli Composition
  153. 153. Async<HttpContext option>HttpContext A "WebPart" (suave.io library) See suave.io site for more
  154. 154. =>=> Result is another WebPart so you can repeat WebPart Composition Kleisli composition symbol
  155. 155. path "/hello" >=> OK "Hello" // a WebPart Checks request path (might fail) Sets response
  156. 156. choose [ path "/hello" >=> OK "Hello" path "/goodbye" >=> OK "Goodbye" ] Picks first WebPart that succeeds
  157. 157. GET >=> choose [ path "/hello" >=> OK "Hello" path "/goodbye" >=> OK "Goodbye" ] Only succeeds if request is a GET
  158. 158. let app = choose [ GET >=> choose [ path "/hello" >=> OK "Hello" path "/goodbye" >=> OK "Goodbye" ] POST >=> choose [ path "/hello" >=> OK "Hello POST" path "/goodbye" >=> OK "Goodbye POST" ] ] startWebServer defaultConfig app A complete web app
  159. 159. Http Response Http Request As I promised – no classes, no loops, etc!
  160. 160. Review • The philosophy of composition – Connectable, no adapters, reusable parts • FP principles: – Composable functions – Composable types • Basic composition – Composition with ">>" – Piping with "|>" • Using partial application to help composition • Monads and monadic functions – Composition using "bind" / ">>=" – Kleisli composition using ">=>"
  161. 161. Slides and video here fsharpforfunandprofit.com/composition Thank you! "Domain modelling Made Functional" book fsharpforfunandprofit.com/books @ScottWlaschin Me on twitter fsharpforfunandprofit.com/videos

×