3. Outline
• Thinking functionally
– What and why
– Paradigm shift
– FP vs OO
• Get functional
– Declarative programming
– Side effects and referential transparency
– Currying
– Composition
• Lift your functional skills
– Memoization
– Monads
4. What is it?
“Functional programming refers to the
declarative evaluation of pure functions to
create immutable programs by avoiding
externally observable side effects.”
5. Why?
• Reduce complexity
• Create code that is easier to trace, debug, and
test
• Modularize code
• Avoid duplications
• Implement changes unobtrusively
• Create code that is extensible and configurable
• …
6. Paradigm shift
• Eliminate externally observable side effects
• Control (reduce or eliminate) mutations
• Write declaratively and point-free
• Everything is a value (even functions)
• Recursion as looping mechanism (eliminate
loops)
• Functions ALWAYS return values
6
7. Is JavaScript functional?
JavaScript is a dynamic, object-oriented programing
language whose expressive power via closures and
high-order functions makes it compelling for writing in
an functional style
ES6 features that favor functional programming:
• const keyword
• Promises
• Lambda expressions
• Generators and Iterators
8. FP JavaScript Ecosystem
JavaScript has many libraries that implement many functional
programming techniques:
• Ramda.js http://ramdajs.com/0.17/index.html
• Lodash.js https://lodash.com/
• Underscore.js http://underscorejs.org/
• Lazy.js -> http://danieltao.com/lazy.js/
• Wu.js https://fitzgen.github.io/wu.js/
• Fn.js http://eliperelman.com/fn.js/
9. Functional Programming in JS
• High-Order functions
– Functions in JS can be used as parameters, assigned to
variables, and returned from other functions (lead to LSP)
• Closures
9
<< outer scope (global) >>
function makeInner(params) {
<< inner scope >>
return function inner(params2) {
<< function body >>
}
var additionalVars;
}
11. Battle of the Hello World!
document.getElementById(’msg').innerHTML = '<h1>Hello World</h1>';
compose(addToDom(’msg'), h1, echo('Hello World'));
vs
13. Declarative Programming
• Describe WHAT a program does
• Not HOW to do it
SQL>
SELECT p.firstname, p.birthYear FROM Person p
WHERE p.birthYear > 1903 AND p.country = 'US'
GROUP BY p.firstname, p.birthYear
17. To functional
var addToTable = compose(
appendToTable(’personTable'),
populateRow,
props(['ssn', 'firstname', 'lastname']),
findPerson,
normalize,
trim);
17
18. Things to understand
• The issue of side effects
• Referential Transparency
• Singularity principle
• Liskov Substitution Principle
• Currying and composition
• Functors and Monads
19. Side effects
• Changing a variable, property or data structure globally
• Changing the original value of a function’s argument
• Processing user input
• Throwing an exception, unless it’s caught within the same
function
• Printing to the screen or logging
• Querying the DOM or databases
21. How do we deal with change?
• Simply don’t change any objects…. (right)
• Use const (limited)
• Create Value Objects (only in certain cases)
• JavaScript’s Object.freeze (shallow)
• Use Lenses (more elegant option!)
22. Lenses
var person = new Person('Alonzo', 'Church');
var lastnameLens = lenseProp('lastName');
view(lastnameLens, person); //-> 'Church'
var newPerson = set(lastnameLens,
'Mourning', person);
newPerson.lastname; //-> 'Mourning’
person.lastname; //-> 'Church'
var person = {
firstname:'Alonzo’,
lastname: 'Church'
}
23. Understanding referential transparency
• Functions behave like mathematical functions
var increment = (val) => val + 1;
• Functions are relations that
map a set of types to other
types
• Functions shall return the
same output on same input
• Functions require all parameters needed to perform
its work
N
N
N
N
N
increment
domain range
25. Simple Functions
• Singularity principle: functions are supposed
to do perform only one task
• Simple functions typically have fewer
arguments (reduced arity) that complex
functions
• Simple functions are easy to compose and
chain
26. Bye Bye Loops
• Loops introduce non-linear program flow
• Loops mutate data (variable counter)
• They can be modeled with functions
var acc = 0;
for (let i = 0; i < nums.length; i++) {
acc += nums[i];
}
function sum(arr) {
var list = _(arr);
return list.isEmpty() ? 0
: return list.head() + sum(list.tail());
}
27. Lazy Function Chains
• Other alternatives to loops
• Use high level constructs such as map, reduce, and
filter
• Functional libraries implement clever techniques like
– Pipelining
– Method fusion
var fruits = ['Apple', 'apple', 'ORANGE',
'banana', 'bAnAnA']
result = chain(fruits)
.map(startCase)
.uniq()
.sort()
.value(); //-> ['Apple', 'Banana','Orange']
28. Liskov Substitution Principle
• Functions that use references to base classes must
be able to use objects of derived classes without
knowing it
• Programming functionally with objects means
separating the state from its behavior
• Beneficial for building function chains and pipelines
• Practically, this means avoiding the use of this
29. fullname(person
)
Person
get fullname()
Student
get school()
Person
Student
var person = new Person('Alonzo', 'Church', '444-44-4444');
p.fullname(); //-> Alonzo Church
var fullname = (person) =>
[person.firstname, person.lastname].join('
');
fullname(person); //-> Alonzo Church
Uses the this reference to
access object’s data
Eliminates the use of this since
object is supplied as parameter
FP separates methods into high-order function that can work on
instances of base type Person, which must also work with Student
30. Currying
• Some functions can’t be reduced to single arguments
• Used to partially evaluate a function as a sequence of
steps by providing arguments one-at-a-time
• Currying enables the composition of complex
functions
function f(a, b, c) { … }
f a
f(a, undefined,
undefined)
evaluating
:
returns
:
31. Currying2
function f(a, b, c) { … }
curry(f) :: (a,b,c) -> f(a) -> f(b) -> f(c)
f(a, b, c) {
return function (a) {
return function (b) {
return function (c) {
…
}
}
}
f a f(b, c)
evaluating:
f a f(c)b
f a resultb c
returns:
32. Currying3
var name = curry2(function (last, first) {
return [last, first].join(',');
});
name('Curry')('Haskell'); //-> 'Curry, Haskell’
name('Curry'); //-> Function
33. Composition
• Deep-link a function’s
return value with another
function’s arguments
• Separates a program’s
description from
evaluation
• Composition leads to
point-free programs
A
A
A
B
B C
Cg
f
f o g = f(g(x))
• The resulf of composing a function is another
function that can be composed further
34. Composition2
f o g = f(g) = compose :: (B -> C) -> (A -> B) -> (A -> C)
var str = `A complex system that works is
invariably found to have evolved from a simple
system that worked `;
var explode = (str) => str.split(/s+/);
var count = (arr) => arr.length;
var countWords = compose(count, explode);
countWords(str); // -> 17
35. function addToRoster(personId) {
if(personId!== null) {
personId =
personId.replace(/^s*|-|s*$/g, '');
if(personId.length !== 9) {
throw new Error('Invalid Input');
}
var person = db.find(personId);
if (person !== null) {
var rowInfo =
`<td>${person.ssn}</td>
<td>${person.firstname}</td>
<td>${person.lastname}</td>`;
$(`#${tableId} tr:last`).after(
`<tr>${rowInfo}</tr>`);
return $(`#${tableId} tr`).length – 1;
}
else {
throw new Error(’Person Record not found!');
}
}
else {
return 0;
}
}
Breaking
monolithic
functions
37. Building blocks
var safeFindObject = curry(function (db, id) {
return Maybe.fromNullable(find(db, id));
});
var findPerson = safeFindObject(DB(’people'));
var trim = (str) => str.replace(/^s*|s*$/g, '');
var normalize = (str) => str.replace(/-/g, '');
38. Building blocks2
var populateRow = function (columns) {
var cell_t = _.template('<td><%= a %></td>');
var row_t = _.template('<tr><%= a %></tr>');
var obj = function (a) {
return {'a': a};
};
var row = compose(row_t, obj, R.join(''), map(cell_t), map(obj));
return row(columns);
};
var addToTable = curry(
function (elementId, rowInfo) {
$(`#${elementId} tr:last`).after(`<tr>${rowInfo}</tr>`);
return $(`#${elementId} tr`).length - 1;
});
42. Memoization
• Optimization technique used to avoid
unnecessary invocation of a computationally
expensive function
• Based on the principle of referential
transparency
• Only applies to pure functions
• Implemented using a simple caching layer
43. Memoization2
43
Function.prototype.memoized =
function () {
var key = JSON.stringify(arguments);
this._cache = this._cache || {};
this._cache[key] = this._cache[key] ||
this.apply(this, arguments);
return this._cache[key];
};
Function.prototype.memoize = function () {
var fn = this;
if (fn.length === 0 || fn.length > 1) {
return fn;
}
return function () {
return fn.memoized.apply(fn, arguments);
};
};
44. var md5 = (function (str) {
// algorithm details here...
return digest;
}).memoize();
var str = ’OO in the large, functional in the small’;
md5(str); // 0.733 ms
md5(str); // second time: 0.021 ms
44
Memoization3
45. md5
_cache
'Get Functional!'
check cache
function key value
md5 Get
Functional!
96d18935a41d37a54d60ce9976
75cc91
put
function key value
[empty]
contains
false
memoized
96d18935a41d37a54d60ce997675cc91
run
function
96d18...96d18935a41d37a5...
'Get Functional!'
check cache
contains
true
get
96d18...
96d18935a41d37a54d60ce997675cc91
First call
Second call
46. Containerizing
46
var Wrapper = function (val) {
this._val = val;
};
// Map
Wrapper.prototype.map = function (f) {
return f(this._val);
};
// Unit
var wrap = (val) => new Wrapper(val);
guarded
identity
map
identity returns
the same value
Wrapper
47. Containerizing2
var wrappedValue = wrap('Get Functional');
// extract the value
var value = wrappedValue.map(toUpper).map(repeat(2)).map(identity);
value; //-> 'GET FUNCTIONAL GET FUNCTIONAL'
48. Functors: next level containers
48
// Map
Wrapper.prototype.map = function (f) {
return f(this.val);
};
// Functor
Wrapper.prototype.fmap = function (f) {
return wrap(f(this.val));
};
• Data structure that can be
mapped over
• Lift values into a container so
that you can apply functions
onto them, place the result
back into the container
49. Functors2
49
var plus = curry((a, b) => a + b);
var plus3 = plus(3);
var two = wrap(2);
var five = two.fmap(plus3); //-> Wrapper(5)
two.fmap(plus3).fmap(plus10); //-> Wrapper(15)
plus3
fmap
Wrapper
2
Wrapper
2
apply function
Wrapper
5
wrap
50. Why do this?
50
Wrapping a
potentially null
value or a function
that can cause the
program to fail
54. Monads2
54
• Backbone of functional
programming
• Treat data and operations
algebraically
• Data type used for applying a
sequence of transformations on
data (conveyor belt model)
• Abstract data flows
• Used for error handling, IO,
Logging, etc
55. Error handling and Maybe
55
• Wall-off impurity
• Consolidate null-check logic
• Consolidated exception throwing
• Support compositionally of functions
• Centralize logic for providing default values
58. Maybe Monad4
58
class Nothing extends Maybe {
map(f) {
return this; // noop
}
get value() {
throw new TypeError(`Can't extract the value of a
Nothing.`);
}
getOrElse(other) {
return other;
}
}
60. Maybe Example2
60
function getCountry(student) {
var school = student.school();
if (school !== null ) {
var addr = school.address();
if (addr !== null ) {
return addr.country();
}
}
return 'Country does not
exist!';
}
student; //-> Maybe<Student>
var getCountry = (student) => student
.map(prop('school'))
.map(prop('address'))
.map(prop('country'))
.getOrElse('Country does not
exist!');
61. Monads abstract data flow
61
cleanInputSSN SSN checkLengthSsn
SSN findPerson
addToTable(SSN)
nullpopulateRow
Left
null
Left
appedToTable
orElse errorLog
skipped skipped
props
skipped
When an error occurs, Maybe safely propagates
the error through the components of your code
63. function addToTable(personId) {
if(personId!= null) {
personId= personId (/^s*|-|s*$/g, '');
if(personId!== 9) {
throw new Error('Invalid Input');
}
var person= store.get(personId);
if (person) {
var rowInfo =
`<td>${person.ssn}</td>
<td>${person.firstname}</td>
<td>${person.lastname}</td>`;
$(`#${tableId}
tr:last`).after(`<tr>${rowInfo}</tr>`);
return $(`#${tableId} tr`).length - 1;
}
else {
throw new Error(’Person not found!');
}
}
else {
return 0;
From
imperative
64. To functional
var addToTable = compose(
appendToTable(’personTable'),
populateRow,
props(['ssn', 'firstname', 'lastname']),
findPerson,
normalize,
trim);
64
• Eliminate side effects!
• Reduce complexity!
• Code is more testable!
• Declarative and easier to read!
• Reduce number of bugs!