O slideshow foi denunciado.
Utilizamos seu perfil e dados de atividades no LinkedIn para personalizar e exibir anúncios mais relevantes. Altere suas preferências de anúncios quando desejar.

The Functional Programmer's Toolkit (NDC London 2019)

1.528 visualizações

Publicada em

(slides and video at https://fsharpforfunandprofit.com/fptoolkit)

The functional programming community has a number of patterns with strange names such as monads, monoids, functors, and catamorphisms.

In this beginner-friendly talk, we'll demystify these techniques and see how they all fit together into a small but versatile "tool kit".

We'll then see how the tools in this tool kit can be applied to a wide variety of programming problems, such as handling missing data, working with lists, and implementing the functional equivalent of dependency injection.

Publicada em: Software
  • Entre para ver os comentários

The Functional Programmer's Toolkit (NDC London 2019)

  1. 1. The Functional Programming Toolkit (NDC London 2019) @ScottWlaschin fsharpforfunandprofit.com
  2. 2. Why do functional programmers use so many strange words?
  3. 3. Functional programming is scary
  4. 4. Functional programming is scary
  5. 5. Object oriented programming is scary
  6. 6. These aren't just academic concepts. They are actually useful tools!
  7. 7. The FunctionalToolbox
  8. 8. map combine bind return apply
  9. 9. The FunctionalToolbox • Composition • Combination/Aggregation • Iteration • Working with effects – Mixing effects and non-effects – Chaining effects in series – Working with effects in parallel – Pulling effects out of a list
  10. 10. The FunctionalToolbox • Composition: compose • Iteration: fold • Combination/Aggregation: combine & reduce • Working with effects – Mixing effects and non-effects: map & return – Chaining effects in series: bind/flatMap – Working with effects in parallel: apply & zip – Pulling effects out of a list: sequence & traverse
  11. 11. Functional Toolbox (FP jargon version) • Combination/Aggregation: Monoid • Working with effects – Mixing effects and non-effects: Functor – Chaining effects in series: Monad – Working with effects in parallel: Applicative
  12. 12. This talk A whirlwind tour of many sights Don't worry if you don't understand everything
  13. 13. Core principles of statically-typed FP
  14. 14. Core principles of (statically-typed) FP Function Functions are things Composition everywhere
  15. 15. Core FP principle: Functions are things Function
  16. 16. The Tunnel of Transformation Function apple -> banana A function is a thing which transforms inputs to outputs
  17. 17. A function is a standalone thing, not attached to a class It can be used for inputs and outputs of other functions
  18. 18. input A function can be an output A function can be an output
  19. 19. output A function can be an input A function can be an input
  20. 20. input output A function can be a parameter A function can be a parameter You can build very complex systems from this simple foundation! Most of the tools in the functional toolbox are "function transformers" that convert functions to functions
  21. 21. Core FP principle: Composition everywhere
  22. 22. 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
  23. 23. All pieces are designed to be connected
  24. 24. Connect two pieces together and get another "piece" that can still be connected
  25. 25. The pieces are reusable in different contexts
  26. 26. Make big things from small things in the same way
  27. 27. Functional Programming Philosophy • Design functions that do one thing well – Functions can be reused in different contexts • Design functions to work together – Expect the output of every function to become the input to another, as yet unknown, function • Use types to ensure that inputs match outputs
  28. 28. Function composition
  29. 29. Function 1 apple -> banana Function 2 banana -> cherry
  30. 30. >> Function 1 apple -> banana Function 2 banana -> cherry
  31. 31. New Function apple -> cherry Can't tell it was built from smaller functions! Where did the banana go?
  32. 32. Http Response Http Request A web application built from functions only (no classes!)
  33. 33. Http Response Http Request I have a whole talk on "The Power of Composition" at fsharpforfunandprofit.com/composition A web application built from functions only (no classes!)
  34. 34. Combining things with Monoids Tool #1
  35. 35. Mathematics Ahead
  36. 36. 1 + 2 = 3 1 + (2 + 3) = (1 + 2) + 3 1 + 0 = 1 0 + 1 = 1
  37. 37. 1 + 2 = 3 Some things A way of combining them
  38. 38. 2 x 3 = 6 Some things A way of combining them
  39. 39. "a" + "b" = "ab" Some things A way of combining them
  40. 40. concat([a],[b]) = [a; b] Some things A way of combining them
  41. 41. 1 + 2 1 + 2 + 3 1 + 2 + 3 + 4 Is an integer Is an integer A pairwise operation has become an operation that works on lists!
  42. 42. 1 + (2 + 3) = (1 + 2) + 3 Order of combining doesn’t matter 1 + 2 + 3 + 4 (1 + 2) + (3 + 4) ((1 + 2) + 3) + 4 All the same
  43. 43. 1 - (2 - 3) = (1 - 2) - 3 Order of combining does matter
  44. 44. 1 + 0 = 1 0 + 1 = 1 A special kind of thing that when you combine it with something, just gives you back the original something
  45. 45. 42 * 1 = 42 1 * 42 = 42 A special kind of thing that when you combine it with something, just gives you back the original something
  46. 46. "" + "hello" = "hello" "hello" + "" = "hello" “Zero” for strings
  47. 47. • 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!
  48. 48. Hey, this is just the kind of jargon that makes my head hurt! Show me why this is useful!
  49. 49. • 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.
  50. 50. 1 + 2 + 3 + 4 [ 1; 2; 3; 4 ] |> List.reduce (+) We'll see this a lot! Pairwise operations can be converted into operations that work on lists. pairwise op
  51. 51. 1 * 2 * 3 * 4 [ 1; 2; 3; 4 ] |> List.reduce (*) Pairwise operations can be converted into operations that work on lists. pairwise op
  52. 52. "a" + "b" + "c" + "d" [ "a"; "b"; "c"; "d" ] |> List.reduce (+) Pairwise operations can be converted into operations that work on lists. pairwise op
  53. 53. Benefit of Associativity: Divide and conquer, parallelization, and incremental accumulation.
  54. 54. Parallelization: 1 + 2 + 3 + 4
  55. 55. Parallelization: (1 + 2) (3 + 4) 3 + 7
  56. 56. Benefit of Associativity: Divide and conquer, parallelization, and incremental accumulation.
  57. 57. (1 + 2 + 3) Benefit of Associativity: Divide and conquer, parallelization, and incremental accumulation.
  58. 58. (1 + 2 + 3) + 4 Benefit of Associativity: Divide and conquer, parallelization, and incremental accumulation.
  59. 59. (6) + 4 Benefit of Associativity: Divide and conquer, parallelization, and incremental accumulation.
  60. 60. • 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?
  61. 61. Identity element • Benefit: Initial value for empty or missing data
  62. 62. Tip: Simplify aggregation code with monoids
  63. 63. type OrderLine = {Qty:int; Total:float} let orderLines = [ {Qty=2; Total=19.98} {Qty=1; Total= 1.99} {Qty=3; Total= 3.99} ] How to add them up?
  64. 64. type OrderLine = {Qty:int; Total:float} let combine line1 line2 = let newQty = line1.Qty + line2.Qty let newTotal = line1.Total + line2.Total {Qty=newQty; Total=newTotal} orderLines |> List.reduce combine // {Qty=6; Total= 25.96} Any combination of monoids is also a monoid Write a pairwise combiner Profit!
  65. 65. Pattern: Convert non-monoids to monoids
  66. 66. Customer + Customer + Customer Customer Stats + Customer Stats + Customer Stats Reduce Map Not a monoid A monoid Customer Stats (Total)
  67. 67. Hadoop make me a sandwich https://twitter.com/daviottenheimer /status/532661754820829185
  68. 68. Beware: You will soon start seeing monoids everywhere Monoids Everywhere
  69. 69. Metrics guideline: Use counters rather than rates Alternative metrics guideline: Make sure your metrics are monoids • incremental updates • can handle missing data
  70. 70. Many "design patterns" are monoids • Composite Pattern • Null Object Pattern • Composable commands • Closure of operations (DDD) Mark Seemann: http://blog.ploeh.dk/2019/01/28/better-abstractions-revisited/
  71. 71. Is function composition a monoid?
  72. 72. >> Function 1 apple -> banana Function 2 banana -> cherry New Function apple -> cherry Not the same thing. Not a monoid 
  73. 73. >> Function 1 apple -> apple Same thing Function 2 apple -> apple Function 3 apple -> apple A monoid! 
  74. 74. Monoid summary • A set of values and a combining function: combine (aka concat, plus, <>, <+> ) • Closed and associative and has a zero value • Uses: – Reduce a list of values to a single value – Do parallel calculations – Do incremental calculations
  75. 75. Understanding "effects"
  76. 76. What is an effect? • A generic type List<_> • A type enhanced with extra data Option<_>, Result<_> • A type that can change the outside world Async<_>, Task<_>, Random<_> • A type that carries state State<_>, Parser<_>
  77. 77. What is an effect? • A generic type List<_> • A type enhanced with extra data Option<_>, Result<_> • A type that can change the outside world Async<_>, Task<_>, Random<_> • A type that carries state State<_>, Parser<_> We'll focus on three for this talk
  78. 78. "Normal" world vs. "Effects" world
  79. 79. "Normal" world int string bool int -> string int -> bool
  80. 80. "Option" world Option<int> Option<string> Option<bool> Option<int> -> Option<string> Option<int> -> Option<bool>
  81. 81. "List" world List<int> List<string> List<bool> List<int> -> List<string> List<int> -> List<bool>
  82. 82. "Async" world Async<int> Async<string> Async<bool> Async<int> -> Async<string> Async<int> -> Async<bool>
  83. 83. "Effects" world E<int> E<string> E<bool> E<int> -> E<string> E<int> -> E<bool>
  84. 84. How to do stuff in an effects world? Problem:
  85. 85. Example: Working with Options
  86. 86. World of normal values int string bool World of options Option<int> Option<string> Option<bool>
  87. 87. World of options World of normal values int string bool Option<int> Option<string> Option<bool> 
  88. 88. World of options World of normal values Option<int> Option<string> Option<bool>  int string bool
  89. 89. let add42 x = x + 42 add42 1 // 43 add42 (Some 1) // error
  90. 90. let add42ToOption opt = if opt.IsSome then let newVal = add42 opt.Value Some newVal else None 
  91. 91. World of options World of normal values add42 
  92. 92. World of options World of normal values add42
  93. 93. Moving functions between worlds with "map" Tool #2
  94. 94. World of options World of normal values Option<T> -> -> Option<U> Option.map T -> -> U
  95. 95. let add42 x = ... let add42ToOption = Option.map add42 add42ToOption (Some 1) // Some 43 
  96. 96. let add42 x = ... (Option.map add42) (Some 1)
  97. 97. Example: Working with List world
  98. 98. let add42ToEach list = let newList = new List() for item in list do let newItem = add42 item newList.Add(newItem) // return newList 
  99. 99. World of lists World of normal values add42 
  100. 100. World of lists World of normal values List.map List<T> -> -> List<U> T -> -> U
  101. 101. let add42 x = ... let add42ToEach = List.map add42 add42ToEach [1;2;3] // [43;44;45] 
  102. 102. World of async World of normal values async<T> -> -> async<U> Async.map T -> -> U
  103. 103. Guideline: Most wrapped generic types have a “map”. Use it!
  104. 104. Guideline: If you create your own generic type, create a “map” for it.
  105. 105. FP terminology A functor is i. An effect type – e.g. Option<>, List<>, Async<> ii. Plus a "map" function that "lifts" a function to the effects world – a.k.a. select, lift iii. And it must have a sensible implementation – the Functor laws
  106. 106. Moving values between worlds with "return" Tool #3
  107. 107. World of options World of normal values Option<int> Option.return int
  108. 108. let x = 42 let intOption = Some x
  109. 109. World of lists World of normal values List<int> List.return int
  110. 110. let x = 42 let intList = [x]
  111. 111. Chaining world-crossing functions with "bind" Tool #4
  112. 112. What's a world-crossing function?
  113. 113. let range max = [1..max] // int -> List<int>
  114. 114. ListWorld Normal world A world crossing function List<int> int range
  115. 115. let getCustomer id = if customerFound then Some customerData else None // CustomerId -> Option<CustomerData>
  116. 116. OptionWorld Normal world A world crossing function Option<CustomerData> CustomerId getCustomer
  117. 117. Problem: How do you chain world-crossing functions?
  118. 118. let example input = let x = doSomething input if x.IsSome then let y = doSomethingElse (x.Value) if y.IsSome then let z = doAThirdThing (y.Value) if z.IsSome then let result = z.Value Some result else None else None else None
  119. 119. let taskExample input = let taskX = startTask input taskX.WhenFinished (fun x -> let taskY = startAnotherTask x taskY.WhenFinished (fun y -> let taskZ = startThirdTask y taskZ.WhenFinished (fun z -> z // final result ) ) )
  120. 120. let example input = let x = doSomething input if x.IsSome then let y = doSomethingElse (x.Value) if y.IsSome then let z = doAThirdThing (y.Value) if z.IsSome then // do something with z.Value // in this block else None else None else None Let's fix this! There is a pattern we can exploit...
  121. 121. let example input = let x = doSomething input if x.IsSome then let y = doSomethingElse (x.Value) if y.IsSome then let z = doAThirdThing (y.Value) if z.IsSome then // do something with z.Value // in this block else None else None else None
  122. 122. let example input = let x = doSomething input if x.IsSome then let y = doSomethingElse (x.Value) if y.IsSome then // do something with y.Value // in this block else None else None
  123. 123. let example input = let x = doSomething input if x.IsSome then // do something with x.Value // in this block else None Can you see the pattern?
  124. 124. if opt.IsSome then //do something with opt.Value else None
  125. 125. let ifSomeDo f opt = if opt.IsSome then f opt.Value else None
  126. 126. let example input = doSomething input |> ifSomeDo doSomethingElse |> ifSomeDo doAThirdThing |> ifSomeDo ... let ifSomeDo f opt = if opt.IsSome then f opt.Value else None
  127. 127. Some None Input -> Let's use a railway analogy
  128. 128. on Some Bypass on None
  129. 129. >> >> Composing one-track functions is fine...
  130. 130. >> >> ... and composing two-track functions is fine...
  131. 131.   ... but composing switches is not allowed!
  132. 132. How to combine the mismatched functions?
  133. 133. “Bind” is the answer! Bind all the things!
  134. 134. Two-track input Two-track input One-track input Two-track input  
  135. 135. Two-track input Slot for switch function Two-track output
  136. 136. Two-track input Two-track output
  137. 137. let bind nextFunction optionInput = match optionInput with | Some s -> nextFunction s | None -> None Two-track input Two-track output
  138. 138. let bind nextFunction optionInput = match optionInput with | Some s -> nextFunction s | None -> None Two-track input Two-track output
  139. 139. let bind nextFunction optionInput = match optionInput with | Some s -> nextFunction s | None -> None Two-track input Two-track output
  140. 140. let bind nextFunction optionInput = match optionInput with | Some s -> nextFunction s | None -> None Two-track input Two-track output
  141. 141. Pattern: Use bind to chain options
  142. 142. let example input = let x = doSomething input if x.IsSome then let y = doSomethingElse (x.Value) if y.IsSome then let z = doAThirdThing (y.Value) if z.IsSome then let result = z.Value Some result else None else None else None Before
  143. 143. let bind f opt = match opt with | Some v -> f v | None -> None After
  144. 144. let example input = doSomething input |> bind doSomethingElse |> bind doAThirdThing |> bind ... let bind f opt = match opt with | Some v -> f v | None -> None No pyramids! Code is linear and clear. After
  145. 145. Pattern: Use bind to chain tasks a.k.a "promise" "future"
  146. 146. When task completesWait Wait
  147. 147. let taskExample input = let taskX = startTask input taskX.WhenFinished (fun x -> let taskY = startAnotherTask x taskY.WhenFinished (fun y -> let taskZ = startThirdTask y taskZ.WhenFinished (fun z -> z // final result ) ) ) Before
  148. 148. let taskBind f task = task.WhenFinished (fun taskResult -> f taskResult) let taskExample input = startTask input |> taskBind startAnotherTask |> taskBind startThirdTask |> taskBind ... After
  149. 149. Problem: How to handle errors elegantly?
  150. 150. string UpdateCustomerWithErrorHandling() { var request = receiveRequest(); validateRequest(request); canonicalizeEmail(request); db.updateDbFromRequest(request); smtpServer.sendEmail(request.Email) return "OK"; }
  151. 151. string UpdateCustomerWithErrorHandling() { var request = receiveRequest(); var isValidated = validateRequest(request); if (!isValidated) { return "Request is not valid" } canonicalizeEmail(request); db.updateDbFromRequest(request); smtpServer.sendEmail(request.Email) return "OK"; }
  152. 152. string UpdateCustomerWithErrorHandling() { var request = receiveRequest(); var isValidated = validateRequest(request); if (!isValidated) { return "Request is not valid" } canonicalizeEmail(request); var result = db.updateDbFromRequest(request); if (!result) { return "Customer record not found" } smtpServer.sendEmail(request.Email) return "OK"; }
  153. 153. string UpdateCustomerWithErrorHandling() { var request = receiveRequest(); var isValidated = validateRequest(request); if (!isValidated) { return "Request is not valid" } canonicalizeEmail(request); try { var result = db.updateDbFromRequest(request); if (!result) { return "Customer record not found" } } catch { return "DB error: Customer record not updated" } smtpServer.sendEmail(request.Email) return "OK"; }
  154. 154. string UpdateCustomerWithErrorHandling() { var request = receiveRequest(); var isValidated = validateRequest(request); if (!isValidated) { return "Request is not valid" } canonicalizeEmail(request); try { var result = db.updateDbFromRequest(request); if (!result) { return "Customer record not found" } } catch { return "DB error: Customer record not updated" } if (!smtpServer.sendEmail(request.Email)) { log.Error "Customer email not sent" } return "OK"; }
  155. 155. string UpdateCustomerWithErrorHandling() { var request = receiveRequest(); var isValidated = validateRequest(request); if (!isValidated) { return "Request is not valid" } canonicalizeEmail(request); try { var result = db.updateDbFromRequest(request); if (!result) { return "Customer record not found" } } catch { return "DB error: Customer record not updated" } if (!smtpServer.sendEmail(request.Email)) { log.Error "Customer email not sent" } return "OK"; }
  156. 156. Solution: Use a Result type for error handling
  157. 157. Request SuccessValidate Failure type Result = | Ok of SuccessValue | Error of ErrorValue Define a choice type
  158. 158. Request SuccessValidate Failure let validateInput input = if input.name = "" then Error "Name must not be blank" else if input.email = "" then Error "Email must not be blank" else Ok input // happy path
  159. 159. Validate UpdateDb SendEmail
  160. 160. Validate UpdateDb SendEmail
  161. 161. let updateCustomer = receiveRequest() |> validateRequest |> canonicalizeEmail |> updateDbFromRequest |> sendEmail |> returnMessage Before One track
  162. 162. let updateCustomerWithErrorHandling = receiveRequest() |> validateRequest |> canonicalizeEmail |> updateDbFromRequest |> sendEmail |> returnMessage After Two track
  163. 163. Why is bind so important? It makes world-crossing functions composable
  164. 164. EffectsWorld Normal World a E<b> Before bind: A diagonal function (world crossing) bind
  165. 165. bind EffectsWorld Normal World a E<b> EffectsWorld Normal World E<a> E<b> After bind: A horizontal function (all in E-world)
  166. 166. NormalWorld EffectsWorld "Diagonal" functions can't be composed
  167. 167. NormalWorld EffectsWorld Bind
  168. 168. NormalWorld EffectsWorld Bind
  169. 169. NormalWorld EffectsWorld Bind
  170. 170. NormalWorld EffectsWorld "Horizontal" functions can be composed
  171. 171. FP terminology A monad is i. An effect type – e.g. Option<>, List<>, Async<> ii. Plus a return function – a.k.a. pure unit iii. Plus a bind function that converts a "diagonal" (world-crossing) function into a "horizontal" (E- world-only) function – a.k.a. >>= flatMap SelectMany iv. And bind/return must have sensible implementations – the Monad laws
  172. 172. TLDR: If you want to chain effects- generating functions in series, use a Monad
  173. 173. Monads vs. monoids?
  174. 174. = compose with Result is same kind of thing Kleisli Composition
  175. 175. =>=> Result is another monadic function so you can repeat. Kleisli Composition Kleisli composition symbol Sound familiar?
  176. 176. >=> Order not important (Associative) Monoid! >=>( ) ( )>=> >=>
  177. 177. Combining effects in parallel with applicatives Tool #5
  178. 178. How to combine effects? Option<T> Option<U>+ Option<T,U>
  179. 179. Some 42 Some "hello"+ Some (42,"hello") Combining options This is what you expect!
  180. 180. Some 42 None+ None Combining options
  181. 181. How to combine Lists? List<T> List<U>+ List<T,U>
  182. 182. [1,2,3] ["a","b","c"]+ [ (1,"a"), (1,"b"), (1,"c") (2,"a"), (2,"b"), (2,"c") (3,"a"), (3,"b"), (3,"c") ] Combining lists (cross product)
  183. 183. [1,2,3] ["a","b","c"]+ [ (1,"a") (2,"b") (3,"c") ] Combining lists (zip)
  184. 184. The general term for this is "applicative functor" Option, List, Async are all applicatives
  185. 185. FP terminology A applicative (functor) is i. An effect type – e.g. Option<>, List<>, Async<> ii. Plus a return function – a.k.a. pure unit iii. Plus a function that combines two effects into one – a.k.a. <*> apply pair iv. And apply/return must have sensible implementations – the Applicative Functor laws So why is this useful?
  186. 186. Problem: How to validate multiple fields in parallel?
  187. 187. type Customer = { Name : String50 Email : EmailAddress Birthdate : Date } validateName validateEmail validateBirthdate So we create some validation functions: Each field must be validated
  188. 188. validateName validateEmail validateBirthdate Problem: Validation done in series. So only one error at a time is returned
  189. 189. type CustomerDto = { name : string email : string birthdate : string } validateName validateEmail validateBirthdate Combine output Now we do get all errors at once! ... But how to combine them?
  190. 190. World of normal values ResultWorld R<name> R<email> R<bdate>
  191. 191. validName validEmail validDate World of normal values validCustomer We know how to combine the normal values (use a constructor)
  192. 192. ResultWorld R<name> R<email> R<bdate> R<Customer> The output is also in Result world Use the magic of Applicatives!
  193. 193. let createValidCustomer (dto:CustomerDto) = // get the validated values let nameOrError = validateName dto.name let emailOrError = validateEmail dto.email let birthdateOrError = validateBirthdate dto.birthdate // call the constructor makeCustomer <!> nameOrError <*> emailOrError <*> birthdateOrError // final output is Result<Customer,ErrMsg list> Here's where the magic happens! What the code looks like Monoids used here
  194. 194. Let's review the tools
  195. 195. The FunctionalToolbox • "combine" – Combines two values to make another one of the same kind • "reduce" – Reduces a list to a single value by using "combine" repeatedly
  196. 196. The FunctionalToolbox • "map" – Lifts functions into an effects world • "return" – Lifts values into an effects world • "bind" – Converts "diagonal" functions into "horizontal" ones so they can be composed. • "apply" – Combines two effects in parallel – "map2", "lift2", "lift3" are specialized versions of "apply“
  197. 197. Using all the tools together Example
  198. 198. Example scenario • Download a URL into a JSON file • Decode the JSON into a Customer DTO • Convert the DTO into a valid Customer • Store the Customer in a database
  199. 199. NormalWorld ResultWorld AsyncWorld Download the json file Url AsyncResult<Json>
  200. 200. World of normal values ResultWorld R<CustomerDto> Json Decode the json
  201. 201. World of normal values ResultWorld R<name> R<email> R<bdate> CustomerDto Validate fields
  202. 202. World of normal values Construct the customer validName validEmail validDate validCustomer
  203. 203. NormalWorld ResultWorld AsyncWorld Customer Store the customer AsyncResult<unit>
  204. 204. How do we compose these functions together? None the worlds match up ... ... but we can use the functional toolkit!
  205. 205. World of normal values ResultWorld R<name> R<email> R<bdate> CustomerDto Validate fields AND create a customer World of normal values validName validEmail validDate validCustomer We already did this one using applicatives (and monoids)
  206. 206. World of normal values ResultWorld CustomerDto R<Customer> Validate fields AND create a customer We already did this one using applicatives, and monoids
  207. 207. World of normal values ResultWorld CustomerDto R<Customer> Parse json AND create a customer R<CustomerDto> Json Use "bind" to turn the diagonal functions into horizontal ones Bind Bind
  208. 208. World of normal values ResultWorld R<Customer> Parse json AND create a customer R<CustomerDto>R<Json> Bind Bind
  209. 209. World of normal values ResultWorld Parse json AND create a customer R<Customer>R<Json> Then compose them into one function
  210. 210. let processCustomerDto jsonOrError = jsonOrError |> Result.bind decodeCustomerDto |> Result.bind createValidCustomer What the code looks like
  211. 211. NormalWorld ResultWorld AsyncWorld Parse json AND create a customer R<Customer>R<Json> Then lift it up to Async world using map Map
  212. 212. NormalWorld ResultWorld AsyncWorld Parse json AND create a customer AsyncResult<Customer>AsyncResult<Json>
  213. 213. NormalWorld AsyncWorld Customer Store the customer Use "bind" to turn the diagonal function horizontal AsyncResult<unit> Bind
  214. 214. NormalWorld ResultWorld AsyncWorld AsyncResult<Customer> AsyncResult<unit> Store the customer Bind
  215. 215. NormalWorld AsyncWorld All steps are now composable Url AsyncResult<Json> AsyncResult<Customer>AsyncResult<Json> AsyncResult<Customer> AsyncResult<unit>
  216. 216. NormalWorld AsyncWorld All steps are now composable Url AsyncResult<Json> AsyncResult<Customer> AsyncResult<unit>
  217. 217. NormalWorld AsyncWorld All steps are now composable into one single function Url AsyncResult<unit>
  218. 218. let processCustomerDto jsonOrError = jsonOrError |> Result.bind decodeCustomerDto |> Result.bind createValidCustomer let downloadAndStoreCustomer url = url |> downloadFile |> Async.map processCustomerDto |> AsyncResult.bind storeCustomer What the code looks like It takes much longer to explain it than to write it!
  219. 219. In conclusion… • FP jargon is not that scary – Can you see why monads are useful? • The FP toolkit is very generic – FP's use these core functions constantly! • You can now recognize "map", "apply" and "bind" – Don’t expect to understand them all straight away.
  220. 220. "The Functional Programming Toolkit" – Slides and video will be posted at • fsharpforfunandprofit.com/fptoolkit Related talks – "Functional Design Patterns" • fsharpforfunandprofit.com/fppatterns – "The Power of Composition" • fsharpforfunandprofit.com/composition – "Domain Modeling Made Functional" • fsharpforfunandprofit.com/ddd Thanks! Twitter: @ScottWlaschin

×