(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?
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. 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. 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. 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. This talk
A whirlwind tour of many sights
Don't worry if you don't understand everything
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
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
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
32. 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
43. 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!)
59. 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"
61. 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";
}
62. 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";
}
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. 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";
}
67. 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
93. 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
107. Two-track input Two-track output
let map singleTrackFunction twoTrackInput =
match twoTrackInput with
| Ok s -> Ok (singleTrackFunction s)
| Error e -> Error e
108. let map singleTrackFunction twoTrackInput =
match twoTrackInput with
| Ok s -> Ok (singleTrackFunction s)
| Error e -> Error e
Two-track input Two-track output
109. let map singleTrackFunction twoTrackInput =
match twoTrackInput with
| Ok s -> Ok (singleTrackFunction s)
| Error e -> Error e
Two-track input Two-track output
110. let map singleTrackFunction twoTrackInput =
match twoTrackInput with
| Ok s -> Ok (singleTrackFunction s)
| Error e -> Error e
Two-track input Two-track output
111. let map singleTrackFunction twoTrackInput =
match twoTrackInput with
| Ok s -> Ok (singleTrackFunction s)
| Error e -> Error e
Two-track input Two-track output
112. let map singleTrackFunction twoTrackInput =
match twoTrackInput with
| Ok s -> Ok (singleTrackFunction s)
| Error e -> Error e
Two-track input Two-track output
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<_>
119. 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
128. 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
153. 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
154. let listMap f aList =
let newList = new List()
for item in aList do
let newItem = f item
newList.Add(newItem)
// return
newList
155. let listMap f =
fun aList ->
let newList = new List()
for item in aList do
let newItem = f item
newList.Add(newItem)
// return
newList
157. 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.
158. World of async
World of normal values
async<T> -> -> async<U>
asyncMap
T -> -> U
We do the same for other worlds too
161. 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
174. 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
175. 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
)
)
)
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
Let's fix this!
There is a pattern we can exploit...
178. 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
179. 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
180. 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?
182. let ifSomeDo f opt =
if opt.IsSome then
f opt.Value
else
None
183. let example input =
doSomething input
|> ifSomeDo doSomethingElse
|> ifSomeDo doAThirdThing
|> ifSomeDo ...
let ifSomeDo f opt =
if opt.IsSome then
f opt.Value
else
None
197. 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
198. let optionBind f opt =
match opt with
| Some v -> f v
| None -> None
After
199. 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
202. 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
203. let taskBind f task =
task.WhenFinished (fun taskResult ->
f taskResult)
let taskExample input =
startTask input
|> taskBind startAnotherTask
|> taskBind startThirdTask
|> taskBind ...
After
204. Why is bind so important?
It makes world-crossing functions
composable
213. 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
214. TLDR: If you want to chain effects-
generating functions in series,
use a Monad
222. The general term for this is
"applicative functor"
Option, List, Async are all applicatives
223. 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?
225. type Customer = {
Name : String50
Email : EmailAddress
Birthdate : Date
}
validateName validateEmail validateBirthdate
So we create some validation functions:
Each field must be validated
227. 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?
228. World of normal values
Result World
R<name> R<email> R<bdate>
234. 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
236. 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
238. 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
244. We now have the tools to compose
these functions together!
245. 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"
246. 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
247. 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
248. World of normal values
Result World
R<Customer>
Parse json AND create a customer
R<CustomerDto>R<Json>
Bind Bind
249. World of normal values
Result World
Parse json AND create a customer
R<Customer>R<Json>
Then compose them into one function
250. 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!
255. 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
258. 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!
259. 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
260. 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.
261. "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