A presentation that tries to introduce some functional programming's core concepts in a more digestible way. It tries to stay away from all the complicated lingo and math, so the average developer can start his adventures through the dangerous but beautiful realms of functional programming.
5. SOME FACTS:
(THAT MAY BLOW YOUR MIND)
Elegant, readable and simple code makes it hard for bugs to hide
70% of time spent while maintaining code is spent reading it
Global average for a coder's loc written p/ day is ~10
6.
7. WHY TO FP?
Because FP "laws" and tools enables us to write a more:
Readable code
Declarative code
Reusable code
Testable code
In general, a more reliable & maintainable code in the long term
8. READABILITY CURVE
But the journey has its bumps
Source: https://github.com/getify/Functional-Light-JS/blob/master/ch1.md
9. A PRACTICAL INTRO TO FP
We'll go over some "mandatory" concepts:
First Class Functions
High-Order Functions & Closures
Function Purity
Managing Function Input
Function Composition
Value Immutability
Array Operations
Recursion
Monads Basics
10. A PRACTICAL INTRO TO FP
Some "mandatory" concepts:
First Class Functions
High-Order Functions & Closures
Function Purity
Managing Function Input
Function Composition
Value Immutability
Array Operations
Recursion
Monads Basics
12. The majority of patterns and tools around FP requires functions to
be treated as first-class citizens
Which means they can:
13. BE ASSIGNED TO VARIABLES
// anonymous functions
const aFunction = function () {
console.log('hello fp');
};
// or named functions
const aFunction = function aFunctionName() {
console.log('hello fp');
};
// or arrow functions
const aFunction = () => console.log('hello fp');
// or even borrowed methods
const aFunction = someObj.someOtherFunction;
14. BE ASSIGNED TO DATA STRUCTURES
// With objects
const obj = {
methodAnon: function() { },
methodNamed: function aFunctionName() { },
methodArrow: () => { },
methodBorrowed: otherObj.someOtherFunction;
};
// Or with arrays
const arr = [
function() { },
function aFunctionName() { },
() => { },
otherObj.someOtherFunction
];
15. BE USED AS OTHER FUNCTIONS ARGUMENTS
const hello = () => {
console.log('hello fp');
};
const callFn = fn => fn();
// ...
callFn(hello); // hello fp
16. BE RETURNED FROM OTHER FUNCTIONS
const getHello = () => {
return () => {
console.log('hello fp');
};
};
// or the shorter
const getHello = () => () => console.log('hello fp');
// ...
const hello = getHello();
hello(); // hello fp
// or in one go
getHello()(); // hello fp
17. A PRACTICAL INTRO TO FP
Some "mandatory" concepts:
First Class Functions
High-Order Functions & Closures
Function Purity
Managing Function Input
Function Composition
Value Immutability
Array Operations
Recursion
Monads Basics
20. 1. TAKES ONE OR MORE FUNCTIONS AS
ARGUMENTS
Useful to separate concerns and abstract/decouple logic
const highOrderSecret = (fnArg) => {
const secret = 'FP rulez!';
fnArg(secret);
};
const logSecret = (secret) => console.log(secret);
const saveSecret = (secret) => secretStorage.add(secret);
// ...
highOrderSecret(logSecret); // FP rulez!
highOrderSecret(saveSecret);
21. 2. RETURNS A FUNCTION AS ITS RESULT
Useful to "hide" state (achieve privacy), persist state to be
processed/used later and compose/add behaviour to other
functions
const makeSecret = () => {
const secret = 'FP rulez!';
return () => secret; // Btw, this is a closure
};
const getSecret = makeSecret();
console.log(getSecret()); // FP rulez!
22. CLOSURES
A closure is a function that refers to "free variables" (variables
defined in parent scopes)
In other words, it's a function that "remembers" the
state/environment where it was created
23. A CLOSER LOOK INTO A CLOSURE
// global scope
const makeSecret = () => {
// scope 0
const secret = 'FP rulez';
// following will log undefined because parent a scope
// does not have access to child scopes
console.log(secretSuffix); // ReferenceError: secretSuffix is not defined
return () => {
// scope 1
const secretSuffix = '!!!!!';
return secret + secretSuffix;
};
};
console.log(secret); // ReferenceError: secret is not defined
const getSecret = makeSecret();
// It remembers its own scope plus parent scopes
console.log(getSecret()); // FP rulez!!!!!
24. A PRACTICAL INTRO TO FP
Some "mandatory" concepts:
First Class Functions
High-Order Functions & Closures
Function Purity
Managing Function Input
Function Composition
Value Immutability
Array Operations
Recursion
Monads Basics
26. A function is considered pure if it does not break the following
"laws":
1. Always has to return the same output given the same input
2. Does not depend on/causes any side effect (state mutations, I/O
operations)
27. PURE FUNCTIONS
const add = (a, b) => a + b;
const getCircleArea = r => Math.PI * r * r;
const getFullName = (first, last) => `${first} ${last}`;
const logUserIn = user => Object.assign(
{},
user,
{ loggedIn: true }
);
28. IMPURE FUNCTIONS
// I/O operation
const logMsg = msg => console.log(msg);
// Different outputs, same input
const getRandom = (max) => Math.random() * max;
// depends on mutable state
const getFullName = (first, last) =>
`${globalNamePrefix} ${first} ${last}`;
// Mutating object state
const logUserIn = user => user.loggedIn = true;
29. A program without any observable side effect is also a program that
accomplishes nothing useful
but, side effects should be avoided where possible
as they make programs hard to follow/read, hard to test and hard to
maintain
most of a program codebase should be composed of small, single-
purpose and pure functions
30. A PRACTICAL INTRO TO FP
Some "mandatory" concepts:
First Class Functions
High-Order Functions & Closures
Function Purity
Managing Function Input
Function Composition
Value Immutability
Array Operations
Recursion
Monads Basics
32. ARGS VS PARAMS
Question: what's the difference between arguments and
parameters?
// firstName, middleName and lastName are parameters
const getFullName = (firstName, middleName, lastName) =>
`${firstName} ${middleName} ${lastName}`;
// All strings passed into getFullName() call are arguments
getFullName('Allan', 'Marques', 'Baptista');
// arguments < parameters - perfectly valid in JS
getFullName('Emperor', 'Palpatine');
// arguments > parameters - also valid
getFullName('Some', 'Big', 'Ass', 'Freaking', 'Name');
33. ARGS VS PARAMS
Parameter is the variable which is part of the function signature
Argument is the value/variable/reference/expression being passed
in during a function call
34. ARITY
The number of parameters a function expects in its signature is
called arity
It's possible to get a function's arity through the
Function.prototype.length property
const double = n => n * 2; // arity = 1 (unary)
// arity = 3 (ternary)
const getFullName = (firstName, middleName, lastName) =>
`${firstName} ${middleName} ${lastName}`;
const double = n => n * 2;
console.log(double.length); // 1
35. By combining the power of high-order functions (HoF), knowledge of
function arity and loose arguments application, we can build
powerful abstractions
36. FORCING UNARY FUNCTIONS
Sometimes we need to ensure a function that expects more than
one parameter to receive only one argument
That happens because parseInt's signature is:
parseInt(str [, radix])
And Array.prototype.map calls any function passed in with
the arguments:
fn(item, index, arr)
const strArr = ['1', '2', '3', '4', '5'];
const mumArr = strArr.map(parseInt);
console.log(numArr); // [1, NaN, NaN, NaN, NaN]
37. FORCING UNARY FUNCTIONS
We can fix that with a utility HoF usually called unary
That can be implemented in JS like so:
And used like this:
const unary = fn =>
param => fn(param);
const strArr = ['1', '2', '3', '4', '5'];
const mumArr = strArr.map(unary(parseInt));
console.log(numArr); // [1, 2, 3, 4, 5]
38. PARTIAL APPLICATION
Calling a function and passing some arguments to it like:
foo(bar, baz);
can also be described as applying function foo to the arguments
bar and baz
39. PARTIAL APPLICATION
Means fixing/binding a number of arguments to a function
producing another function with smaller arity
It's useful when we know some of the arguments that'll be applied
to a function ahead of time
But the rest of the arguments we'll only know at a later point in
execution time.
40. PARTIAL APPLICATION
A partial function application utility can easily be implemented like
so:
And it's used like this:
const partial = (fn, ...eagerArgs) =>
(...lazyArgs) => fn(...eagerArgs, ...lazyArgs);
const fullName = (preferedTreatment, firstName, lastName) =>
`${preferedTreatment} ${lastName}, ${firstName}`;
const maleName = partial(fullName, 'Sir');
const femaleName = partial(fullName, 'Ma'am');
maleName('Allan', 'Baptista'); // Sir Baptista, Allan
femaleName('Nadia', 'Carvalho'); // Ma'am Carvalho, Nadia
41. PARTIAL APPLICATION
It's also possible to implement a utility that partially applies the
final arguments like so:
That can be used like this:
const partialRight = (fn, ...rightArgs) =>
(...leftArgs) => fn(...leftArgs, ...rightArgs);
const fullName = (preferedTreatment, firstName, lastName) =>
`${preferedTreatment} ${lastName}, ${firstName}`;
const kirk = partialRight(fullName, 'James', 'Kirk');
kirk('Sir'); // Sir Kirk, James
kirk('Captain'); // Captain Kirk, James
42. CURRYING
It's creating a function that only executes its actual logic once it has
gathered all parameters it expects
When a curried function is applied to less arguments than its arity it
returns another function
And it keeps returning another function until all arguments are
provided
43. CURRYING
const curriedFullName = preferedTreatment =>
firstName =>
lastName =>
`${preferedTreatment} ${lastName}, ${firstName}`;
const getName = curriedFullName('Mr'); // preferedTreatment = 'Mr'
const getLastName = getName('James'); // firstName = 'James'
getLastName('Bond'); // Mr. Bond, James
// or in one go
curriedFullName('Sir')('Leonard')('Nimoy'); // Sir Nimoy, Leonard
44. CURRYING
In Haskell all functions are curried by default, but in javascript we
need to write a utility function to achieve the same
const autoCurry = (fn, arity = fn.length) =>
(...args) =>
args.length >= arity ?
fn(...args) :
autoCurry(partial(fn, ...args), arity - args.length);
45. CURRYING
const curriedFullName = autoCurry(
(preferedTreatment, firstName, lastName) =>
`${preferedTreatment} ${lastName}, ${firstName}`
);
const getName = curriedFullName('Mr'); // preferedTreatment = 'Mr'
const getLastName = getName('James'); // firstName = 'James'
getLastName('Bond'); // Mr. Bond, James
// or
curriedFullName('Sir')('Leonard')('Nimoy'); // Sir Nimoy, Leonard
// or
curriedFullName('Sir')('Rowan', 'Atkinson'); // Sir Atkinson, Rowan
// or
curriedFullName('Mr', 'Mickey', 'Mouse'); // Mr Mouse, Mickey
46. CURRYING
Note that the strict implementation of currying produces only
unary functions a er each call
So the implementation showed here should be called loose
currying, which is o en more useful
47. A PRACTICAL INTRO TO FP
Some "mandatory" concepts:
First Class Functions
High-Order Functions & Closures
Function Purity
Managing Function Input
Function Composition
Value Immutability
Array Operations
Recursion
Monads Basics
49. When a program/application is well split into simple, single-
purpose and pure functions a repeating pattern starts to come up:
And to avoid repetition, it's common to create composed
abstractions:
const outputData = freeze(enhance(escape(inputData)));
const transformData = data => freeze(enhance(escape(data)));
// later somewhere...
const outputData = transformData(inputData);
// and even later...
const dataToPersist = transformData(inputData);
50. A BETTER WAY
What if there was a way to achieve the same thing in a declarative
way?
const transformData = compose(freeze, enhance, escape);
// later somewhere...
const outputData = transformData(inputData);
51. compose(...fns) takes a list of functions
and returns another function that applies each function from right
to le , so:
// This
const transformData = compose(freeze, enhance, escape);
transformData(...args);
// is the same as this
const escaped = escape(...args);
const enhanced = enhance(escaped);
const outputData = freeze(enhanced);
// or this
const outputData = freeze(enhance(escape(...args)));
52. One can implement compose in JS like so:
const compose = (...fns) =>
(...args) => fns
.slice(0, -1)
.reduceRight(
(res, fn) => fn(res),
fns[fns.length - 1](...args)
);
53. FUNCTION COMPOSITION AND ARITY
Note that all functions besides the first one to be applied are
expected to be unary
as it's not possible to return more the one value from a function
By combining the concepts of function composition, arity and input
management one can build very complex logic in a very declarative
way
54. PIPING
Sometimes reading the flow of data from right to le can be
counter-intuitive
to fix that, we can build a variation of compose that applies each
function from le to right
that variation is usually called pipe or pipeline
const transformData = pipe(escape, enhance, freeze);
// later somewhere...
const outputData = transformData(inputData);
55. PIPING
pipe can be implemented like so:
Other than the difference on how data flows compose and pipe
works in the same way
(Except this implementation o pipe is a little bit more performant than compose's implementation showed
before)
const pipe = (firstFn, ...restFns) =>
(...args) => restFns.reduce(
(res, fn) => fn(res),
firstFn(...args)
);
56. A PRACTICAL INTRO TO FP
Some "mandatory" concepts:
First Class Functions
High-Order Functions & Closures
Function Purity
Managing Function Input
Function Composition
Value Immutability
Array Operations
Recursion
Monads Basics
58. In javascript (and the majority of hybrid/OO languages)
immutability is usually not natively enforced on objects
Some may naively think assigning objects with the const keyword
prevents objects from being mutated
59. But in fact, const only prevents the variable from being re-
assigned
const config = { cannotChange: 'Never changed' };
config.cannotChange = 'Chaos';
console.log(config); // { cannotChange: 'Chaos' }
// but the following throws a TypeError
config = { cannotChange: 'Invalid' };
60. THE CASE FOR IMMUTABILITY
Mutating an object is a side effect
Mutable objects are hard to follow/read
Mutable objects are hard to predict
Mutable objects o en are the source of hard-to-find bugs
Mutable objects are hard to debug
So, if immutability is not enforced natively by the language, how do
we achieve it?
61. IMMUTABILITY AS A CHOICE
PLAIN OBJECTS
// Very bad
const logIn = user => {
user.loggedIn = true;
return user;
};
const loggedUser = logIn(anonymousUser);
console.log(loggedUser.loggedIn); // true
console.log(anonymousUser.loggedIn); // true
62. IMMUTABILITY AS A CHOICE
PLAIN OBJECTS
Pattern: copy objects and mutate the copy
// Good
const logIn = user => {
const userCopy = Object.assign({}, user);
userCopy.loggedIn = true;
return userCopy;
};
const loggedUser = logIn(anonymousUser);
console.log(loggedUser.loggedIn); // true
console.log(anonymousUser.loggedIn); // false
63. IMMUTABILITY AS A CHOICE
ARRAYS
// Very bad
const addTask = (taskList, task) => {
taskList.push(add);
return taskList;
};
const newTaskList = addTask(taskList, task);
console.log(newTaskList.length); // 10
console.log(taskList.length); // 10
64. IMMUTABILITY AS A CHOICE
ARRAYS
Pattern: avoid mutable methods (push, pop, shi , unshi , splice,
sort, fill, reverse)
instead use immutable methods (concat, slice, map, filter) or the
spread notation
// Good
const addTask = (taskList, task) => {
// or [...taskList, task];
return taskList.concat(task);
};
const newTaskList = addTask(taskList, task);
console.log(newTaskList.length); // 10
console.log(taskList.length); // 9
65. IMMUTABILITY AS A LAW
Object.freeze freezes an object, preventing it from being
mutated (works w/ arrays as well)
Pattern: combine Object.freeze with other immutable
patterns to achieve full immutability
const user = Object.freeze({ name: 'Elza' });
user.name = 'Evil'; // throws if in 'strict mode'
console.log(user.name); // Elza
66. Note that Object.freeze only freezes objects shallowly
So to achieve full immutability all child objects also need to be
frozen
const user = Object.freeze({
name: {
first: 'Elza',
last: 'Arendelle'
}
});
user.name.first = 'Evil';
// { first: 'Evil', last: 'Arendelle' }
console.log(user.name);
67. Note that these patterns are much less performant than its mutable
counterpart
Even more if we're dealing with deep nested objects
If you need immutability as well as performance maybe it's time to
bring a library in
69. But if performance is still an issue, you should think about
replacing parts of your code with mutable patterns
but remember:
"Premature optimization is the root of all evil"
- Donald Knuth
70. A PRACTICAL INTRO TO FP
Some "mandatory" concepts:
First Class Functions
High-Order Functions & Closures
Function Purity
Managing Function Input
Function Composition
Value Immutability
Array Operations
Recursion
Monads Basics
72. ARRAY METHODS VS LOOPS
const activeItems = [];
for (let i = 0; i < arr.length; i++) {
if (arr[i].active === true) {
activeItems.push(arr[i]);
}
}
// vs
const activeItems = arr.filter(item => item.active === true);
73. ARRAY METHODS VS LOOPS
Array methods are usually better because:
Traversal logic is abstracted
Terser, more readable and declarative code
Functions and all its goodness!
Loops are better when:
Performance is needed (still, very questionable)
Need to break out of loop early
74. MAP()
Array.prototype.map is a HoF that traverses the list applying
the provided operator function to each item
and produces a new array with the values returned from each
operator call
const bananas = [' ', ' ', ' ', ' ', ' ', ' '];
const mix = bananas.map((banana, index) => (
index % 2 === 0 ? ' ' : banana
));
console.log(mix); // [' ', ' ', ' ', ' ', ' ', ' ']
76. A WORD ABOUT THE FEARED FUNCTOR
In FP terminology, a Functor is a wrapper object that has a utility
method for applying an operator function to its wrapped value
returning a new Functor wrapping the new value produced by the
operator
If the wrapped value is compound the Functor applies the operator
to each indidual value instead
All this is just a fancy way of saying that Functor is just an object
that has a map method
77. FILTER()
Array.prototype.filter is a HoF that traverses the list
applying the provided predicate function to each item
and produces a new array with the values of which the predicate
function returned truthy
const badDiet = [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '];
const goodDiet = badDiet.filter(food => !food.includes(' '));
console.log(goodDiet); // [' ', ' ', ' ', ' ']
79. REDUCE()
Array.prototype.reduce is a HoF that traverses the list
applying the provided reducer function to the previous returned
value and current value
And produces whatever the last reducer call returns
const people = [' ', ' ', ' ', ' '];
const family = people.reduce((str, person) => (
str === '' ?
person :
str + 'u200D' + person
), '' /* <- initial value */);
console.log(family); // ' '
81. A PRACTICAL INTRO TO FP
Some "mandatory" concepts:
First Class Functions
High-Order Functions & Closures
Function Purity
Managing Function Input
Function Composition
Value Immutability
Array Operations
Recursion
Monads Basics
83. Recursion is when a function calls itself until a base condition is
satisfied
const fib = n =>
n <= 1 ?
n :
fib(n - 2) + fib(n - 1);
fib(10); // 55
84. DECLARATIVE ITERATIONS
Although it may be less performant, expressing repetition with
recursion is usually more readable because of its declarative nature
const sum = (...values) => {
let total = 0;
for(let i = 0; i < values.length; i++) {
total += values[i];
}
return total;
};
// vs
const sum = (firstValue, ...otherValues) =>
otherValues.length === 0 ?
firstValue :
firstValue + sum(...otherValues);
85. "Loops may achieve a performance gain for your
program. Recursion may achieve a performance
gain for your programmer. Choose which is
more important in your situation!"
- Leigh Caldwell
86. DIVIDE AND CONQUER
An common strategy to apply when creating a recursive functions is
taking the divide and conquer approach:
Treat every list as a pair containing the first value and a list with
the rest of the values
Define the base condition
Define logic around the first value
Apply the function itself to the rest of the values
88. DIVIDE AND CONQUER
Note that this technique doesn't only work with lists. Lists are just
easier to wrap you head around the concept
This approach can be applied to almost anything
const iterativeRangeSum = (start, end) => {
let result = start;
for (let i = start; i < end; i++) {
result += i;
}
return result;
}
// vs
const recursiveRangeSum = (start, end) =>
start === end ?
0 :
start + recursiveRangeSum(start, end - 1);
89. STACK OVERFLOW
The function stack is a limited resource
If the base condition of a recursive function is not met until the
environment runs out of stack frames
The program/application will crash and burn
Always ensure the base condition will be satisfied before that or
refactor the function to use the benefits of tail call optimization
// If list has something like 200 items or more,
// Stack Overflow!
const values = recursiveMap(bigList, item => item.value);
90. TAIL CALL OPTIMIZATION
Tail call is when a function call is the very last thing evaluated inside
a function
When this happens the compiler can optimize the runtime by
reusing the last stack frame
const foo = () => {
const value = 'tail call';
return bar(value); // <- bar is being tail called
};
91. TAIL CALL OPTIMIZATION
By refactoring the recursive map utility from:
To use TCO:
map() will now support mapping over lists of any size
const map = ([firstVal, ...rest], fn) =>
firstVal === undefined ?
[] :
[fn(firstVal), ...map(rest, fn)];
const map = ([firstVal, ...rest], fn, result = []) =>
firstVal === undefined ?
result :
map(rest, fn, [...result, fn(firstVal)]);
92. A PRACTICAL INTRO TO FP
Some "mandatory" concepts:
First Class Functions
High-Order Functions & Closures
Function Purity
Managing Function Input
Function Composition
Value Immutability
Array Operations
Recursion
Monads Basics
94. But first, some boring topics that won't be covered:
(but you should at some point)
Abstract Algebra
Category Theory
Type Theory
Some people say these fundamental topics are mandatory to start
FPing
I disaggree. But when you feel ready to dive into the theory behind
FP, it's' recommended you do so
95. REVISITING FUNCTORS
Meet something and nothing:
Note: the names of this Functor implementations vary a lot
(Some/None, Just/Nothing, Something/Empty...)
That happens because Functors, like any other FP type, is like a loose interface
Implementations must respect the type laws but their names are not enforced
const something = (value) => ({
map: fn => something(fn(value))
});
const nothing = () => ({
map: nothing
});
96. REVISITING FUNCTORS
something is useful
const getUser = userId => {
const user = repository.findUserById(userId);
return user ? something(user) : nothing();
}
// now we can write
// beautiful, idiomatic, declarative code
getUser(existingId) // User exists
.map(attachPermissions(permissions))
.map(attachOrders(orders))
.map(showProfile(template));
97. REVISITING FUNCTORS
nothing is useful
It's the same exact code, but nothing happens. Not even an
exception!
const getUser = userId => {
const user = repository.findUserById(userId);
return user ? something(user) : nothing();
}
// now we can write
// beautiful, idiomatic, declarative code
getUser(nonExistantId) // User is not found
.map(attachPermissions(permissions))
.map(attachOrders(orders))
.map(showProfile(profileTemplate));
98. REVISITING FUNCTORS
Maybe?
When we use the Something and Nothing Functors together in a
function like this, we're actually implementing the Maybe Functor.
It allows easier and safer null/empty checks without sacrificing
readability.
100. REVISITING FUNCTORS
handling branches/errors
const getUser = userId => {
const user = repository.findUserById(userId);
return user ? success(user) : error(new Error('User not found'));
}
// now we can write
// beautiful, idiomatic, declarative code
getUser(nonExistantId) // User is not found
.map(attachPermissions(permissions))
.map(attachOrders(orders))
.map(showProfile(profileTemplate))
// error branch executed when is error
.catch(showError(errorTemplate));
101. REVISITING FUNCTORS
Either?
When we use the Error and Success together in a function like this,
we're actually implementing the Either Functor.
Its strict implementation is more complex, but the core concept is
the same.
In the strict implementation of the Either Functor, the Error and Success Functors are o en called Le and Right
respectively.
102. REVISITING FUNCTORS
Containing containers
const attachPermissions = permissions => user =>
permissions ?
something(user.setPermissions(permissions)) :
nothing();
const attachOrders = orders => user =>
orders ?
something(user.setOrders(orders)) :
nothing();
getUser(nonExistantId)
.map(attachPermissions(permissions)) // something(something(user))
.map(attachOrders(orders))// Error: setOrders is not a function
.map(showProfile(profileTemplate))
.catch(showError(errorTemplate));
103. MONADS TO THE RESCUE
Much like the Functor, the Monad has a utility method for applying
an operator function to its wrapped value
But unlike the Functors map() utility, it does not wrap the output
value in a new monad
Because it expects the value to be already wrapped in a monad
This utility function has many names: bind(), chain() and
flatMap()
106. MONADS TO THE RESCUE
Unboxing a box
const attachPermissions = permissions => user =>
permissions ?
something(user.setPermissions(permissions)) :
nothing();
const attachOrders = orders => user =>
orders ?
something(user.setOrders(orders)) :
nothing();
getUser(nonExistantId)
.flatMap(attachPermissions(permissions))// something(user)
.flatMap(attachOrders(orders))// something(user)
.map(showProfile(profileTemplate))
.catch(showError(errorTemplate));
107. YOU ARE USING MONADS ALREADY
Let's create a fictional monad that wraps a future value
That can be used like this:
const future = futureValue => ({
map: fn => future(fn(futureValue)),
flatMap: fn => fn(futureValue)
});
future(asyncGetUser(userId))
.flatMap(asyncAttatchPermissions(userId))
.flatMap(asyncAttatchOrders(userId))
.map(showProfile(profileTemplate));
108. YOU ARE USING MONADS ALREADY
But map() and flatMap() are not very meaningful when dealing
with future values
What if we "merged" and renamed them?
const future = futureValue => ({
then: fn => {
const nextFutureValue = fn(futureValue);
const isFutureMonad = (
nextFurureValue &&
typeof nextFutureValue.then === 'function'
);
return isFutureMonad ?
nextFutureValue :
future(nextFutureValue);
}
});
109. YOU ARE USING MONADS ALREADY
Now it reads a lot better:
future(asyncGetUser(userId))
.then(asyncAttatchPermissions(userId))
.then(asyncAttatchOrders(userId))
.then(showProfile(profileTemplate));
110. YOU ARE USING MONADS ALREADY
Feeling a déjà vu?
Yes! Promise is a Monad!
Promise.resolve(asyncGetUser(userId))
.then(asyncAttatchPermissions(userId))
.then(asyncAttatchOrders(userId))
.then(showProfile(profileTemplate));
119. JS FP LIBS
- Functional utilities on top of lodash
- Functional utilities
- Functional utilities
- Suite of functional libraries
lodash/fp
Ramda
functional.js
Folktale
120. RESOURCES
- Great FP book, greatly inspired
this talk
- Awesome FP book, dives into
theory more
- A must read for every JS developer
- List of resources about FP in JS
Functional Light JS (by getify)
Mostly adequate guide to FP
JavaScript Allongé
Awesome FP JS