SlideShare a Scribd company logo
INTRODUCTION TO FP
(W/ JAVASCRIPT)
"A monad is just a monoid in the category of
endofunctors..."
Introduction to Functional Programming (w/ JS)
WHY TO FP?
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
Introduction to Functional Programming (w/ JS)
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
READABILITY CURVE
But the journey has its bumps
Source: https://github.com/getify/Functional-Light-JS/blob/master/ch1.md
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
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
FIRST CLASS FUNCTIONS
(OR FUNCTIONS AS VALUES)
The majority of patterns and tools around FP requires functions to
be treated as first-class citizens
Which means they can:
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;
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
];
BE USED AS OTHER FUNCTIONS ARGUMENTS
const hello = () => {
console.log('hello fp');
};
const callFn = fn => fn();
// ...
callFn(hello); // hello fp
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
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
HIGH-ORDER FUNCTIONS & CLOSURES
(OR WORKING WITH STATEFUL FUNCTIONS)
HIGH-ORDER FUNCTIONS
A high-order function is a function that does at least one of the
following:
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);
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!
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
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!!!!!
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
FUNCTION PURITY
(OR AVOIDING SIDE EFFECTS)
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)
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 }
);
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;
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
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
MANAGING FUNCTION INPUT
(OR MANIPULATING FUNCTION ARGUMENTS)
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');
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
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
By combining the power of high-order functions (HoF), knowledge of
function arity and loose arguments application, we can build
powerful abstractions
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]
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]
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
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.
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
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
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
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
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);
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
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
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
FUNCTION COMPOSITION
(OR PLAYING WITH BUILDING BLOCKS)
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);
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);
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)));
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)
);
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
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);
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)
);
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
VALUE IMMUTABILITY
(OR WRITING PREDICTABLE LOGIC)
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
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' };
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?
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
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
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
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
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
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);
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
IMMUTABILITY LIBS
(by facebook)
(port of ClojureScript data structures)
ImmutableJS
mori
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
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
ARRAY OPERATIONS
(OR MORE READABLE LOOPS)
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);
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
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); // [' ', ' ', ' ', ' ', ' ', ' ']
MAP()
Source: https://github.com/getify/Functional-Light-JS/blob/master/ch8.md
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
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); // [' ', ' ', ' ', ' ']
FILTER()
Source: https://github.com/getify/Functional-Light-JS/blob/master/ch8.md
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); // ' '
REDUCE()
Source: https://github.com/getify/Functional-Light-JS/blob/master/ch8.md
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
RECURSION
(OR RECURSION (OR RECURSION...))
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
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);
"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
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
DIVIDE AND CONQUER
Iterative:
Recursive:
const map = (arr, fn) => {
const newArr = [];
for (let i = 0; i < arr.length; i++) {
newArr.push(fn(arr[i]));
}
return newArr;
};
const map = ([firstVal, ...rest], fn) =>
firstVal === undefined ?
[] :
[fn(firstVal), ...map(rest, fn)];
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);
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);
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
};
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)]);
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
MONADS BASICS
(OR THE THING THAT, ONCE UNDERSTOOD, YOU WON'T BE ABLE
TO EXPLAIN)
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
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
});
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));
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));
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.
REVISITING FUNCTORS
handling branches/errors
const success = (value) => ({
map: fn => something(fn(value)),
catch: nothing
});
const error = (e) => ({
map: () => error(e),
catch: fn => something(fn(e))
});
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));
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.
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));
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()
MONADS TO THE RESCUE
Unboxing a box
const something = (value) => ({
map: fn => something(fn(value)),
flatMap: fn => fn(value)
});
const nothing = () => ({
map: nothing,
flatMap: nothing
});
MONADS TO THE RESCUE
Unboxing a box
const success = (value) => ({
map: fn => something(fn(value)),
flatMap: fn => fn(value)
catch: nothing
});
const error = (e) => ({
map: () => error(e),
flatMap: () => error(e),
catch: fn => something(fn(e))
});
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));
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));
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);
}
});
YOU ARE USING MONADS ALREADY
Now it reads a lot better:
future(asyncGetUser(userId))
.then(asyncAttatchPermissions(userId))
.then(asyncAttatchOrders(userId))
.then(showProfile(profileTemplate));
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));
FEEL BETRAYED?
IMPERATIVE VS DECLARATIVE
WHAT THIS CODE IS DOING?
const words = [
'The', 'quick', 'brown', 'fox,', 'jumps', 'over',
'the', 'lazy', 'dog.', '- It', 'was', 'a', 'german',
'shepherd!'
];
let result = false;
for (let i = 0; i < words.length; i++) {
words[i] = words[i].replace(/[ -_:;.,!?]/g, '');
if (words[i].length >= 3 && words[i].length <= 6) {
words[i] = words[i].toUpperCase()
.split('').reverse().join('');
if (words[i] === 'GOD') {
result = true;
break;
}
}
}
if (result) console.log('Found GOD...');
if (words[0] !== 'The') console.log('...and the devil');
AND THIS ONE?
const words = [
'The', 'quick', 'brown', 'fox,', 'jumps', 'over',
'the', 'lazy', 'dog.', '- It', 'was', 'a', 'german',
'shepherd!'
];
const removeInvalidChars = word => word.replace(/[ -_:;.,!?]/g, '');
const enoughChars = word => word.length >= 3;
const canContainGod = word => word.length >= 3 && word.length <= 6;
const toUpperCase = word => word.toUpperCase();
const reverseStr = word => word.split('').reverse().join('');
const toBibleCode = compose(reverseStr, toUpperCase);
const isGod = word => word === 'GOD';
const logIf = (condition, str) = condition && console.log(str);
const result = words
.map(removeInvalidChars)
.filter(canContainGod)
.map(toBibleCode)
.some(isGod);
logIf(result === true, 'Found GOD...');
logIf(words[0] !== 'The', '...and the devil');
ANSWER:
(ALMOST) THE SAME THING
Challenge: Can you spot a difference between both outputs?
DEVELOPER EVOLUTION
WHAT TO FP?
JS FP LIBS
- Functional utilities on top of lodash
- Functional utilities
- Functional utilities
- Suite of functional libraries
lodash/fp
Ramda
functional.js
Folktale
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
CONTACTS
Linkedin:
Github:
Skype:
E-mail:
/in/allanbaptista
m4n3z40
abaptista.daitan
abaptista@daitangroup.com
THANKS
I'm out

More Related Content

What's hot (20)

PDF
PHP7 is coming
julien pauli
 
PPT
Unit 4
siddr
 
PPTX
Php string function
Ravi Bhadauria
 
PDF
Notes5
hccit
 
PDF
Old Oracle Versions
Jeffrey Kemp
 
PDF
Javascript fundamentals for php developers
Chris Ramakers
 
PPTX
How Functions Work
Saumil Shah
 
PDF
perltut
tutorialsruby
 
PDF
C++ aptitude
chetan_p211
 
PPTX
React Js Training In Bangalore | ES6 Concepts in Depth
Siva Vadlamudi
 
PPTX
Exploit Research and Development Megaprimer: Unicode Based Exploit Development
Ajin Abraham
 
PDF
Attributes Unwrapped: Lessons under the surface of active record
.toster
 
PPT
Advance ROP Attacks
n|u - The Open Security Community
 
PPTX
Introduction to Perl Programming
Collaboration Technologies
 
PDF
2021.laravelconf.tw.slides2
LiviaLiaoFontech
 
PDF
The Ring programming language version 1.10 book - Part 97 of 212
Mahmoud Samir Fayed
 
PDF
JavaScript Functions
Colin DeCarlo
 
PDF
Ejemplo completo de integración JLex y CUP
Egdares Futch H.
 
PPTX
An introduction to ROP
Saumil Shah
 
PHP7 is coming
julien pauli
 
Unit 4
siddr
 
Php string function
Ravi Bhadauria
 
Notes5
hccit
 
Old Oracle Versions
Jeffrey Kemp
 
Javascript fundamentals for php developers
Chris Ramakers
 
How Functions Work
Saumil Shah
 
perltut
tutorialsruby
 
C++ aptitude
chetan_p211
 
React Js Training In Bangalore | ES6 Concepts in Depth
Siva Vadlamudi
 
Exploit Research and Development Megaprimer: Unicode Based Exploit Development
Ajin Abraham
 
Attributes Unwrapped: Lessons under the surface of active record
.toster
 
Introduction to Perl Programming
Collaboration Technologies
 
2021.laravelconf.tw.slides2
LiviaLiaoFontech
 
The Ring programming language version 1.10 book - Part 97 of 212
Mahmoud Samir Fayed
 
JavaScript Functions
Colin DeCarlo
 
Ejemplo completo de integración JLex y CUP
Egdares Futch H.
 
An introduction to ROP
Saumil Shah
 

Similar to Introduction to Functional Programming (w/ JS) (20)

PDF
Functional programming 101
Maneesh Chaturvedi
 
PDF
Introduction to Functional Programming
Hoàng Lâm Huỳnh
 
ODP
Functional programming
S M Asaduzzaman
 
PPTX
Functional Programming in Javascript - IL Tech Talks week
yoavrubin
 
PDF
379008-rc217-functionalprogramming
Luis Atencio
 
PDF
Functional Programming with Javascript
Deepankar Chopra
 
PPTX
Functional programming in javascript
Boris Burdiliak
 
PPTX
Основы функционального JS
Анна Луць
 
PDF
Christian Gill ''Functional programming for the people''
OdessaJS Conf
 
PPTX
A Skeptics guide to functional style javascript
jonathanfmills
 
PDF
Functional JavaScript Fundamentals
Srdjan Strbanovic
 
PPTX
An Introduction to Functional Programming with Javascript
Doug Sparling
 
PPT
25-functions.ppt
JyothiAmpally
 
PDF
Functional Programming in JavaScript
Will Livengood
 
PDF
Functional programing in Javascript (lite intro)
Nikos Kalogridis
 
PPTX
Things about Functional JavaScript
ChengHui Weng
 
PPTX
WHY JAVASCRIPT FUNCTIONAL PROGRAMMING IS SO HARD?
reactima
 
PPTX
Why Functional Programming So Hard?
Ilya Sidorov
 
PPTX
Столпы функционального программирования для адептов ООП, Николай Мозговой
Sigma Software
 
PPTX
Why functional programming in C# & F#
Riccardo Terrell
 
Functional programming 101
Maneesh Chaturvedi
 
Introduction to Functional Programming
Hoàng Lâm Huỳnh
 
Functional programming
S M Asaduzzaman
 
Functional Programming in Javascript - IL Tech Talks week
yoavrubin
 
379008-rc217-functionalprogramming
Luis Atencio
 
Functional Programming with Javascript
Deepankar Chopra
 
Functional programming in javascript
Boris Burdiliak
 
Основы функционального JS
Анна Луць
 
Christian Gill ''Functional programming for the people''
OdessaJS Conf
 
A Skeptics guide to functional style javascript
jonathanfmills
 
Functional JavaScript Fundamentals
Srdjan Strbanovic
 
An Introduction to Functional Programming with Javascript
Doug Sparling
 
25-functions.ppt
JyothiAmpally
 
Functional Programming in JavaScript
Will Livengood
 
Functional programing in Javascript (lite intro)
Nikos Kalogridis
 
Things about Functional JavaScript
ChengHui Weng
 
WHY JAVASCRIPT FUNCTIONAL PROGRAMMING IS SO HARD?
reactima
 
Why Functional Programming So Hard?
Ilya Sidorov
 
Столпы функционального программирования для адептов ООП, Николай Мозговой
Sigma Software
 
Why functional programming in C# & F#
Riccardo Terrell
 
Ad

Recently uploaded (20)

DOCX
Python coding for beginners !! Start now!#
Rajni Bhardwaj Grover
 
PPTX
Agentforce World Tour Toronto '25 - Supercharge MuleSoft Development with Mod...
Alexandra N. Martinez
 
PDF
Peak of Data & AI Encore AI-Enhanced Workflows for the Real World
Safe Software
 
PDF
Staying Human in a Machine- Accelerated World
Catalin Jora
 
PDF
Newgen Beyond Frankenstein_Build vs Buy_Digital_version.pdf
darshakparmar
 
PDF
Go Concurrency Real-World Patterns, Pitfalls, and Playground Battles.pdf
Emily Achieng
 
PDF
“Voice Interfaces on a Budget: Building Real-time Speech Recognition on Low-c...
Edge AI and Vision Alliance
 
PPTX
Designing_the_Future_AI_Driven_Product_Experiences_Across_Devices.pptx
presentifyai
 
PDF
Bitcoin for Millennials podcast with Bram, Power Laws of Bitcoin
Stephen Perrenod
 
PDF
AI Agents in the Cloud: The Rise of Agentic Cloud Architecture
Lilly Gracia
 
PDF
POV_ Why Enterprises Need to Find Value in ZERO.pdf
darshakparmar
 
PDF
“NPU IP Hardware Shaped Through Software and Use-case Analysis,” a Presentati...
Edge AI and Vision Alliance
 
PDF
How do you fast track Agentic automation use cases discovery?
DianaGray10
 
PPTX
Digital Circuits, important subject in CS
contactparinay1
 
PDF
Kit-Works Team Study_20250627_한달만에만든사내서비스키링(양다윗).pdf
Wonjun Hwang
 
PDF
Newgen 2022-Forrester Newgen TEI_13 05 2022-The-Total-Economic-Impact-Newgen-...
darshakparmar
 
PDF
What’s my job again? Slides from Mark Simos talk at 2025 Tampa BSides
Mark Simos
 
PDF
Future-Proof or Fall Behind? 10 Tech Trends You Can’t Afford to Ignore in 2025
DIGITALCONFEX
 
PDF
“Squinting Vision Pipelines: Detecting and Correcting Errors in Vision Models...
Edge AI and Vision Alliance
 
PDF
ICONIQ State of AI Report 2025 - The Builder's Playbook
Razin Mustafiz
 
Python coding for beginners !! Start now!#
Rajni Bhardwaj Grover
 
Agentforce World Tour Toronto '25 - Supercharge MuleSoft Development with Mod...
Alexandra N. Martinez
 
Peak of Data & AI Encore AI-Enhanced Workflows for the Real World
Safe Software
 
Staying Human in a Machine- Accelerated World
Catalin Jora
 
Newgen Beyond Frankenstein_Build vs Buy_Digital_version.pdf
darshakparmar
 
Go Concurrency Real-World Patterns, Pitfalls, and Playground Battles.pdf
Emily Achieng
 
“Voice Interfaces on a Budget: Building Real-time Speech Recognition on Low-c...
Edge AI and Vision Alliance
 
Designing_the_Future_AI_Driven_Product_Experiences_Across_Devices.pptx
presentifyai
 
Bitcoin for Millennials podcast with Bram, Power Laws of Bitcoin
Stephen Perrenod
 
AI Agents in the Cloud: The Rise of Agentic Cloud Architecture
Lilly Gracia
 
POV_ Why Enterprises Need to Find Value in ZERO.pdf
darshakparmar
 
“NPU IP Hardware Shaped Through Software and Use-case Analysis,” a Presentati...
Edge AI and Vision Alliance
 
How do you fast track Agentic automation use cases discovery?
DianaGray10
 
Digital Circuits, important subject in CS
contactparinay1
 
Kit-Works Team Study_20250627_한달만에만든사내서비스키링(양다윗).pdf
Wonjun Hwang
 
Newgen 2022-Forrester Newgen TEI_13 05 2022-The-Total-Economic-Impact-Newgen-...
darshakparmar
 
What’s my job again? Slides from Mark Simos talk at 2025 Tampa BSides
Mark Simos
 
Future-Proof or Fall Behind? 10 Tech Trends You Can’t Afford to Ignore in 2025
DIGITALCONFEX
 
“Squinting Vision Pipelines: Detecting and Correcting Errors in Vision Models...
Edge AI and Vision Alliance
 
ICONIQ State of AI Report 2025 - The Builder's Playbook
Razin Mustafiz
 
Ad

Introduction to Functional Programming (w/ JS)

  • 2. "A monad is just a monoid in the category of endofunctors..."
  • 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
  • 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
  • 11. FIRST CLASS FUNCTIONS (OR FUNCTIONS AS VALUES)
  • 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
  • 18. HIGH-ORDER FUNCTIONS & CLOSURES (OR WORKING WITH STATEFUL FUNCTIONS)
  • 19. HIGH-ORDER FUNCTIONS A high-order function is a function that does at least one of the following:
  • 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
  • 31. MANAGING FUNCTION INPUT (OR MANIPULATING FUNCTION ARGUMENTS)
  • 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
  • 48. FUNCTION COMPOSITION (OR PLAYING WITH BUILDING BLOCKS)
  • 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
  • 57. VALUE IMMUTABILITY (OR WRITING PREDICTABLE LOGIC)
  • 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
  • 68. IMMUTABILITY LIBS (by facebook) (port of ClojureScript data structures) ImmutableJS mori
  • 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
  • 71. ARRAY OPERATIONS (OR MORE READABLE LOOPS)
  • 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
  • 82. RECURSION (OR RECURSION (OR RECURSION...))
  • 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
  • 87. DIVIDE AND CONQUER Iterative: Recursive: const map = (arr, fn) => { const newArr = []; for (let i = 0; i < arr.length; i++) { newArr.push(fn(arr[i])); } return newArr; }; const map = ([firstVal, ...rest], fn) => firstVal === undefined ? [] : [fn(firstVal), ...map(rest, fn)];
  • 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
  • 93. MONADS BASICS (OR THE THING THAT, ONCE UNDERSTOOD, YOU WON'T BE ABLE TO EXPLAIN)
  • 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.
  • 99. REVISITING FUNCTORS handling branches/errors const success = (value) => ({ map: fn => something(fn(value)), catch: nothing }); const error = (e) => ({ map: () => error(e), catch: fn => something(fn(e)) });
  • 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()
  • 104. MONADS TO THE RESCUE Unboxing a box const something = (value) => ({ map: fn => something(fn(value)), flatMap: fn => fn(value) }); const nothing = () => ({ map: nothing, flatMap: nothing });
  • 105. MONADS TO THE RESCUE Unboxing a box const success = (value) => ({ map: fn => something(fn(value)), flatMap: fn => fn(value) catch: nothing }); const error = (e) => ({ map: () => error(e), flatMap: () => error(e), catch: fn => something(fn(e)) });
  • 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));
  • 112. IMPERATIVE VS DECLARATIVE WHAT THIS CODE IS DOING?
  • 113. const words = [ 'The', 'quick', 'brown', 'fox,', 'jumps', 'over', 'the', 'lazy', 'dog.', '- It', 'was', 'a', 'german', 'shepherd!' ]; let result = false; for (let i = 0; i < words.length; i++) { words[i] = words[i].replace(/[ -_:;.,!?]/g, ''); if (words[i].length >= 3 && words[i].length <= 6) { words[i] = words[i].toUpperCase() .split('').reverse().join(''); if (words[i] === 'GOD') { result = true; break; } } } if (result) console.log('Found GOD...'); if (words[0] !== 'The') console.log('...and the devil');
  • 115. const words = [ 'The', 'quick', 'brown', 'fox,', 'jumps', 'over', 'the', 'lazy', 'dog.', '- It', 'was', 'a', 'german', 'shepherd!' ]; const removeInvalidChars = word => word.replace(/[ -_:;.,!?]/g, ''); const enoughChars = word => word.length >= 3; const canContainGod = word => word.length >= 3 && word.length <= 6; const toUpperCase = word => word.toUpperCase(); const reverseStr = word => word.split('').reverse().join(''); const toBibleCode = compose(reverseStr, toUpperCase); const isGod = word => word === 'GOD'; const logIf = (condition, str) = condition && console.log(str); const result = words .map(removeInvalidChars) .filter(canContainGod) .map(toBibleCode) .some(isGod); logIf(result === true, 'Found GOD...'); logIf(words[0] !== 'The', '...and the devil');
  • 116. ANSWER: (ALMOST) THE SAME THING Challenge: Can you spot a difference between both outputs?
  • 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