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