FP in JS
Vladimir Novick
Front End Developer & Architect
mail: vnovick@gmail.com
twitter: @VladimirNovick
github: vnovick
facebook: vnovickdev
Agenda
-
How FP is different from OOP
-
FP features in JS
-
Basic Concepts
-
FP Techniques
-
FP Abstractions
Assumptions
-
You are familiar with ES6
-
arrow functions
-
rest operator
-
string templates
-
-
You know your JavaScript
-
You are ready to open your mind
Is JS good for FP
Javascript is Functional Language
Javascript is Functional Language
Javascript is OO Language
Javascript is OO Language
JavaScript is as functional as it is object-oriented
Why Functional
-
Easier to reason about (test)
-
Input -> Output
-
-
Reuse more
-
Function Composition
-
-
Declaritive vs Imperative
-
Easier to implement immutability
Object Oriented Programming
- Single Responsibility principle
- Open Closed Principle
- Liskov substitution principle
- Interface segregation principle
- Dependency Inversion principle
- Factory pattern
- Decorator pattern
- Strategy pattern
- Visitor pattern
Generics, Interfaces, Inheritance, Polymorphism, IoC, DI, MVC ...
Functional Programming
FP features in JS
-
Higher Order Functions
-
Closures
-
Lambda expressions
Higher Order Functions
Functions that can be assigned to variables, passed as parameters and returned from other functions
Closures
Closure is an inner function which has access to:
-
it's own scope
-
outer function variables
-
global variables
Closures
function getGreetingGenerator(greeting) {
return function(name) {
console.log(greeting + ", " + name);
}
}
var englishGreeting = getGreetingGenerator("Welcome");
englishGreeting("Vladimir Novick");
Lambda expressions
Lambda expressions === "ES6 arrow functions"
var sum = function add(a,b) {
return a + b
}
let sum = (a, b) => a + b
In ES6 it can be written as
Basic Concepts
Function in Math
A function f takes an input x, and returns a single output f(x)
What is Side effect
Side effect is when a function can change a program's state aside from it's returning value
Pure Function
-
It depends only on the input provided
-
It doesn't have side effects
var flag = true
function calc(){
// ....Calculations based on flag
return calculationResult
}
calc()
Pure Function
Function with Side effect
Referential Transparency
function calc(flagParam, ...args){
//...Calculations based on flagParam
return calculationResult
}
var flag = true
calc(flag, 1, 2.5, 3, 0.5)
RT is a formal way of defining Pure Function. If function always yields the same result on the same input it's called referential transparent
Stop looping around
Loops by definition have side effects
Instead you have:
-
map, filter, reduce
-
Recursion
map, filter, reduce
var jediCouncil = [
{ name: 'Yoda', origin: 'Dagobah', species: 'Unknown' },
{ name: 'Plo Koon', origin: 'Dorin', species: 'Kel Dor' },
{ name: 'Mace Windu', origin: 'Haruun Kal', species: 'Korunnai'},
{ name: 'Ki-Adi-Mundi', origin: 'Cerea', species: 'Cerean'},
{ name: 'Saesee tiin', origin: 'Iktotch', species: 'Iktotchi'},
{ name: 'yaddle', origin: 'Unknown', species: 'Unknown'},
{ name: 'even piell', origin: 'Haruun Kal', species: ''},
{ name: 'O. Rancisis', origin: 'Thisspias', species: 'Thisspiasian'},
{ name: 'Adi Gallia', origin: 'Tholothian', species: 'Tholothian'},
{ name: 'Yarael Poof', origin: '', species: 'Quermian'},
{ name: 'even piell', origin: 'Zabrak', species: 'Lannik'},
{ name: 'Obi-Wan Kenobi', origin: 'Stewjon', species: 'Human'},
]
Array.prototype.filter
// var humans = []
// for (var i = 0; i < jediCouncil.length; i++) {
// if (jediCouncil[i].species === 'Human') {
// humans.push(jediCouncil[i])
// }
// }
let isHuman = member =>
member.species === 'Human'
let humanCouncils = jediCouncil.filter(isHuman)
// find instead of filter returns only first element that matches
//[{"name":"Obi-Wan Kenobi","origin":"Stewjon","species":"Human"}]
Array.prototype.map
// var text = [];
// for (var i=0; i < jediCouncil.length; i++ ) {
// text.push(jediCouncil[i].name +
// " is " +
// jediCouncil[i].species +
// " specie born on " +
// jediCouncil[i].origin)
// }
var text = jediCouncil.map( member =>
`${member.name} is ${member.species} specie born on ${member.origin}` )
console.log (text.join('\n'))
// Yoda is Unknown specie born on Dagobah
// Plo Koon is Kel Dor specie born on Dorin
// Mace Windu is Korunnai specie born on Haruun Kal
// Ki-Adi-Mundi is Cerean specie born on Cerea
// Saesee tiin is Iktotchi specie born on Iktotch
// yaddle is Unknown specie born on Unknown
// even piell is specie born on Haruun Kal
// O. Rancisis is Thisspiasian specie born on Thisspias
// Adi Gallia is Tholothian specie born on Tholothian
// Yarael Poof is Quermian specie born on
// even piell is Lannik specie born on Zabrak
// Obi-Wan Kenobi is Human specie born on Stewjon
Array.prototype.reduce
let accumulator = {
jediNames: [],
planetsOfOrigin: []
}
let council = jediCouncil.reduce( (accumulator, item) => {
let { jediNames, planetsOfOrigin } = accumulator
return {
...accumulator,
...{
jediNames: [...jediNames, item.name],
planetsOfOrigin: [...planetsOfOrigin, item.origin]
}
}
},accumulator)
console.log(accumulator)
console.log(council)
// {"jediNames":[],"planetsOfOrigin":[]}
// {"jediNames":["Yoda","Plo Koon","Mace Windu","Ki-Adi-Mundi","Saesee tiin","yaddle","even piell","O. Rancisis","Adi Gallia","Yarael //Poof","even piell","Obi-Wan Kenobi"],"planetsOfOrigin":["Dagobah","Dorin","Haruun Kal","Cerea","Iktotch","Unknown","Haruun
Recursion
// var counter = 0;
// var totalLength = jediCouncil.length;
// while(counter < totalLength) {
// var name = jediCouncil[counter].name
// console.log(counter++ + ")" + jediCouncil[counter].name);
// }
var printJedis = function(log, counter, jediCouncil) {
if (counter < jediCouncil.length) {
var name = jediCouncil[counter].name
log(counter + 1 + ")" + name);
return printJedis(log, counter + 1, jediCouncil);
}
};
printJedis(log, 0, jediCouncil);
// 1)Yoda
// 2)Plo Koon
// 3)Mace Windu
// 4)Ki-Adi-Mundi
// 5)Saesee tiin
// 6)yaddle
// 7)even piell
// 8)O. Rancisis
// 9)Adi Gallia
// 10)Yarael Poof
// 11)even piell
// 12)Obi-Wan Kenobi
FP Abstractions in JS
-
Function Composition
-
Currying
-
Partial Application
Function Composition
let compose = (...fns) =>
fns.reduce(
(f, g) =>
(...args) =>
f(g(...args)));
(g∘ f )(x) = g(f(x)) for all x in X
f : X → Y and g : Y → Z can be composed to yield a function which maps x in X to g(f(x)) in Z
Function Composition
let isSpeciesUnknown = member =>
member.species === 'Unknown'
let getUnknownSpecies = (x) =>
x.filter(isSpeciesUnknown)
let printUnknownSpecies = (x) =>
x.map( member =>
`${member.name} is ${member.species} specie born on ${member.origin}` )
let getAndPrint = compose(printUnknownSpecies, getUnknownSpecies)
console.log (getAndPrint(jediCouncil))
console.log(
printUnknownSpecies(
getUnknownSpecies(jediCouncil)
)
)
// Result:
// ["Yoda is Unknown specie born on Dagobah","yaddle is Unknown specie born on Unknown"]
// ["Yoda is Unknown specie born on Dagobah","yaddle is Unknown specie born on Unknown"]
Currying
currying is the technique of translating the evaluation of a function that takes multiple arguments (or a tuple of arguments) into evaluating a sequence of functions, each with a single argument
becomes
Currying
Curry takes a function and transforms it to return chain of functions with one single argument for every function argument
f(a,b,c) transforms to f(a) -> f(b) -> f(c)
var printJediWelcome = (name, species, origin) => {
console.log( `Welcome, ${name}, the ${species} from ${origin}`)
}
var curriedJediWelcome = name => species => origin => {
console.log( `Welcome, ${name}, the ${species} from ${origin}`)
}
var genericJediWelcome = curriedJediWelcome('Jedi')
var onlyHumansWelcome = genericJediWelcome('human')
console.log(onlyHumansWelcome("Mars"))
//Welcome, Jedi, the human from Mars
Currying
var printJedisWelcome = (log, name, species, origin) => {
log( `Welcome, ${name}, the ${species} from ${origin}`)
}
let log = (...args) => { console.log(...args) }
curry(printJedisWelcome, log, "Jedi", "Human")("Earth")
curry(printJedisWelcome, log, "Jedi")("Human")("Earth")
curry(printJedisWelcome, log)("Jedi")("Human")("Earth")
curry(printJedisWelcome)(log)("Jedi")("Human")("Earth")
Partial Application
the process of fixing a number of arguments to a function, producing another function of smaller arity
let log = (...args) => { console.log(...args) }
let fn1 = log.bind(null, 1, 2);
fn1(3, 4); // => 1, 2, 3, 4
Currying implementation
A function that will return a new function until it receives all it’s arguments
let curry = (fn, ...args) => {
let _curry = (args) => {
return args.length < fn.length
? (..._args) => _curry([...args, ..._args])
: fn(...args);}
return _curry(args);
};
There are few things
more to learn
- Functors
- Monads
- Monoids
...But next time
The end
FP in JS
By vladimirnovick
FP in JS
- 1,967