An Introduction to Functional Programming

Samuel Silva

Part 01

In computer science, functional programming is a programming paradigm

 that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data. It is a declarative programming approach, which means programming is done with expressions or declarations instead of statements.

 

Wikipedia

Imperative

vs

Declarative

Let's see some different between an imperative code and a declarative code

  1. Based off a list of candidates coming from some API.
  2. I want to filter all users that live in US.
  3. Allowed to work.
  4. Have JavaScript and React experience.
  5. We want to log all operations

Imperative Code

const users = [...];
const candidatesInUS = [];
const allowedWork = [];
const haveJsAndReact = [];

for(user of users) {
    if (user.country === 'US') {
     candidatesInUS.push(user)
    }
}

for(user of candidatesInUS) {
    if (user.allowedWork) {
     allowedWork.push(user)
   }
}

for(user of allowedWork) {
    let hasJs = false;
    let hasReact = false;
    
    for(skill of user.skills) {
      if (skill === 'JavaScript') {
	hasJs = true
    } else if (skill === 'React') {
        hasReact = true
    }
  }
  
  if (hasJs && hasReact) {
    haveJsAndReact.push(user)
  }
}


console.log('Candidates in US', candidatesInUS.length);
console.log('Allowed Work', allowedWork.length);
console.log('JS and React Experience,', haveJsAndReact.length);
console.log('Candidates', haveJsAndReact)

Declarative Code

const filterInUs = compose(
    logger('Candidates In US'), 
    filter(({country}) => country === 'US')
)

const filterAllowedWork = compose(
    logger('Allowed Work'), 
    filter(({allowedWork}) => allowedWork)
)

const filterJSAndReactSkills = ({skills}) => {
    return contains('JavaScript')(skills) && contains('React')(skills)
};

const filterReactAndJS = compose(
    logger('JS and React Experience'), 
    filter(filterJSAndReactSkills)
)

const filterCandidates = compose(
    filterReactAndJS, 
    filterAllowedWork, 
    filterInUs
);

console.log('Candidates', filterCandidates(users));

Referential Transparency

An expression is referentially transparent if you can replace that expression with its corresponding value without changing the behaviour of the program.

// Without Referential Transparency

function addOne() {
  let i = 10;
  
  i++; // evaluates 10. 

  return i;   
}

function addOne() {
  let i = 10;
  
  10; // Replacing the value of i with the evaluation value will change the result of this function

  return i;   
}

// With Referential Transparency

function addOne2() {
  let i = 10;
  
  let j = i + 1 // evaluates 11

  return i;   
}

function addOne2() {
  let i = 10;
  
  let j = 11; // Replacing the value of the statement with the evaluation result will not change function's return

  return j;   
}

Referential Transparency also means that you don't need to worry about the state outside of your function since you're not mutating data

To fully understand Referential Transparency we need to learn a few important concepts:

  • Pure Functions

  • Immutability

  • Recursion

Pure Functions

A function is considered "pure" if it satisfies two conditions:

 

- it always returns same result given same arguments.

 

- it doesn't produce any side effects (which include things like input/output or mutating data).

Pure Functions

const sum = (a, b) => a + b;

const subtract = (a, b) => a - b;

const average = (a, b) => (a + b) / 2;

const currySum = a => b => a + b

const currySubtract = a => b => a - b;

sum(1, 2);

average(3, 4);

subtract(2, 1);

currySum(1)(2);

currySubract(2)(1);

Non-Pure Functions

const value = 0;

const sum = (a) => value + b;

const showValue = () => {
   console.log(value);
};

sum(1);

showValue()

Immutability

An immutable object is an object whose state cannot be modified after it is created. All operations will in fact create new objects rather than change it.

let user = {
    firstName: 'Samuel',
    lastName: 'Castro'
}

// Immutability

const changeName = firstName => lastName => {
    return {
        firstName,
        lastName
    }
}

// Mutability

const changeName = firstName => lastName => {
    user.firstName = firstName;
    user.lastName = lastName;

    return user;
}


console.log('Immutable', changeNameImmutable('New', 'Name') === user); // ?

console.log('Mutable', changeNameMutable('New', 'Name') === user); // ?
const hobbies = [
    'programming',
    'reading',
    'music'
];

const firstTwo = hobbies.splice(0, 2);

console.log(firstTwo); // ['programming', 'reading']

console.log(hobbies); // ['music'] // original array was mutatle :(

How can we prevent mutability?

const hobbies = Object.freeze([
    'programming',
    'reading',
    'music'
]);

const firstTwo = hobbies.splice(0, 2);

console.log(firstTwo); // ['programming', 'reading'] // TypeError

Object.freeze

Immutable.js

Immutable.js provides many Persistent Immutable data structures including: List, Stack, Map, OrderedMap, Set, OrderedSet and Record.

These data structures are highly efficient on modern JavaScript VMs by using structural sharing via hash maps tries* and vector tries* as popularized by Clojure and Scala, minimizing the need to copy or cache data.

 

* at high level, hash maps and vector tries are implemented algorithms to map over collections/arrays using  economic memory usage and performance improvements.

const { Map } = require('immutable')

const map1 = Map({ a: 1, b: 2, c: 3 })
const map2 = Map({ a: 1, b: 2, c: 3 })

map1.equals(map2) // true
map1 === map2 // false

Recursion

On functional programming all operations should be immutable, so does not make sense to use loop statements like for/while, therefore we need to use recursion.

Factorial

4! = 4 x 3 x 2 x 1 = 24

// Using loops operations

const factorial = (n) => {
	let result = 1;
  
  while(n > 1) {      // :(
    result *= n 
    n--
  }
  
  return result
}

console.log(factorial(4)); // 24

Factorial with Recursion

const factorial = (n) => {
    if (n < 2) return 1
  
  return n * factorial(n - 1)
}

factorial(4); // 24

However there is a problem!

It's pure, we're not mutating data and It's declarative.

What happen if I try call factorial(20000)

const factorial = (n) => {
    if (n < 2) return 1
  
  return n * factorial(n - 1)
}

factorial(20000); // Uncaught RangeError: Maximum call stack size exceeded

4 * factorial(3)

4 * 3 * factorial(2)

4 * 3 * 2 * factorial(1)

4 * 3 * 2 * 1

4 * 3 * 2

4 * 6

24

This algorithm runs in the order of O(n) meaning that it's not scalable at all

Tail Call Optimization

Tail-call optimization is where you are able to avoid allocating a new stack frame for a function because the calling function will simply return the value that it gets from the called function

const factorial = (n, acc = 1) => {
  if (n < 2) return acc
  
  return factorial(n - 1, n * acc)
}

console.clear();

console.log(factorial(20000)); // Infinity

factorial(4)

factorial(3, 4)

factorial(2, 12)

factorial(1, 24)

24

continue...

Takeaway

  • Functional Programming is a declarative coding paradigm based on function composition
  • Pure functions are functions that take arguments and do not change or receive values from outside of itself.
  • Immutability is a concept of functional programming where data should not be changed.
  • Since data should not be mutate, loop operations should be made with recursion.
  • ECMAScript 6 offers tail call optimization, where you can make some function calls without growing the call stack

Coming next:

Higher Order Functions.

Partial Application and Curry functions.

Point Free programming.

Function Composition

Working with Ramda.js

Questions?

Higher Order Function

In mathematics and computer science, a higher-order function is a function that does at least one of the following:

 

- takes one or more functions as arguments (i.e. procedural parameters),

- returns a function as its result.

Made with Slides.com