Intro to Functional JS

Terminology

What is a function?

Functions in the mathematical sense

A function takes input(s) and definitely always has a return value.

Β f(x) = 2x + 3

function fn(x) {
  return 2 * x + 3;
}
function p1() {
  return 'something';
}

function p2(x) {
  const y = x + 1;
}

βœ…

πŸ›‘

Word: Arity

// unary
function foo(x) {
// ..
}

foo.length; // 1
// binary
function foo(x, y) {
// ..
}

foo.length; // 2
// trinary
function foo(x, y, z) {
// ..
}

foo.length; // 3

Side-effects

var y;
function f(x) {
  y = (2 * Math.pow( x, 2 )) + 3;
}

f(2);
y; // 11
function f(x) {
  return (2 * Math.pow( x, 2 )) + 3;
}

var y = f( 2 );
y; // 11

Side-effects

function sum(list) {
  var total = 0;
  for (let i = 0; i < list.length; i++) {
    if (!list[i]) list[i] = 0;
    total = total + list[i];
  }

  return total;
}

var nums = [ 1, 3, 9, 27, , 84 ];
sum( nums ); // 124

Is this code problematic? πŸ€”

A function with no side causes/effects is called a pure function. A pure function is idempotent in the programming sense, because it cannot have any side effects.

Word: Pure Functions

Which is pure

function add(x,y) {
  return x + y;
}
const PI = 3.141592;

function circleArea(radius) {
  return PI * radius * radius;
}

function cylinderVolume(radius,height) {
  return height * circleArea( radius );
}

βœ…

βœ…

2 Words: Referential
Transparency

Referential transparency is the assertion that a function call could be replaced by its output value, and the overall program behavior wouldn’t change.

Pure functions have referential transparency

Higher Order Functions

Functions of Functions

Higher Order Functions

function forEach(list,fn) {
  for (let v of list) {
    fn(v);
  }
}

forEach([1,2,3,4,5], (val) => {
  console.log(val);
});

Higher Order Functions

function foo() {
  return function inner(msg) {
    return msg.toUpperCase();
  };
}

Closures

Closure is when a function remembers and accesses variables from outside of its own scope, even when that function is executed in a different scope.

Closures

function counter (start) {
  var val = start;
  return function current(increment = 1){
    val = val + increment;
    return val;
  };
}

const myCounter = counter(0);

myCounter(); // 1
myCounter(); // 2
myCounter(); // 3

Closures

function incrementBy (inc) {
  return function (value) {
    return value + inc;
  }
}

const increment = incrementBy(1);
const increment2 = incrementBy(2);
const increment3 = incrementBy(3);

increment(5); // 6
increment2(5); // 7
increment3(5); // 8

Β is the process of taking a function with multiple arguments and returning a series of functions that take one argument and eventually resolve to a value.

Currying

function volume(l, w, h) {
  return l * w * h;
}

// curried
function volume1(length) {
  return function(width) {
    return function(height) {
      return height * width * length;
    }
  }
}

volume(2,3,4); // 24
volume1(2)(3)(4); // 24

Currying

function curry(fn) {

  return (function nextCurried(prevArgs) {
 
    return function curried(nextArg) {
      var args = [...prevArgs, nextArg];
 
      if (args.length >= fn.length) {
        return fn(...args);
      }
      
      return nextCurried(args);
    };
 
  })([]);
}

curry()

Composing Functions

Composing Functions

function clap (str) {
  return str.split(' ').join(' πŸ‘ ');
}

function scream (str) {
  return str.toUpperCase();
}

clap(
  scream(
    'Hello World!'
  ) // HELLO WORLD
) // HELLO πŸ‘ WORLD


const louder = compose(clap, scream);

louder('Hello World!') // HELLO πŸ‘ WORLD

basic compose()

function compose2(fn2, fn1) {
  return function composed(origValue) {
    return fn2(fn1(origValue));
  };
}

finalValue <-- func1 <-- func2 <-- ... <-- funcN <-- origValue

What if we want to compose more than two functions?

compose()

function compose(...fns) {
  return fns.reverse().reduce((fn1,fn2) => {
    return function composed(...args){
      return fn2( fn1( ...args ) );
    };
  });
}

Order Matters!

πŸ’€πŸ’€πŸ’€πŸ’€

Order of operations is not Omni-directional in most cases

function add (x) {
  return x + 3;
}

function mul (x) {
  return x * 4;
}


const c1 = compose(add, mul);
const c2 = compose(mull, add);

c1(5) !== c2(5);
function pipe(...fns) {
  return fns.reduce((fn1,fn2) => {
    return function piped(...args){
      return fn2( fn1( ...args ) );
    };
  });
}

pipe()

Point-free style

Boring!

Point Free

function double(x) {
  return x * 2;
}

[1,2,3,4,5].map(v => {
  return double(v);
});
function double(x) {
  return x * 2;
}

[1,2,3,4,5].map(double);
["1","2","3"].map(v => {
  return parseInt(v);
});

Point Free ALL THE THINGS!

["1","2","3"].map(parseInt);

⏬

πŸ’€

unary()

function unary (fn) {
  return function (arg) {
    return fn(arg);
  }
}

Locks the arity to 1

["1","2","3"].map(unary(parseInt));

Immutability and Side Effects

Immutability plays a large role when building pure functions

function reverse (arr) {
  return [...arr].reverse();
}

let arr = [1, 2, 3, 4, 5];
reverse(arr);
function reverse (arr) {
  return arr.reverse();
}

let arr = [1, 2, 3, 4, 5];
reverse(arr);

Immutable, Pure

Mutable, Impure

Immutability with Objects

function process (obj) {
  const newObj = { ...obj };
  
  // DO stuff
  return newObj;
}

const obj = {
  a: 12,
  b: 'hello world',
  c: { x: 0, y: 10 }
};

process(obj);

Immutability with Objects

function process (obj) {
  const newObj = { ...obj, c: { ...obj.c } };
  
  // DO stuff
  return newObj;
}

const obj = {
  a: 12,
  b: 'hello world',
  c: { x: 0, y: 10 }
};

process(obj);

Deep

Immer

import produce from "immer"

const baseState = [
    {
        todo: "Learn typescript",
        done: true
    },
    {
        todo: "Try immer",
        done: false
    }
];

const nextState = produce(baseState, draftState => {
    draftState.push({todo: "Tweet about it"})
    draftState[1].done = true
});

Popular Libraries

Resources:

This Presentation is based on examples and explanations from Functional Lite JavaScriptΒ by Kyle Simpson

Intro to Functional JS

By Abdelrahman Awad

Intro to Functional JS

  • 767