Thinking Functionally with JavaScript

Post on 19-Jan-2017

715 views 0 download

Transcript of Thinking Functionally with JavaScript

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