Functional Programming

in Javascript

PURE

IMPURE

CURRYING

COMPOSE

DECORATORS

FUNCTORS

CLOSURES

MAP

FILTER

PARTIAL APPLICATIONS

CHAIN

PIPE

REDUCE

RECURSION

TAIL CALL OPTIMIZATION

FIRST CLASS

CITIZENS

COMBINATORS

ONCE

MAYBE

MONADS

Medium :: @vijayabharathib :: Twitter

Blog: pineboat.in

Why Functional Programming?

  • Concise
  • Less code for bugs to hide
  • Easier to Test
  • Easier to Read
  • Easier to Reason About
  • Easier to Maintain
  • Better Reuse
  • It is catching up fast [again]
  • You'll read a lot FP code at work
  • You don't wan to be left behind
  •  
  •  

FP roots in Javascript

Brenden Eich was supposed to bring in "Scheme", a functional programming language, before Java joined the party.

 

The result, we have best of both (or many) worlds.

First Class Functions

let print=console.log;
print("How's that?");
let print2=function newPrint(value){
    console.log(value);
}
print2("How about now?");

Functions can be assigned to variables

First Class Functions

let timeout=function(fn,waitTime,...args){
    waitFor(waitTime);//<- own implementation
    fn(...args);
}

timeout(console.log,10000,"abstract");

setTimeout(console.log,10000,"abstract"); 

Functions can be passed as parameters

First Class Functions

const remove = delimiter => 
             str => str.replace(delimiter, "");

/*const remove = function(delimiter) {
  function substitute(string) {
    return string.replace(delimiter, "");
  }
  return substitute;
};*/

const deleteSpace = remove(" ");
console.log(deleteSpace("Hell o")); //Hello

const deleteDot = remove(".");
console.log(deleteDot("Hell.o")); //Hello

Functions can return other functions

Higher Order Functions

  • Take functions as parameters

  • Return new functions

  • Or do both!

Partial Application

const remove = delimiter => 
        str => str.replace(delimiter, "");

const deleteDot = remove(".");

deleteDot("Hell.o"); //Hello

A function partially applies arguments and returns another function

let remove = (delimiter,str) => str.replace(delimiter, "");
remove(".","Hell.o"); //Hello

Full Application

Partial Application

State Change

  • Explicitly Changing Global Scope
  • Local copy of environment & state change
  • Implicitly Changing referenced variables (side effects)

Global State Change

let go=true;

function globalOnce(fn){
  return v => go ? (go=false, fn(v)) : void 0;
}

let y=globalOnce(console.log);
y("first"); //first
y("second"); //
let z=globalOnce(console.log);
z("third"); //
z("fourth"); //

Local State Change

const localOnce = (fn) => {
  let go=true;
  return v => go ? (go=false, fn(v)) : void 0;
}

let y=localOnce(console.log);
y("first"); //first
y("second"); //

let z=localOnce(console.log);
z("third"); //third
z("fourth"); //

Global Vs. Local

let y=localOnce(console.log);
y("first"); //first
y("second"); //

let z=localOnce(console.log);
z("third"); //third
z("fourth"); //
let y=globalOnce(console.log);
y("first"); //first
y("second"); //

let z=globalOnce(console.log);
z("third"); //
z("fourth"); //

Closures

const closureOnce = (fn) => {
  let go=true;
  return (...v) => go ? (go=false, fn(...v)) : undefined;
}

const launchRocket=closureOnce(setTimeout);
const ignite=()=>console.log("launched");

launchRocket(ignite,60000); //LAUNCHED
launchRocket(ignite,60000); //
launchRocket(ignite,60000); //

When you return a function from another, a copy of the parent function environment is created (an enclosure)

This is accessible by the returned function

Closures

const construct = (fn) => {

    let go=true;
    let playArea=true;
    let trees=true;
    let cave=true;

  const animalEnclosure = (animal) => {
    if(go){
        go=false;
        return fn(animal,trees,cave);
    }else{
        return undefined;
    }
  }

  return animalEnclosure;
}

Exclusive enCLOSURE!

Partial Application

const remove = delimiter => 
        str => str.replace(delimiter, "");

const deleteSpace = remove(" ");
deleteSpace("Hell o"); //Hello

const deleteDot = remove(".");
deleteDot("Hell.o"); //Hello

Makes heavy use of closures!

Inner functions returned from 'remove' have closure over 'delimiter'

Implicit State Change

const lastOf = (array) => {
    array.reverse();
    return array[0];
}

let arr=[1, 2, 3];
console.log(arr); // [1, 2, 3]
console.log(lastOf(arr)); // 3
console.log(arr); // [3, 2, 1]

A function "accidentally" changes an object passed to it! An impure one!

const square = (n) => {
    n=n*n;
    return n;
}

let x=10;
console.log(x); // 10
console.log(square(x)); // 100
console.log(x); // 10

Implicit State Change


let arr=[1, 2, 3];

Passing literals is 'By Value'

Passing objects is 'By Ref' and prone to side-effects


let x=10;

X

10

0xF9E31

[1, 2, 3]

arr

Pure & Impure Functions

let arr=[1,2,3];
arr.slice(0,1);
console.log(arr); // [1,2,3]
let arr=[1,2,3];
arr.splice(0,1); 
console.log(arr); // [2, 3]

Pure - Array.slice - returns a new array

Impure - Array.splice - mutates the source

Avoiding Side Effects

  • ImmutableJS
  • Object.assign
  • Array.slice

Use pure functions as much as possible (80%)

 

Very limited (20%) and isolated impure functions to manage state, logs and I/O.

Recursion

To understand recursion, you first need to understand recursion. - John D. Cook

No More Iterative Loops

for(let i=1; i<=10; i=i+1){
    console.log("for",i);
}
function loop(n, limit) {
    if (n >= limit) return;
    console.log("loop", n);
    loop(n + 1, limit);
}

Statements

Expressions

Recursion

function loop(n, limit) {
    if (n >= limit) return;
    console.log("loop", n);
    loop(n + 1, limit);
}

When a function calls itself, until baseline condition is met.

Recursion

function loop(n, limit) {
    if (n >= limit) return;
    console.log("loop", n);
    loop(n + 1, limit);
}

loop(0,1000000);

//console.js:116
//      throw e;
//      ^
///RangeError: Maximum call stack size exceeded

There is a limit!

Tail Call Optimization

function loop(limit,n=1) {
    if (n >= limit) return;
    console.log("loop", n);
    loop(limit,n + 1);
}
  • The last  (tail) line is  the recursive call.
  • Just one (tail) stack frame
  • It is part of ES6.
  • But browser support is very minimal.
function loop(limit, n=1) {
    if (n >= limit) return;
    loop(limit,n+1);
    console.log("loop", n);
}

Optimized (Tail Call)

Inefficient (& output is different)

Divide & Conquer

let sort=(arr)=>{
   if(arr.length < 2 ) return arr;
   let [left,right]=divide(arr);
   return conquer(sort(left),sort(right));
}
  • Divide the problem until it is small enough to solve
  • Solve the problem
  • Compose solutions

No More Loops?

Not Really.

 

Use recursions when the data structure demands it, that too when the data set is small.

 

Such as, recursively reading through the tree structure of a UI menu (Hopefully, it's just about a screen wide).

 

Use iterative loops elsewhere, at least until better browser support for TCO.

Patterns

Map

let arr=[1,2,3,4];
let bit_size=arr
    .map(n => n.toString(2)) //['1','10','11','100']
    .map(s => s.length);    // [1,2,2,3]

Array is a Functor

const toBinary = n => n.toString(2);
const charLength = s => s.length;

let arr=[1,2,3,4];
let bit_size=arr
    .map(toBinary) //['1','10','11','100']
    .map(charLength);    // [1,2,2,3]

  • Container implements a map method
  • map returns similar container (context)
  • return from map can be chained
  • map is agnostic about the value contained

 

Filter

let vals=[true,0,1,undefined,NaN,Infinity,false,null];
vals.filter(e=>!!e) //[true,1,Infinity]
let searchResults=result.filter(product => {
    return product.toLowerCase().includes(searchTerm);
});

Takes a function that returns 'boolean'.

Retains elements if the function returns 'true'

Reduce

let sales=[200,300,400,600];
sales.reduce((total,n) => total+n ,0);
//1500

//Syntax
arr.reduce(callback[, initialValue])

callback(accumulator,element,index,array)

* Callback should always return a value for 'accumulator'

Reduce

let sales=[200,300,400,600];
sales.reduce((arr,n) => arr.concat(n) ,[]);
//[200,300,400,600] <- your own shallow copy

Reduce is the most humble, and quite powerful function that can play the role of both map and filter.

 

It returns just one value, which could be a literal, an array or an object...or a function.

Compose

var psd=design();
var repo=build(psd);
deploy(repo);
deploy(build(design()));

Compose

function compose(f1,f2,f3){
    f1(f2(f3()));
}

compose(deploy,build,design);

Compose At Scale

const compose=(...fun)=>{
    return (v)=>fun
        .reverse()
        .reduce((acc,fn)=>fn(acc),v);
}
const last=(arr)=> arr.reverse()[0]; //impure
const square= n => n*n;
const cube = n => n * square(n);
const ternary = compose(cube, square, last);
ternary([1, 2]); //64

Compose

const compose=(...fun)=>{
    return (v)=>fun
        .reduceRight((acc,fn)=>fn(acc),v);
}

const squareLast=compose(square,last);

Pipe

function pipe(f1,f2,f3){
    f3(f2(f1()));
}

pipe(design,build,deploy);

Pipe

const pipe=(...funs)=>{
    return (v) => funs
        .reduce((args,fn)=>fn(args),v);
}

const squareLast=pipe(last,square);

Combinators

  • Combinator is a higher-order function
  • Only functions as arguments
  • Returns another function
  • Returned function is usually a combination of functions passed in as arguments
  • Compose, Pipe...etc.

Unary

let unary = fn => v => fn(v);
let unaryInt = unary(parseInt);
["1", "2"].map(parseInt); //[1, NaN]
["1", "2"].map((s)=>parseInt(s)); //[1,2]
["1", "2"].map(unaryInt); //[1, 2]
//Syntax
parseInt(string, radix);
map(callback(currentValue[, index[, array]]) {},this);

Decorators

let unary = fn => v => fn(v);
let unaryInt = unary(parseInt);
["1", "2"].map(parseInt); //[1, NaN]
["1", "2"].map(unaryInt); //[1, 2]

A function that takes another function, and extends it, is a decorator.

 

Unary takes a function and reduces it to a function that takes only one argument.

Higher Order Functors

let iPromise=new Promise(
    (resolve,reject)=>{});

iPromise.then(callback1)
    .then(callback2)
    .then(callback3)
    .catch(errorHandler);
$('#id')
    .css(x,y)
    .on('click')
    .show();

Boxed and Locked

function Box(x) {
  let value = x;
  this.unbox = fn => 
        new Box(fn(value));
}

Box.of = (v) => new Box(v);

let square=(n)=>n*n;

let v = new Box(2);
let v1 = v
  .unbox(square) //4
  .unbox(Math.sqrt); //2
v1.unbox(console.log); //2

const binary = n => n.toString(2);
const bits = s => s.split('');
const bitSize = s => s.length;

let b = Box.of(20);
let b2 = b
  .unbox(binary) //10100
  .unbox(bits) // ['1','0','1','0','0']
  .unbox(bitSize); //5
let b3=b2.unbox(console.log); //5
console.log(b3); Box { unbox: [Function] }
b3.unbox(console.log); //undefined

There is no way out of the box...

Monads

  • Enhanced Functors (A step above Arrays)
  • Have FlatMap functionality
  • Return Same Type Of Monads
  • Hence, Chainable

Just a design pattern that makes it easy to stitch complex functionalities together!

Monads

function Box(x) {
  let value = x;
  this.unbox = fn => 
        new Box(fn(value));
this.extract= fn => fn(value);
}

Box.of = (v) => new Box(v);

let square=(n)=>n*n;

let v = new Box(2);
let v1 = v
  .unbox(square) //4
  .unbox(Math.sqrt); //2
console.log(v1.unbox(console.log));

const binary = n => n.toString(2);
const bits = s => s.split('');
const bitSize = s => s.length;

let b = Box.of(20);

let b2 = b
  .unbox(binary) //10100
  .unbox(bits) // ['1','0','1','0','0']
  .unbox(bitSize); //5
let b3=b2.unbox(console.log); //5
console.log(b3); Box { unbox: [Function] }
b3.unbox(console.log); //undefined

Of is unit & unbox is flatMap

Libraries

  • Ramda
  • Loadash/fp
  • Monet.js
  • Underscore

References

Thank You

Functional Programming in Javascript

By Vijayabharathi Balasubramanian

Functional Programming in Javascript

Introduction to functional programming principles and how to apply them on JavaScript programs.

  • 441
Loading comments...

More from Vijayabharathi Balasubramanian