Thinking Functionally with JavaScript

62
Thinking Functionally Functional Programming using JavaScript Luis Atencio @luijar Blog: http:// luisatencio.net

Transcript of Thinking Functionally with JavaScript

Page 1: Thinking Functionally with JavaScript

Thinking FunctionallyFunctional Programming using JavaScript

Luis Atencio@luijarBlog: http://luisatencio.net

Page 2: Thinking Functionally with JavaScript

What is FP?

“Functional programming refers to the declarative evaluation of pure functions to

create immutable programs by avoiding externally observable side effects.”

Page 3: Thinking Functionally with JavaScript

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

Page 4: Thinking Functionally with JavaScript

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

Page 5: Thinking Functionally with JavaScript

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)

Page 6: Thinking Functionally with JavaScript

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

Page 7: Thinking Functionally with JavaScript

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

Page 8: Thinking Functionally with JavaScript

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)

Page 9: Thinking Functionally with JavaScript

Hello World!

document.getElementById('msg').innerHTML = '<h1>Hello World</h1>';

compose(addToDom('#msg'), h1)('Hello World');

vs

Page 10: Thinking Functionally with JavaScript

More functional Hello World!

compose ( addToDom('#msg'),

h2, repeat(3))('Hello World');

Declarative & configurable

Page 11: Thinking Functionally with JavaScript

Even more functional Hello World!

compose ( addToDom('#msg'),

h2, repeat(3), escapeChars)('Hello World');

Extensible

Page 12: Thinking Functionally with JavaScript

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);

Page 13: Thinking Functionally with JavaScript

First let’s look at the current state of things

Imperative Programming

Page 14: Thinking Functionally with JavaScript

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; }}

Page 15: Thinking Functionally with JavaScript

… after thinking functionally……and some magic…

Page 16: Thinking Functionally with JavaScript

16

var addToTable = compose( appendToTable(’personTable'),

populateRow, props(['ssn', 'firstname', lastname']),

findPerson, normalize, trim);

addToTable(personId);

Page 17: Thinking Functionally with JavaScript

First, you need to understand…

• The issue with side effects and mutations• Singularity principle• Currying and composition• Functors and Monads

Page 18: Thinking Functionally with JavaScript

Side effects

doWork doMoreWork

sharedData = [...]depends on updatechanges

coupling

1 2order matters

Page 19: Thinking Functionally with JavaScript

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

Page 20: Thinking Functionally with JavaScript

How do we deal with mutations?

Page 21: Thinking Functionally with JavaScript

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'}

Page 22: Thinking Functionally with JavaScript

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

Page 23: Thinking Functionally with JavaScript

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:

( )

Page 24: Thinking Functionally with JavaScript

function f(a, b, c) { … }

f(a, b, c) {

return function (a) { return function (b) {

return function (c) { …

} }

} }

Currying2

Page 25: Thinking Functionally with JavaScript

f a f(b, c)

Evaluating:

f a f(c)b

f a resultb c

returns:

(((

))

)

Currying3

Page 26: Thinking Functionally with JavaScript

Currying Example

var name = curry2(function (first, last) { return [last, first].join(',');}); name('Haskell'); //-> Function

name('Haskell')('Curry'); //-> 'Curry, Haskell' 

Page 27: Thinking Functionally with JavaScript

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

Page 28: Thinking Functionally with JavaScript

Composition2

f•g(x) = f(g(x))

Page 29: Thinking Functionally with JavaScript

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

Page 30: Thinking Functionally with JavaScript

Composition is the backbone of modularity in FP

Page 31: Thinking Functionally with JavaScript

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

Page 32: Thinking Functionally with JavaScript

addToTable

cleanInputcheckLengthSsn

findPerson

populateRow

appendToTable

Impure:can’t be tested

reliably

Decompose => Compose

Become the building blocks of your program

Page 33: Thinking Functionally with JavaScript

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, '');

Page 34: Thinking Functionally with JavaScript

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; });

Page 35: Thinking Functionally with JavaScript

35

const addToTable = compose( appendToTable('personTable'), populateRow, props(['ssn','firstname’,'lastname']), findPerson, normalize, trim);

addToTable(personId);

Functional programming

Page 36: Thinking Functionally with JavaScript

But what about errors?

Page 37: Thinking Functionally with JavaScript

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

Page 38: Thinking Functionally with JavaScript

Containerizing2

const wrappedValue = wrap('Get Functional'); // extract the valueconst value = wrappedValue.map(toUpper).map(repeat(2)).map(identity);

value; //-> 'GET FUNCTIONAL GET FUNCTIONAL'

Page 39: Thinking Functionally with JavaScript

• 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));};

Page 40: Thinking Functionally with JavaScript

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

Page 41: Thinking Functionally with JavaScript

41

What can we do with containers?

Wrap a potentially null value or a function that can cause the program to fail

Page 42: Thinking Functionally with JavaScript

42

Software is unpredictable

Exception thrown but contained within the wrapper

Exception does not affect any other part of the system

Page 43: Thinking Functionally with JavaScript

43

Software must be robust

Function throws an exception

Exception is propagated and gracefully handled

program flow

Page 44: Thinking Functionally with JavaScript

44

Monads

Functor + Unit = Monad

…and some more

Page 45: Thinking Functionally with JavaScript

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

Page 46: Thinking Functionally with JavaScript

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

Page 47: Thinking Functionally with JavaScript

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.

Page 48: Thinking Functionally with JavaScript

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; }}

Page 49: Thinking Functionally with JavaScript

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; } }

Page 50: Thinking Functionally with JavaScript

Usage

50

Maybe.of(3).map(plus2); //-> Maybe(5)

Maybe.of(3).chain(plus2); //-> 5

Maybe.fromNullable(null).map(plus2); //-> Nothing()

Page 51: Thinking Functionally with JavaScript

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!');

Page 52: Thinking Functionally with JavaScript

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

Page 53: Thinking Functionally with JavaScript

To recap

Page 54: Thinking Functionally with JavaScript

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

Page 55: Thinking Functionally with JavaScript

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'););

Page 56: Thinking Functionally with JavaScript

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?

Page 57: Thinking Functionally with JavaScript

My way of contributing and teaching

Page 58: Thinking Functionally with JavaScript

Magazines

https://dzone.com/refcardz/functional-programming-with-javascript

Page 59: Thinking Functionally with JavaScript

FunctionalProgramming in JavaScriptwww.manning.com/atencio

Page 60: Thinking Functionally with JavaScript

Functional PHP

https://leanpub.com/functional-php

Free!

Page 61: Thinking Functionally with JavaScript

@luijar