Thinking FunctionallyFunctional Programming using JavaScript
Luis Atencio@luijarBlog: http://luisatencio.net
What is FP?
“Functional programming refers to the declarative evaluation of pure functions to
create immutable programs by avoiding externally observable side effects.”
Why?• Reduce complexity• Create code that is easier to trace, debug, and test• Modularize code leads to separation of concerns• Avoid duplications• Implement changes unobtrusively• Create code that is extensible and configurable• Foundation for Rx and reactive programming
Why Learn it now?• Most newer languages are/have incorporated
functional features into them. – Java (streams, function interfaces, lambda expressions)– Scala (hybrid FP + OO)– F# (immutable structures, pipes)
• LINQ– ES6 JavaScript -> ES7 JavaScript will have streams– Clojure, Lisp, Scheme, Haskell, OCaml, Erlang, etc
• We live in a highly concurrent world
5
Paradigm shift
• Eliminate externally observable side effects• Control (reduce or eliminate) mutations• Write declaratively and point-free• Everything is a value (even functions)• Functions ALWAYS return values• Recursion as looping mechanism (eliminate
loops)
6
The OO World
Data and behavior tightly coupled
ClassA
data
behavior
ClassB
data
behavior
ClassC
data
behavior Unit testing is challenging
Unit of work: Classes
function doWork(objectA): objectB
The FP World
function doMoreWork(objectB): objectC
function displayResults(objectC)
Data and behavior loosely coupled
Unit testing is (basically) free
Unit of work: Function
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 a functional style.
Features that favor functional programming:• const keyword• Promises• Lambda expressions + closures• Generators and Iterators• FP libraries (Ramda.js, Underscore.js, Lodash.js, etc)
Hello World!
document.getElementById('msg').innerHTML = '<h1>Hello World</h1>';
compose(addToDom('#msg'), h1)('Hello World');
vs
More functional Hello World!
compose ( addToDom('#msg'),
h2, repeat(3))('Hello World');
Declarative & configurable
Even more functional Hello World!
compose ( addToDom('#msg'),
h2, repeat(3), escapeChars)('Hello World');
Extensible
Declarative Programming• Describe WHAT a program does• Not HOW to do itSQL> SELECT firstname, birthYear FROM Person WHERE year > 1903 AND country = 'US' GROUP BY firstname, birthYear
FP> compose(select(firstname, birthYear), from('Person'), where(year => year === 1903), where(country => country === 'US'), groupBy(firstname, birthYear))(query);
First let’s look at the current state of things
Imperative Programming
function addToTable(personId) { if(personId != null) { personId = studentId.replace(/^\s*|\-|\s*$/g, ''); if(personId.length !== 9) { throw new Error('Invalid Input'); } var person = db.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 Record not found!'); } } else { return 0; }}
… after thinking functionally……and some magic…
16
var addToTable = compose( appendToTable(’personTable'),
populateRow, props(['ssn', 'firstname', lastname']),
findPerson, normalize, trim);
addToTable(personId);
First, you need to understand…
• The issue with side effects and mutations• Singularity principle• Currying and composition• Functors and Monads
Side effects
doWork doMoreWork
sharedData = [...]depends on updatechanges
coupling
1 2order matters
function addToTable(personId) { if(personId != null) { personId = studentId.replace(/^\s*|\-|\s*$/g,''); if(personId.length !== 9) { throw new Error('Invalid Input'); } var person = db.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 Record not found!'); } } else { return 0; }}
External Dependencies
Complexity
IO
How do we deal with mutations?
Lenses: object mutations const person = new Person('Alonzo', 'Church'); const lastnameLens = lenseProp('lastName'); view(lastnameLens, person); //-> 'Church'
const newPerson = set(lastnameLens, 'Mourning', person);newPerson.lastname; //-> 'Mourning’person.lastname; //-> 'Church'
var person = { firstname:'Alonzo’, lastname: 'Church'}
Singular Functions• Singularity principle: functions are supposed
to perform only one task• Simple functions typically have fewer
arguments (reduced arity) than complex functions
• Simple functions are easy to test, but also composeable and chainable
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:
( )
function f(a, b, c) { … }
f(a, b, c) {
return function (a) { return function (b) {
return function (c) { …
} }
} }
Currying2
f a f(b, c)
Evaluating:
f a f(c)b
f a resultb c
returns:
(((
))
)
Currying3
Currying Example
var name = curry2(function (first, last) { return [last, first].join(',');}); name('Haskell'); //-> Function
name('Haskell')('Curry'); //-> 'Curry, Haskell'
Composition
• Loosely couple a function’s return value with another function’s arguments (pipeline)
• Separates a program’s description from evaluation
• The resulf of composing a function is another function that can be composed further
Composition2
f•g(x) = f(g(x))
Composition example
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
Composition is the backbone of modularity in FP
function addToTable(personId) { if(personId != null) { personId = studentId.replace(/^\s*|\-|\s*$/g, ''); if(personId.length !== 9) { throw new Error('Invalid Input'); } var person = db.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 Record not found!'); } } ...
Breaking monolithic functions
addToTable
cleanInputcheckLengthSsn
findPerson
populateRow
appendToTable
✔
✔
✔
Impure:can’t be tested
reliably
Decompose => Compose
Become the building blocks of your program
Building blocks
const safeFindObject = curry(function (db, id) { return Maybe.fromNullable(db.get(id));});
const findPerson = safeFindObject(DB('people'));
const trim = (str) => str.replace(/^\s*|\s*$/g, '');
const normalize = (str) => str.replace(/\-/g, '');
Building blocks2const populateRow = function (columns) { const cell_t = template('<td><%= a %></td>'); const row_t = template('<tr><%= a %></tr>'); const obj = function (a) { return {'a': a}; }; const row = compose( row_t, obj, join(''), map(cell_t), map(obj)); return row(columns);};
const addToTable = curry( function (elementId, rowInfo) { $(`#${elementId} tr:last`) .after(`<tr>${rowInfo}</tr>`); return $(`#${elementId} tr`).length - 1; });
35
const addToTable = compose( appendToTable('personTable'), populateRow, props(['ssn','firstname’,'lastname']), findPerson, normalize, trim);
addToTable(personId);
Functional programming
But what about errors?
37
Containerizing
const Wrapper = function (val) { this._val = val; }; // MapWrapper.prototype.map = function (f) { return f(this._val); }; // Unitconst wrap = (val) => new Wrapper(val);
guarded
identity
map
identity returnsthe same value
Wrapper
Containerizing2
const wrappedValue = wrap('Get Functional'); // extract the valueconst value = wrappedValue.map(toUpper).map(repeat(2)).map(identity);
value; //-> 'GET FUNCTIONAL GET FUNCTIONAL'
• 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
Functors: next level containers
39
// FunctorWrapper.prototype.fmap = function (f) { return wrap(f(this._val));};
Functors2
40
const plus = curry((a, b) => a + b);conts plus3 = plus(3);const two = wrap(2);const five = two.fmap(plus3); //-> Wrapper(5) two.fmap(plus3).fmap(plus10); //-> Wrapper(15)
plus3
fmapWrapper
2
Wrapper
2
apply function
Wrapper
5
wrap
41
What can we do with containers?
Wrap a potentially null value or a function that can cause the program to fail
42
Software is unpredictable
Exception thrown but contained within the wrapper
Exception does not affect any other part of the system
43
Software must be robust
Function throws an exception
Exception is propagated and gracefully handled
program flow
44
Monads
Functor + Unit = Monad
…and some more
45
Monads2• 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
46
So call me: Maybe• Wall-off impurity• Consolidate null-check logic• Consolidated exception throwing• Support compositionally of functions• Centralize logic for providing default values
47
Maybe Monad
Just
object
Nothing
Maybe
Just(value): represents a container that wraps a defined value.
Nothing(): represents a container that has no value, or a failure that needs no additional information.
48
Maybe: Justclass Maybe { static fromNullable(a) { return a !== null ? just(a) : nothing(); } static of(a) { return just(a); }}
class Just extends Maybe { map(f) { return of(f(this.value)); } getOrElse() { return this.value; }}
49
Maybe: Nothingclass 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; } }
Usage
50
Maybe.of(3).map(plus2); //-> Maybe(5)
Maybe.of(3).chain(plus2); //-> 5
Maybe.fromNullable(null).map(plus2); //-> Nothing()
Remove nested code
51
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>
const getCountry = student => student .map(prop('school')) .map(prop('address')) .map(prop('country'))
.getOrElse('Country does not
exist!');
52
Monads abstract data flow+ Error Handling
trimxxx-xxx id normalize id findPerson
addToTable(personId)
nullpopulateRowLeft
nullLeft
appendToTable
orElse console.log
skipped skipped
props
skipped
When an error occurs, Maybe safely propagatesthe error through the components of your code
To recap
function addToTable(personId) { if(personId!= null) { personId= personId (/^\s*|\-|\s*$/g, ''); if(personId!== 9) { throw new Error('Invalid Input'); } var person= db.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; }}
Fromimperative
55
To Functionalconst addToTable = compose( appendToTable('personTable'), populateRow, props(['ssn','firstname','lastname']), findPerson, normalize, trim);
addToTable('444-44-4444'); //-> Maybe(Student)addToTable('xxx-xx-xxxx'); //-> Maybe(null)addToTable('xxx-xx-xxxx').orElse( console.log('Error adding student'););
Thinking this way will…
• Allow you create modular, testable, reliable, and robust applications
• Decompose, decompose, decompose!• Pure functions: help design for concurrency • Enable other paradigms: – Reactive programming– Concurrent programming– Immutable architectures?
My way of contributing and teaching
Magazines
https://dzone.com/refcardz/functional-programming-with-javascript
FunctionalProgramming in JavaScriptwww.manning.com/atencio
Functional PHP
https://leanpub.com/functional-php
Free!
@luijar
https://legacy.joind.in/16810
Thanks!