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 Functional Programming Toolkit (NDC Oslo 2019)

2,363 views

Published on

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

The techniques and patterns used in functional programming are very different from object-oriented programming, and when you are just starting out it can be hard to know how they all fit together.

In this big picture talk for FP beginners, I'll present some of the common tools that can be found in a functional programmer's toolbelt; tools such as "map", "apply", "bind", and "sequence". What are they? Why are they important? How are they used in practice? And how do they relate to scary sounding concepts like functors, monads, and applicatives?

Published in: Software
  • I get a 404 on your slides and video link
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

The Functional Programming Toolkit (NDC Oslo 2019)

  1. 1. The Functional Programming Toolkit (NDC Oslo 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. Functional programming is scary
  6. 6. Object oriented programming is scary
  7. 7. These aren't just academic concepts. They are actually useful tools!
  8. 8. Functional programmers have a standard set of tools
  9. 9. map traverse bind return lift
  10. 10. The Functional Toolkit • 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
  11. 11. The Functional Toolkit • 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, lift, zip – Pulling effects out of a list: sequence, traverse
  12. 12. FunctionalToolkit (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
  13. 13. FunctionalToolkit (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 This talk
  14. 14. This talk A whirlwind tour of many sights Don't worry if you don't understand everything
  15. 15. What I'll talk about • The core principles of FP • Function transformers • Some tools in the functional toolkit – map – bind – lift • An example of using all the tools together
  16. 16. Core principles of statically-typed FP Part I
  17. 17. Core principles of (statically-typed) FP Function Functions are things Composition everywhere
  18. 18. Core FP principle: Functions are things Function
  19. 19. The Tunnel of Transformation Function apple -> banana A function is a thing which transforms inputs to outputs
  20. 20. A function is a standalone thing, not attached to a class It can be used for inputs and outputs of other functions
  21. 21. input A function can be an output A function can be an output
  22. 22. output A function can be an input A function can be an input
  23. 23. 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 toolkit are "function transformers" that convert functions to functions
  24. 24. Core FP principle: Composition everywhere
  25. 25. 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
  26. 26. All pieces are designed to be connected
  27. 27. Connect two pieces together and get another "piece" that can still be connected
  28. 28. The pieces are reusable in different contexts
  29. 29. The Lego philosophy let's you make big things!
  30. 30. Can we apply these ideas to programming?
  31. 31. 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
  32. 32. Function composition
  33. 33. Function 1 apple -> banana Function 2 banana -> cherry
  34. 34. >> Function 1 apple -> banana Function 2 banana -> cherry
  35. 35. New Function apple -> cherry Can't tell it was built from smaller functions! Where did the banana go?
  36. 36. Building big things from functions It's compositions all the way up
  37. 37. Low-level operation ToUpper stringstring
  38. 38. 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
  39. 39. Service Use-case UpdateProfileData ChangeProfile Result ChangeProfile Request Service Service
  40. 40. Use-case Web application Http Response Http Request Use-case Use-case
  41. 41. Http Response Http Request A web application built from functions only (no classes!)
  42. 42. 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!)
  43. 43. Composition example in F#
  44. 44. let add1 x = x + 1 let double x = x + x Introducing F#
  45. 45. let add1 x = x + 1 let double x = x + x let square = fun x -> x * x Introducing F#
  46. 46. add1 5 // = 6 double (add1 5) // = 12 square (double (add1 5)) // = 144 How to compose functions together
  47. 47. add1 double square5 6 12 144
  48. 48. 5 |> add1 // = 6 5 |> add1 |> double // = 12 5 |> add1 |> double |> square // = 144 Piping in F#
  49. 49. Problems with composition
  50. 50. Composition works well when the types match up 
  51. 51.  Composition doesn't work well when the types don't match up
  52. 52. Fix: Insert a converter function into the pipeline Converter function
  53. 53. 5 |> add1 |> strLen // ^ error: expecting an int 5 |> add1 |> intToStr |> strLen // ^ fixed! F# example
  54. 54.  What if the types match up but they're wrapped in something? Option< >
  55. 55.  What if the types match up but they're wrapped in something? List< >
  56. 56. FunctionTransformers Part II
  57. 57. FunctionTransformers Part II What do railways have to do with programming?
  58. 58. Receive request Validate request Lowercase the email Update user record in DB Return result to user type Request = { name: string; email: string } "As a user I want to update my name and email address"
  59. 59. string UpdateCustomer() { var request = receiveRequest(); validateRequest(request); lowercaseEmail(request); db.updateDbFromRequest(request); return "OK"; }
  60. 60. string UpdateCustomerWithErrorHandling() { var request = receiveRequest(); var isValidated = validateRequest(request); if (!isValidated) { return "Request is not valid" } lowercaseEmail(request); db.updateDbFromRequest(request); smtpServer.sendEmail(request.Email) return "OK"; }
  61. 61. string UpdateCustomerWithErrorHandling() { var request = receiveRequest(); var isValidated = validateRequest(request); if (!isValidated) { return "Request is not valid" } lowercaseEmail(request); var result = db.updateDbFromRequest(request); if (!result) { return "Customer record not found" } return "OK"; }
  62. 62. string UpdateCustomerWithErrorHandling() { var request = receiveRequest(); var isValidated = validateRequest(request); if (!isValidated) { return "Request is not valid" } lowercaseEmail(request); try { var result = db.updateDbFromRequest(request); if (!result) { return "Customer record not found" } } catch { return "DB error: Customer record not updated" } return "OK"; }
  63. 63. string UpdateCustomerWithErrorHandling() { var request = receiveRequest(); var isValidated = validateRequest(request); if (!isValidated) { return "Request is not valid" } lowercaseEmail(request); try { var result = db.updateDbFromRequest(request); if (!result) { return "Customer record not found" } } catch { return "DB error: Customer record not updated" } return "OK"; }
  64. 64. Use a Result type for error handling
  65. 65. Request SuccessValidate Failure type Result = | Ok of SuccessValue | Error of ErrorValue A choice type, aka sum type
  66. 66. 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
  67. 67. validateInput
  68. 68. Step 1 Step 2 Step 3
  69. 69. Step 1 Step 2 Step 3
  70. 70. Functional flow without error handling let updateCustomer = receiveRequest() |> validateRequest |> lowercaseEmail |> updateDbFromRequest |> returnMessage Before One track
  71. 71. let updateCustomerWithErrorHandling = receiveRequest() |> validateRequest |> lowercaseEmail |> updateDbFromRequest |> returnMessage Functional flow with error handling After Two track
  72. 72. How to implement Railway Oriented Programming
  73. 73. Success! Failure Input ->
  74. 74. Validate UpdateDbon success bypass
  75. 75. Validate UpdateDb
  76. 76. How to compose these functions?
  77. 77. Step 1 Step 2 Step 3 >> >> Composing one-track functions is fine...
  78. 78. Step 1 Step 2 Step 3 >> >> ... and composing two-track functions is fine...
  79. 79. Step 1 Step 2 Step 3   ... but composing switches is not allowed!
  80. 80. How to combine the mismatched functions?
  81. 81. “Bind” is the answer! Bind all the things!
  82. 82. Two-track input Two-track output One-track input Two-track output  
  83. 83. Two-track input Slot for switch function Two-track output
  84. 84. Two-track input Two-track output
  85. 85. let bind nextFunction twoTrackInput = match twoTrackInput with | Ok success -> nextFunction success | Error err -> Error err Two-track input Two-track output
  86. 86. let bind nextFunction twoTrackInput = match twoTrackInput with | Ok success -> nextFunction success | Error err -> Error err Two-track input Two-track output
  87. 87. let bind nextFunction twoTrackInput = match twoTrackInput with | Ok success -> nextFunction success | Error err -> Error err Two-track input Two-track output ©
  88. 88. let bind nextFunction twoTrackInput = match twoTrackInput with | Ok success -> nextFunction success | Error err -> Error err Two-track input Two-track output
  89. 89. Composing switches - review Step 1 Step 2 Step 3 Step 1 Step 2 Step 3
  90. 90. Validation using Bind
  91. 91. Validating input type Input = { Name : string Email : string }
  92. 92. nameLessThan50 let nameNotBlank input = if input.Name = "" then Error "Name must not be blank" else Ok input Let nameLessThan50 input = if input.Name.Length > 50 then Error "Name must not be longer than 50 chars" else Ok input Let emailNotBlank input = if input.Email = "" then Error "Email must not be blank" else Ok input nameNotBlank emailNotBlank
  93. 93. nameNotBlank (composed with) nameLessThan50 (composed with) emailNotBlank nameNotBlank nameLessThan50 emailNotBlank
  94. 94. nameLessThan50 emailNotBlanknameNotBlank nameNotBlank bind nameLessThan50 bind emailNotBlank
  95. 95. request |> nameNotBlank |> Result.bind nameLessThan50 |> Result.bind emailNotBlank name50 emailNotBlanknameNotBlank
  96. 96. validateInput let validateInput input = input |> nameNotBlank |> Result.bind nameLessThan50 |> Result.bind emailNotBlank
  97. 97. validateInput
  98. 98. Shapes vs.Types
  99. 99. FunctionB Result<'TEntity,string> FunctionA
  100. 100. Correct shape AND types match Correct shape, but types don't match
  101. 101. How do single track functions fit this model?
  102. 102. // trim spaces and lowercase let lowercaseEmail input = {input with email = input.email.Trim().ToLower() } lowercaseEmail
  103. 103. Won't compose! UpdateDb EtcValidate LowercaseEmail
  104. 104. Two-track input Slot for one-track function Two-track output
  105. 105. Two-track input Two-track output lowercaseEmaillowercaseEmail
  106. 106. Two-track input Two-track output let map singleTrackFunction twoTrackInput = match twoTrackInput with | Ok s -> Ok (singleTrackFunction s) | Error e -> Error e
  107. 107. let map singleTrackFunction twoTrackInput = match twoTrackInput with | Ok s -> Ok (singleTrackFunction s) | Error e -> Error e Two-track input Two-track output
  108. 108. let map singleTrackFunction twoTrackInput = match twoTrackInput with | Ok s -> Ok (singleTrackFunction s) | Error e -> Error e Two-track input Two-track output
  109. 109. let map singleTrackFunction twoTrackInput = match twoTrackInput with | Ok s -> Ok (singleTrackFunction s) | Error e -> Error e Two-track input Two-track output
  110. 110. let map singleTrackFunction twoTrackInput = match twoTrackInput with | Ok s -> Ok (singleTrackFunction s) | Error e -> Error e Two-track input Two-track output
  111. 111. let map singleTrackFunction twoTrackInput = match twoTrackInput with | Ok s -> Ok (singleTrackFunction s) | Error e -> Error e Two-track input Two-track output
  112. 112. Converting one-track functions UpdateDb EtcValidate LowercaseEmail Will compose
  113. 113. map Converting everything to two-track
  114. 114. bind map Converting everything to two-track These are "function transformers"!
  115. 115. Validate LowercaseEmail DbUpdate Using function transformers so that *can* compose different shaped functions map bind
  116. 116. Understanding "effects" Part III
  117. 117. What is an effect? • A collection type List<_> • A type enhanced with extra data Option<_>, Result<_> • A type that interacts with the outside world Async<_>, Task<_>, Random<_> • A type that carries state State<_>, Parser<_>
  118. 118. What is an effect? • A collection type List<_> • A type enhanced with extra data Option<_>, Result<_> • A type that interacts with the outside world Async<_>, Task<_>, Random<_> • A type that carries state State<_>, Parser<_> We'll focus on three for this talk
  119. 119. "Normal" world vs. "Effects" world
  120. 120. "Normal" world int string bool int -> string int -> bool
  121. 121. "Option" world Option<int> Option<string> Option<bool> Option<int> -> Option<string> Option<int> -> Option<bool>
  122. 122. "List" world List<int> List<string> List<bool> List<int> -> List<string> List<int> -> List<bool>
  123. 123. "Async" world Async<int> Async<string> Async<bool> Async<int> -> Async<string> Async<int> -> Async<bool>
  124. 124. Generic "Effects" world E<int> E<string> E<bool> E<int> -> E<string> E<int> -> E<bool>
  125. 125. Different names, same concept • "Effect" world • "Enhanced" world • "Elevated" world – Because we use the word "lift" a lot!
  126. 126. How to work with effects? Challenge:
  127. 127. Example scenario • Download a URL into a JSON object • Decode the JSON into a Customer DTO • Convert the DTO into a valid Customer • Store the Customer in a database
  128. 128. NormalWorld Result World AsyncWorld Url AsyncResult<Json,Err> Download the json file
  129. 129. World of normal values Result World R<CustomerDto,Err> Json Decode the JSON into a DTO
  130. 130. World of normal values Result World R<name> R<email> CustomerDto Validate the fields of the customer
  131. 131. World of normal values validName validEmail validCustomer Construct a customer from the fields
  132. 132. NormalWorld Result World AsyncWorld Customer AsyncResult<unit,Err> Store the customer in the DB
  133. 133. How do we compose these functions together? None the worlds match up ... ... but we can use the functional toolkit!
  134. 134. Example: Working with Options
  135. 135. World of normal values int string bool World of options Option<int> Option<string> Option<bool>
  136. 136. World of options World of normal values int string bool Option<int> Option<string> Option<bool> 
  137. 137. World of options World of normal values Option<int> Option<string> Option<bool>  int string bool
  138. 138. add1 1 // 2 add1 (Some 1) // error
  139. 139. let add1ToOption opt = if opt.IsSome then let newVal = add1 opt.Value Some newVal else None 
  140. 140. World of options World of normal values add1 
  141. 141. World of options World of normal values add1
  142. 142. Moving functions between worlds with "map" Tool #1
  143. 143. let add1ToOption opt = if opt.IsSome then Some (add1 opt.Value) else None Let's take this code and turn it into a generic, reusable tool
  144. 144. let optionMap f opt = if opt.IsSome then Some (f opt.Value) else None
  145. 145. let optionMap f = fun opt -> if opt.IsSome then Some (f opt.Value) else None
  146. 146. let optionMap f fun opt -> if opt.IsSome then Some (f opt.Value) else None
  147. 147. World of options World of normal values Option<T> -> -> Option<U> optionMap T -> -> U
  148. 148. let add1 x = ... (optionMap add1) (Some 1) 
  149. 149. Example: Working with List world
  150. 150. let add1ToEach aList = let newList = new List() for item in aList do let newItem = add1 item newList.Add(newItem) // return newList 
  151. 151. World of lists World of normal values add1 
  152. 152. let listMap f aList = let newList = new List() for item in aList do let newItem = f item newList.Add(newItem) // return newList Let's make a generic, reusable tool again
  153. 153. let listMap f aList = let newList = new List() for item in aList do let newItem = f item newList.Add(newItem) // return newList
  154. 154. let listMap f = fun aList -> let newList = new List() for item in aList do let newItem = f item newList.Add(newItem) // return newList
  155. 155. World of lists World of normal values listMap List<T> -> -> List<U> T -> -> U
  156. 156. let add1 x = ... (listMap add1) [1;2;3] Q: Why is this any better than writing your own loops every time? A: Because it's a pattern you can learn to recognize.
  157. 157. World of async World of normal values async<T> -> -> async<U> asyncMap T -> -> U We do the same for other worlds too
  158. 158. Guideline: Most wrapped generic types have a “map”. Use it!
  159. 159. Guideline: If you create your own generic type, create a “map” for it.
  160. 160. 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
  161. 161. Moving values between worlds with "return" Tool #2
  162. 162. World of options World of normal values Option<int> Option.return int
  163. 163. let x = 42 let intOption = Some x
  164. 164. World of lists World of normal values List<int> List.return int
  165. 165. let x = 42 let intList = [x]
  166. 166. Chaining world-crossing functions with "bind" Tool #3
  167. 167. What's a world-crossing function?
  168. 168. let range max = [1..max] // int -> List<int>
  169. 169. ListWorld Normal world A world crossing function List<int> int range
  170. 170. let getCustomer id = if customerFound then Some customerData else None // CustomerId -> Option<CustomerData>
  171. 171. OptionWorld Normal world A world crossing function Option<CustomerData> CustomerId getCustomer
  172. 172. Problem: How do you chain world-crossing functions?
  173. 173. let optionExample 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
  174. 174. 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 ) ) )
  175. 175. How can we fix this?
  176. 176. let optionExample 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...
  177. 177. let optionExample 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
  178. 178. let optionExample 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
  179. 179. let optionExample input = let x = doSomething input if x.IsSome then // do something with x.Value // in this block else None Can you see the pattern?
  180. 180. if opt.IsSome then //do something with opt.Value else None
  181. 181. let ifSomeDo f opt = if opt.IsSome then f opt.Value else None
  182. 182. let example input = doSomething input |> ifSomeDo doSomethingElse |> ifSomeDo doAThirdThing |> ifSomeDo ... let ifSomeDo f opt = if opt.IsSome then f opt.Value else None
  183. 183. Some None Input -> Let's revisit the railway analogy
  184. 184. on Some Bypass on None
  185. 185. “Bind” is the answer (again!)
  186. 186. Option input Option output One-track input Option output  
  187. 187. Option input Slot for switch function Option output
  188. 188. Option input Option output
  189. 189. let bind nextFunction optionInput = match optionInput with | Some s -> nextFunction s | None -> None Option input Option output
  190. 190. let bind nextFunction optionInput = match optionInput with | Some s -> nextFunction s | None -> None Option input Option output
  191. 191. let bind nextFunction optionInput = match optionInput with | Some s -> nextFunction s | None -> None Option input Option output
  192. 192. let bind nextFunction optionInput = match optionInput with | Some s -> nextFunction s | None -> None Option input Option output
  193. 193. Pattern: Use "bind" to chain options
  194. 194. let optionExample 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
  195. 195. let optionBind f opt = match opt with | Some v -> f v | None -> None After
  196. 196. let optionExample input = doSomething input |> optionBind doSomethingElse |> optionBind doAThirdThing |> optionBind ... let optionBind f opt = match opt with | Some v -> f v | None -> None No pyramids! Code is linear and clear. After
  197. 197. Pattern: Use "bind" to chain tasks a.k.a "promise" "future"
  198. 198. When task completesWait Wait
  199. 199. 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
  200. 200. let taskBind f task = task.WhenFinished (fun taskResult -> f taskResult) let taskExample input = startTask input |> taskBind startAnotherTask |> taskBind startThirdTask |> taskBind ... After
  201. 201. Why is bind so important? It makes world-crossing functions composable
  202. 202. EffectsWorld Normal World a E<b> Before bind: A diagonal function (world crossing) bind
  203. 203. bind EffectsWorld Normal World a E<b> EffectsWorld Normal World E<a> E<b> After bind: A horizontal function (all in E-world)
  204. 204. bind EffectsWorld Normal World a E<b> EffectsWorld Normal World E<a> E<b>
  205. 205. NormalWorld Effects World "Diagonal" functions can't be composed
  206. 206. NormalWorld Effects World Bind
  207. 207. NormalWorld Effects World Bind
  208. 208. NormalWorld Effects World Bind
  209. 209. NormalWorld Effects World "Horizontal" functions can be composed
  210. 210. 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
  211. 211. TLDR: If you want to chain effects- generating functions in series, use a Monad
  212. 212. Combining effects in parallel with applicatives Tool #4
  213. 213. How to combine effects? Option<T> Option<U>+ Option<T,U>
  214. 214. Some 42 Some "hello"+ Some (42,"hello") Combining options This is what you expect!
  215. 215. Some 42 None+ None Combining options
  216. 216. How to combine Lists? List<T> List<U>+ List<T,U>
  217. 217. [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)
  218. 218. [1,2,3] ["a","b","c"]+ [ (1,"a") (2,"b") (3,"c") ] Combining lists (zip)
  219. 219. The general term for this is "applicative functor" Option, List, Async are all applicatives
  220. 220. 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 liftA2 iv. And apply/return must have sensible implementations – the Applicative Functor laws So why is this useful?
  221. 221. Problem: How to validate multiple fields in parallel?
  222. 222. type Customer = { Name : String50 Email : EmailAddress Birthdate : Date } validateName validateEmail validateBirthdate So we create some validation functions: Each field must be validated
  223. 223. validateName validateEmail validateBirthdate Problem: Validation done in series. So only one error at a time is returned
  224. 224. 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?
  225. 225. World of normal values Result World R<name> R<email> R<bdate>
  226. 226. validName validEmail validDate World of normal values validCustomer We know how to combine the normal values (use a constructor)
  227. 227. Result World R<name> R<email> R<bdate> R<Customer> The output is also in Result world Use the magic of Applicatives!
  228. 228. Introducing "liftA2", "liftA3", etc
  229. 229. ResultWorld NormalWorld Option<T>, Option<U> -> -> Option<V> liftA2 T,U -> -> V "liftA2" is just like map but works on functions with two parameters
  230. 230. ResultWorld NormalWorld Opt<T>,Opt<U>,Opt<V> -> -> Option<W> liftA3 T,U,V -> -> W "liftA3" is just like map but works on functions with three parameters
  231. 231. let dtoToCustomer (dto:CustomerDto) = // get the validated values let nameOrError = validateName dto.name let emailOrError = validateEmail dto.email let birthdateOrError = validateBirthdate dto.birthdate // call the constructor (liftA3 makeCustomer) nameOrError emailOrError birthdateOrError // final output is Result<Customer,ErrMsg list> Here's where the magic happens! What the code looks like
  232. 232. Let's review the tools
  233. 233. 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 – "liftA2", "liftA3" for example
  234. 234. Using all the tools together Part IV
  235. 235. Revisiting the example scenario • Download a URL into a JSON object • Decode the JSON into a Customer DTO • Convert the DTO into a valid Customer • Store the Customer in a database
  236. 236. NormalWorld Result World AsyncWorld Download the json file Url AsyncResult<Json>
  237. 237. World of normal values Result World R<CustomerDto> Json Decode the json
  238. 238. World of normal values Result World R<name> R<email> R<bdate> CustomerDto Validate fields
  239. 239. World of normal values Construct the customer validName validEmail validDate validCustomer
  240. 240. NormalWorld Result World AsyncWorld Customer Store the customer in the DB AsyncResult<unit>
  241. 241. We now have the tools to compose these functions together!
  242. 242. World of normal values ResultWorld R<name> R<email> R<bdate> CustomerDto Validate fields AND create a customer Use Result type for validation R<Customer> Use "lift3"
  243. 243. World of normal values Result World CustomerDto R<Customer> Validate fields AND create a customer We now have a world crossing function from the DTO to the Customer
  244. 244. World of normal values Result World CustomerDto R<Customer> Parse json AND create a customer R<CustomerDto> Json Use "bind" to turn the diagonal functions into horizontal ones Bind Bind
  245. 245. World of normal values Result World R<Customer> Parse json AND create a customer R<CustomerDto>R<Json> Bind Bind
  246. 246. World of normal values Result World Parse json AND create a customer R<Customer>R<Json> Then compose them into one function
  247. 247. let jsonToCustomer jsonOrError = jsonOrError |> Result.bind jsonToCustomerDto |> Result.bind dtoToCustomer What the code looks like It takes much more time to explain than to write it!
  248. 248. NormalWorld Result World AsyncWorld Parse json AND create a customer R<Customer>R<Json> Then lift it up to Async world using map Map
  249. 249. NormalWorld Result World AsyncWorld Parse json AND create a customer AsyncResult<Customer>AsyncResult<Json>
  250. 250. NormalWorld AsyncWorld Customer Store the customer in the DB Use "bind" to turn the diagonal function horizontal AsyncResult<unit> Bind
  251. 251. NormalWorld Result World AsyncWorld AsyncResult<Customer> AsyncResult<unit> Store the customer in the DB Bind
  252. 252. NormalWorld AsyncWorld All steps are now composable Url AsyncResult<Json> AsyncResult<Customer>AsyncResult<Json> AsyncResult<Customer> AsyncResult<unit> Convert JSON to customer Store customer in DB
  253. 253. NormalWorld AsyncWorld All steps are now composable Url AsyncResult<Json> AsyncResult<Customer> AsyncResult<unit>
  254. 254. NormalWorld AsyncWorld All steps are now composable into one single function Url AsyncResult<unit>
  255. 255. let jsonToCustomer jsonOrError = jsonOrError |> Result.bind jsonToCustomerDto |> Result.bind dtoToCustomer let downloadAndStoreCustomer url = url |> downloadFile |> Async.map jsonToCustomer |> AsyncResult.bind storeCustomerInDb What the code looks like The patterns might be unfamiliar but once you get use to them, you can compose code quickly. Again, it takes much more time to explain than to write it!
  256. 256. Language support for monads • F# has computation expressions • Haskell has "do" notation • Scala has "for" comprehensions • C# has "SelectMany"  a.k.a. using "bind" everywhere gets ugly
  257. 257. 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", "lift" and "bind" – Don’t expect to understand them all straight away.
  258. 258. "The Functional ProgrammingToolkit" – 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

×