INTRODUCTION TO FP (W/ JAVASCRIPT)
"A monad is just a monoid in the category of endofunctors..."
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
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

Introduction to Functional Programming (w/ JS)

  • 1.
  • 2.
    "A monad isjust a monoid in the category of endofunctors..."
  • 4.
  • 5.
    SOME FACTS: (THAT MAYBLOW 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? BecauseFP "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 thejourney has its bumps Source: https://github.com/getify/Functional-Light-JS/blob/master/ch1.md
  • 9.
    A PRACTICAL INTROTO 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 INTROTO 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 (ORFUNCTIONS AS VALUES)
  • 12.
    The majority ofpatterns and tools around FP requires functions to be treated as first-class citizens Which means they can:
  • 13.
    BE ASSIGNED TOVARIABLES // 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 TODATA 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 ASOTHER FUNCTIONS ARGUMENTS const hello = () => { console.log('hello fp'); }; const callFn = fn => fn(); // ... callFn(hello); // hello fp
  • 16.
    BE RETURNED FROMOTHER 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 INTROTO 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-orderfunction is a function that does at least one of the following:
  • 20.
    1. TAKES ONEOR 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 AFUNCTION 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 isa 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 LOOKINTO 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 INTROTO 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
  • 25.
  • 26.
    A function isconsidered 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/Ooperation 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 withoutany 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 INTROTO 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 (ORMANIPULATING 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 Parameteris 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 ofparameters 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 thepower of high-order functions (HoF), knowledge of function arity and loose arguments application, we can build powerful abstractions
  • 36.
    FORCING UNARY FUNCTIONS Sometimeswe 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 Wecan 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 afunction 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/bindinga 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 partialfunction 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 alsopossible 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 afunction 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 allfunctions 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 thestrict 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 INTROTO 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 PLAYINGWITH BUILDING BLOCKS)
  • 49.
    When a program/applicationis 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 Whatif 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 alist 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 implementcompose 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 ANDARITY 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 theflow 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 beimplemented 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 INTROTO 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.
  • 58.
    In javascript (andthe 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 FORIMMUTABILITY 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 ACHOICE 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 ACHOICE 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 ACHOICE 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 ACHOICE 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 ALAW 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.freezeonly 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 thesepatterns 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) (portof ClojureScript data structures) ImmutableJS mori
  • 69.
    But if performanceis 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 INTROTO 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.
  • 72.
    ARRAY METHODS VSLOOPS 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 VSLOOPS 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 aHoF 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); // [' ', ' ', ' ', ' ', ' ', ' ']
  • 75.
  • 76.
    A WORD ABOUTTHE 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 aHoF 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); // [' ', ' ', ' ', ' ']
  • 78.
  • 79.
    REDUCE() Array.prototype.reduce is aHoF 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); // ' '
  • 80.
  • 81.
    A PRACTICAL INTROTO 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.
  • 83.
    Recursion is whena 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 itmay 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 achievea 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 Ancommon 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: constmap = (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 Notethat 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 functionstack 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 Tailcall 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 Byrefactoring 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 INTROTO 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 THETHING THAT, ONCE UNDERSTOOD, YOU WON'T BE ABLE TO EXPLAIN)
  • 94.
    But first, someboring 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 somethingand 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 isuseful 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 isuseful 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 weuse 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 constsuccess = (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 constgetUser = 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 weuse 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 constattachPermissions = 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 THERESCUE 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 THERESCUE Unboxing a box const something = (value) => ({ map: fn => something(fn(value)), flatMap: fn => fn(value) }); const nothing = () => ({ map: nothing, flatMap: nothing });
  • 105.
    MONADS TO THERESCUE 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 THERESCUE 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 USINGMONADS 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 USINGMONADS 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 USINGMONADS ALREADY Now it reads a lot better: future(asyncGetUser(userId)) .then(asyncAttatchPermissions(userId)) .then(asyncAttatchOrders(userId)) .then(showProfile(profileTemplate));
  • 110.
    YOU ARE USINGMONADS ALREADY Feeling a déjà vu? Yes! Promise is a Monad! Promise.resolve(asyncGetUser(userId)) .then(asyncAttatchPermissions(userId)) .then(asyncAttatchOrders(userId)) .then(showProfile(profileTemplate));
  • 111.
  • 112.
    IMPERATIVE VS DECLARATIVE WHATTHIS 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');
  • 114.
  • 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 SAMETHING Challenge: Can you spot a difference between both outputs?
  • 117.
  • 118.
  • 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 FPbook, 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
  • 121.
  • 122.