Watch course

What is functional programming?

Functional programming is a coding paradigm inspired by the "Lambda Calculus"

The lambda functions are uni arity functions, therefore, most of our functions will be defined this way.

The four pillars of OOP

Abstraction

Inheritance

Polymorphism

Encapsulation

The four pillars of functional programming

Purity

Recursion

Closures

Immutability

Coding Styles

Imperative

Declarative

Functional

OOP

Imperative

Follow don't think
do this then do that "as I say".
const findMismatchingElement = (needle, haystack) => {
  for (let i = 0; i < needle.length; i++) {
    if (haystack.indexOf(needle[i]) === -1) return [needle[i]]
  }

  return []
}

Declarative

Find out your way of doing my task
I want to do that, "do it your way"
const findMismatchingElement = (needle, haystack) =>
  needle.filter((item) => haystack.indexOf(item) === -1)

Functions

A function is an I/O process of data

f(x) = tan(x)

Functions

A function is an I/O process of data

Triangulate

Functions

A function is an I/O process of data

const sum = (...numbers) => numbers.reduce((val, accum) => val + accum, 0)

Sum

Numbers

Procedures

A procedure is set of instructions performed on some sort of data, more likely an input

function addNumbers(...numbers) {
  let total = 0

  for (number of numbers) {
    total += number
  }

  console.log(total)
}

Side effects

  • Indirect inputs/outputs
  • I/O interactions (files, console.. etc)
  • Hitting Restful APIs
  • Manipulating the DOM
  • Interacting with databases
  • Changing the state of the program
  • Modifying data (or inputs)
  • Heating up CPU
  • etc...

Pure functions

A pure function is a function that does not interact with the outside world i.e. global scope

  • Independent function
  • Depends only on its parameters
  • Doesn't pollute the global space
  • Doesn't cause any side effects
  • Its output is predictable

Pure functions

A pure function is a function that does not interact with the outside world i.e. global scope

Impure

let first_name = 'Ahmed';

const greeting = () => console.log(`Hello ${first_name}`);

Pure

const greeting = (first_name) => `Hello ${first_name}`;

Pure functions

A pure function is a function that does not interact with the outside world i.e. global scope

Impure

const coords = { x: 10, y: 5 };

const increaseHorizontalCoordinate = (amount) => {
  coords.x += amount;
};

console.log(coords);

increaseHorizontalCoordinate(2);

console.log(coords);

Pure

const coords = { x: 10, y: 5 };

const increaseHorizontalCoordinate = (coords, amount) => {
  return {
    x: coords.x + amount,
    y: coords.y,
  };
};

console.log(coords);

console.log(increaseHorizontalCoordinate(coords, 2));

Extracting Impurity

let tweet = {
  user: {
    id: '119C9AE6F9CA741BD0A76F87FBA0B22CAB5413187AFB2906AA2875C38E213603',
    name: 'Prof. James Moriarty',
  },
  message: 'In world of locked rooms, the man with the key is a KING',
}

const makeTweet = (tweet) => {
  let record = {
    id: generateIdFor(tweet),
    userID: tweet.user.id,
    twitter: tweet.user.name,
    message: tweet.message,
    time: Date.now(),
  }

  let elem = buildTweetElement(record)

  tweetsList.appendChild(elem)
}

Impure

Extracting Impurity

let tweet = {
  user: {
    id: '119C9AE6F9CA741BD0A76F87FBA0B22CAB5413187AFB2906AA2875C38E213603',
    name: 'Prof. James Moriarty',
  },
  message: 'In world of locked rooms, the man with the key is a KING',
}

const makeTweet = (tweet, id) => ({
  id,
  userID: tweet.user.id,
  twitter: tweet.user.name,
  message: tweet.message,
  time: Date.now(),
})

const addTweet = (tweet) => {
  tweetsList.appendChild(
    buildTweetElement(makeTweet(tweet, generateIdFor(tweet)))
  )
}

Pure

Containing Impurity

Higher order functions

(HOFS)

Functions in JavaScript are considered first-class citizens which enables them to be

  • Passed as arguments
  • Stored in variables
  • Returned from other functions

Higher order functions

(HOFS)

function loopAndDouble(array) {
  for (let i = 0; i < array.length; i++) {
    array[i] *= 2;
  }
}

function loopAndHalfen(array) {
  for (let i = 0; i < array.length; i++) {
    array[i] /= 2;
  }
}
function loopAndAddTwo(array) {
  for (let i = 0; i < array.length; i++) {
    array[i] += 2;
  }
}

function loopAndSubtractTwo(array) {
  for (let i = 0; i < array.length; i++) {
    array[i] -= 2;
  }
}

Besides the fact that these functions are completely repetitive, they're modifying the main array which causes impurity.

Higher order functions

(HOFS)

const double = (n) => n * 2
const halfen = (n) => n / 2

let doubled = applyRule([1, 2, 3], double) // [2, 4, 6]
let halfed = applyRule([1, 2, 3], halfen)  // [0.5, 2, 1.5]
const applyRule = (array, rule) => array.map(rule)

This refactor guarantees you not repeating your code (applying DRY) and a lot more concise, pure and declarative code

Functions' arguments

Arguments and parameters are always used interchangeably

The difference between them is that argument is the value passed to a function on calling this function

const add = (x, y) => x + y

let z = add(2, 3)

Parameter

Argument

Functions' arguments

Arguments do shape the function

const add = (x, y) => x + y

let z = add(2, 3)

The add function is called binary function

const add = (x, y) => x + y

const increase = (x) => add(x, 1)

The increase function is called unary function

Adapting functions' arguments

const unary = (fn) => (arg) => fn(arg)

const binary = (fn) => (arg1, arg2) => fn(arg1, arg2)
const flip = (fn) => (arg1, arg2, ...args) => fn(arg2, arg1, ...args)

const reverse = (fn) => (...args) => fn(...args.reverse())
const apply = (fn) => (...args) => fn(...args)
const f = (...args) => args

Equational Reasoning

doSomething(function operation(input) {
  return predicate(input)
})
doSomething(function predicate(input) {
  return operation(input)
})

Functions of the same shape, are interchangeable

Point free

Point-free functions: defining a function without defining its points (aka inputs)

So what are point-free functions?

Point free

const isEven = (number) => (number % 2 == 0)
const isOdd = (number) => (number % 2 == 1)
const isOdd = number => !isEven(number)

These functions are all not point-free functions

as they are defining their inputs

Higher order functions

(HOFS)

Functions in JavaScript are considered first-class citizens which enables them to be

  • Passed as arguments
  • Stored in variables
  • Returned from other functions

Higher order functions

(HOFS)

function loopAndDouble(array) {
  for (let i = 0; i < array.length; i++) {
    array[i] *= 2;
  }
}

function loopAndHalfen(array) {
  for (let i = 0; i < array.length; i++) {
    array[i] /= 2;
  }
}
function loopAndAddTwo(array) {
  for (let i = 0; i < array.length; i++) {
    array[i] += 2;
  }
}

function loopAndSubtractTwo(array) {
  for (let i = 0; i < array.length; i++) {
    array[i] -= 2;
  }
}

Besides the fact that these functions are completely repetitive, they're modifying the main array which causes impurity.

Higher order functions

(HOFS)

const double = (n) => n * 2
const halfen = (n) => n / 2

let doubled = map([1, 2, 3], double) // [2, 4, 6]
let halfed = map([1, 2, 3], halfen)  // [0.5, 2, 1.5]
const map = (array, fn) => array.map(fn)

This refactor guarantees you not repeating your code (applying DRY) and a lot more concise, pure and declarative code

const double = (n) => n * 2
const halfen = (n) => n / 2

let doubled = map([1, 2, 3], double) // [2, 4, 6]
let halfed = map([1, 2, 3], halfen)  // [0.5, 2, 1.5]
const map = (array, fn) => array.map(fn)

The map function under the hood

function map(array, fn) {
  let copy = []

  for (let i = 0; i < array.length; i++) {
    copy.push(fn(array[i]))
  }

  return copy
}
function length(array) {
  return array.length
}

function head(array) {
  return array[0]
}

function concat(array1, array2) {
  return array1.concat(array2)
}

function tail(array) {
  return array.slice(1)
}
const map = (array, fn) => array.map(fn)

The map function under the hood

function map(array, fn) {
  let copy = []

  for (let i = 0; i < array.length; i++) {
    copy.push(fn(array[i]))
  }

  return copy
}
function map(array, fn) {
  if (length(array) === 0) return []
  
  return [fn(head(array))].concat(map(tail(array), fn))
}
const map = (array, fn) => array.map(fn)

The map function under the hood

function filter(array, fn) {
  let copy = []

  for (let i = 0; i < array.length; i++) {
    if (fn(array[i])) {
      copy.push(array[i])
    }
  }

  return copy
}
const filter = (array, fn) => array.filter(fn)

The filter function under the hood

function filter(array, predicateFn) {
  if (length(array) === 0) return []

  const firstItem = head(array)
  const filteredFirst = predicateFn(firstItem) ? [firstItem] : []

  return concat(filteredFirst, filter(tail(array), predicateFn))
}
const filter = (array, fn) => array.filter(fn)

The filter function under the hood

function reduce(arr, reducer, initialValue) {
  for (let i = 0; i < arr.length; i++) {
    initialValue = reducer(initialValue, arr[i])
  }

  return initialValue
}
const reduce = (array, fn, initialValue) => array.reduce(fn, initialValue)

The reduce function under the hood

function reduce(array, reducer, initial) {
  if (length(array) === 0) return initial

  const newInitialValue = reducer(initial, head(array))

  return reduce(tail(array), reducer, newInitialValue)
}
const reduce = (array, fn, initialValue) => array.reduce(fn, initialValue)

The reduce function under the hood

function mapReduce(array, fn) {
  let copy = []

  for (let i = 0; i < array.length; i++) {
    copy.push(fn(array[i]))
  }

  return function reduce(reducer, initialValue, array = copy) {
    let accumulator = initialValue === undefined ? 0 : initialValue

    for (let i = 0; i < array.length; i++)
      accumulator = reducer(accumulator, array[i], i, array)

    return accumulator
  }
}
const value = [1,2,3,4].map(x => x * 2).reduce((a, b) => a + b, 0)

The mapReduce function under the hood

function mapReduce(array, fn) {
  if (length(array) === 0) return []

  let arr = [fn(head(array))].concat(map(tail(array), fn))

  return function reduce(reducer, initial) {
    function apply(x, y, array = arr) {
      if (length(array) == 0) return y

      const newInitialValue = x(y, head(array))

      return apply(reducer, newInitialValue, tail(array))
    }

    return apply(reducer, initial)
  }
}
const value = [1,2,3,4].map(x => x * 2).reduce((a, b) => a + b, 0)

The mapReduce function under the hood

Scopes in JS

  • Global scope
  • Script Scope
  • Local Scope
  • Block scopes
  • Lexical scope
A scope is the variables that a function can access at certian line ( at function call).
Global scope is when you define a variable appended to the window object

Global Scope

Global scope is when you define a variable appended to the window object
var first_name = "Ahmed"

Script Scope

Script scope is when you define a variable that's accessible everywhere but not appended to the window object
var first_name = "Ahmed"

let last_name = "Osama"

Local Scope

Local scope is when you define a variable inside a functional scope 
function one() {
  let x = 1

  function two() {
    let x = 2

    function three() {
      let x = 3
    }
  }
}

Block Scope

Block scope is when you define a variable within any curly braces or a statement on condition that you use a let instead of var
for (var i = 0; i < 5; i++) {
  console.log(`i value now is: ${i}`);
}

console.log(`i value now is: ${i}`)

...

Block Scope

for (var i = 0; i < 5; i++) {
  console.log(`i value now is: ${i}`);
}

console.log(`i value now is: ${i}`)

for (let j = 0; j < 5; i++) {
  console.log(`j value now is: ${j}`);
}

console.log(j) // reference error
Block scope is when you define a variable within any curly braces or a statement on condition that you use a let instead of var

Block Scope

if (true) {
  var x = 5;
}

console.log(x); // 5

...
Block scope is when you define a variable within any curly braces or a statement on condition that you use a let instead of var

Block Scope

if (true) {
  var x = 5;
}

console.log(x); // 5

if (false) {
  // some dummy code
} else {
  const y = 5;
}

console.log(y); // reference error
Block scope is when you define a variable within any curly braces or a statement on condition that you use a let instead of var

Block Scope

{
  var x = 5
}

console.log(x) // 5

...
Block scope is when you define a variable within any curly braces or a statement on condition that you use a let instead of var

Block Scope

{
  var x = 5
}

console.log(x) // 5

{
  let y = 5;
}

console.log(y) // reference error
Block scope is when you define a variable within any curly braces or a statement on condition that you use a let instead of var

Lexical Scope

let x = 5

function firstLayer() {
  console.log(x)

  ...
}

firstLayer()
Lexical scope is when a group of nested functions have access to their defined variables and the variables that are defined in their parent scope.

Lexical Scope

let x = 5

function firstLayer() {
  console.log(x)

  let y = 3

  return function secondLayer() {
    console.log(y)
    
    ...
  }
}

firstLayer()()
Lexical scope is when a group of nested functions have access to their defined variables and the variables that are defined in their parent scope.

Lexical Scope == [[Scopes]]

function counter() {
  ...
}

let x = counter()

Closures

function counter() {
  var initial = 0
  var final = 5

  ...
}

let x = counter()

Closures

function counter() {
  var initial = 0
  var final = 5

  var increase = function () {
    return initial++
  }

  return increase
}

let x = counter()

Closures

function counter() {
  var initial = 0
  var final = 5

  return function increase() {
	return initial++
  }
}

let x = counter()

Closures

function counter() {
  var initial = 0
  var final = 5

  var increase = () => initial++;
  
  var decrease = () => initial--;
  
  return {
    increase,
    decrease
  }
}

let x = counter()

Closures

Kyle Simpson

Closures

Closure is when a function "remembers" its lexical scope even when the function is executed outside that lexical scope.
function counter() {
  var idx = 1

  return function one() {
    let idx1 = 2

    return function two() {
      let idx2 = 3

      return idx + idx1 + idx2
    }
  }
}

Use cases of closures

Currying

Partial Apps

Encapsulation

Stateful function

Memoization

Shaping functions

Module pattern

Partial applications means that you transform a function from being general to special by partially applying some parameters.

Optimizing Recursion

Mocking classes

Use cases of closures

Currying

Encapsulation

Stateful function

Memoization

Shaping functions

Module pattern

Partial Apps

var list = (joint, ...items) => {
  ...
}

list('and', 'one', 'two', 'three') // one, two, and three
list('or', 'one', 'two') // one or two
Partial applications means that you transform a function from being general to special by partially applying some parameters.

Mocking classes

Optimizing Recursion

Currying

Encapsulation

Stateful function

Memoization

Shaping functions

Module pattern

Partial Apps

Partial applications means that you transform a function from being general to special by partially applying some parameters.
var list = (joint, ...items) => {
  var commaSeparated = items.slice(0, -1).join(', ')

  var lastItem = items.pop()

  return `${commaSeparated} ${joint} ${lastItem}`
}

list('and', 'one', 'two', 'three') // one, two, and three
list('or', 'one', 'two') // one or two

Mocking classes

Optimizing Recursion

Currying

Encapsulation

Stateful function

Memoization

Shaping functions

Module pattern

Partial Apps

Partial applications means that you transform a function from being general to special by partially applying some parameters.
var list = (joint, ...items) => {...}

function partial(fn, ...args) {
  return function partiallyAppliedArgs(...rest) {
    return fn(...args, ...rest)
  }
}

list('and', 'one', 'two', 'three') // one, two and three
list('or', 'one', 'two') // one or two
                                   
var listAnd = partial(list, 'and') // function partiallyAppliedArgs(...rest) {}

listAnd('five', 'six', 'seven') // five, six and seven

Mocking classes

Optimizing Recursion

Currying

Encapsulation

Stateful function

Memoization

Shaping functions

Module pattern

Partial Apps

function multiply(x, y) {
  return x * y;
}

function pow(base, exp) {
  return base ** exp;
}
Currying is the action of reducing functions arity from enary to unary by producing newer functions with prefilled inputs.

Mocking classes

Optimizing Recursion

Currying

Encapsulation

Stateful function

Memoization

Shaping functions

Module pattern

function multiply(x, y) {
  return x * y;
}

function pow(base, exp) {
  return base ** exp;
}
Currying is the action of reducing functions arity from enary to unary by producing newer functions with prefilled inputs.

General functions

Mocking classes

Optimizing Recursion

Currying

Encapsulation

Stateful function

Memoization

Shaping functions

Module pattern

function multiply(x, y) {...}

function pow(base, exp) {...}

var double = (x) => mutliply(2, x)
var square = (x) => pow(x, 2)
Currying is the action of reducing functions arity from enary to unary by producing newer functions with prefilled inputs.

General functions

Mocking classes

Optimizing Recursion

Currying

Encapsulation

Stateful function

Memoization

Shaping functions

Module pattern

function mutliply(y) {
  return function multiplyFor(x) {
    return x * y;
  }
}

function pow(exponent) {
  return function powFor(base) {
    return base ** exponent;
  }
}
Currying is the action of reducing functions arity from enary to unary by producing newer functions with prefilled inputs.

General functions

Mocking classes

Optimizing Recursion

Currying

Encapsulation

Stateful function

Memoization

Shaping functions

Module pattern

function mod(y) {
  return function modFor(x) {
    return x % y;
  }
}

function eq(y) {
  return function eqTo(x) {
    return x == y;
  }
}
Currying is the action of reducing functions arity from enary to unary by producing newer functions with prefilled inputs.

Mocking classes

Optimizing Recursion

Currying

Encapsulation

Stateful function

Memoization

Shaping functions

Module pattern

const R = require('ramda')

var list = (joint, items) =>
  `${items.slice(0, -1).join(', ')} ${joint} ${items.pop()}`

var curriedList = R.curry(list)

var orList = curriedList('or')
var andList = curriedList('and')

console.log(curriedList('or', ['one', 'two', 'three'])) // one, two or three
console.log(orList(['one', 'two'])) // one or two
console.log(andList(['one', 'two'])) // one and two
console.log(andList(['one', 'two', 'three', 'four'])) // one, two, three and four
Currying is the action of reducing functions arity from enary to unary by producing newer functions with prefilled inputs.

Mocking classes

Optimizing Recursion

Currying

Encapsulation

Stateful function

Memoization

Shaping functions

Module pattern

Encapsulation is the act of hiding state and make it only accessible to class memebers.

Mocking classes

class Encapsulation {
  #state = []
}

var enc = new Encapsulation()

console.log(enc.state)  

/* index.js
 * returns undefined not an empty 
 * array as state is completely 
 * different from the private 
 * member #state 
*/
class Encapsulate {
  private state: any = []
}

var enc = new Encapsulate()

console.log(enc.state)

/* index.ts 
 * Throws an error as we cannot
 * access a private property outside
 * its context
*/

Optimizing Recursion

Encapsulation

Stateful function

Memoization

Shaping functions

Module pattern

Encapsulation is the act of hiding state and make it only accessible to class memebers.

Mocking classes

class Encapsulate {
  #state = null

  constructor(state) {
    this.#state = state
  }

  get state() {
    return this.#state
  }
}

var enc = new Encapsulate([])

console.log(enc.state) // []
class Encapsulate {
  private _state: any

  constructor(state: any) {
    this._state = state
  }

  get state() {
    return this._state
  }
}

var enc = new Encapsulate([])

console.log(enc.state) // []

Optimizing Recursion

Encapsulation

Stateful function

Memoization

Shaping functions

Module pattern

Encapsulation is the act of hiding state and make it only accessible to class memebers.

Mocking classes

function encapsulation(state = null) {
  return {
    get state() {
      return state
    },
  }
}

var enc = encapsulation([])

console.log(enc.state)
function ecnapsulation(state: any = null) {
  return {
    get state() {
      return state
    },
  }
}

var enc = ecnapsulation([])

console.log(enc.state)

Optimizing Recursion

Encapsulation

Stateful function

Memoization

Shaping functions

Module pattern

Mocking classes

Lazy/Eager Execution

function repeat(count) {
  return function generateString(string) {
    return string.repeat(count)
  }
}

let word = repeat(10)

word("a ") // "a a a a a a a a a a "
word("a ") // "a a a a a a a a a a "

Optimizing Recursion

Stateful function

Memoization

Shaping functions

Module pattern

Mocking classes

Lazy/Eager Execution

function repeat(count) {
  var str = "a ".repeat(count)
  return function generateString() {
    return str
  }
}

let word = repeat(10)

word() // "a a a a a a a a a a "
word() // "a a a a a a a a a a "

Optimizing Recursion

Stateful function

Memoization

Shaping functions

Module pattern

Mocking classes

Lazy/Eager Execution

function repeat(count) {
  var str
  return function generateString(string) {
    if (typeof str == 'undefined') {
      str = string.repeat(count)
    }
    
    return str
  }
}

let word = repeat(10)

word('a ') // "a a a a a a a a a a "
word('a ') // "a a a a a a a a a a "

Optimizing Recursion

Stateful function

Memoization

Shaping functions

Module pattern

Mocking classes

function repeat(count) {
  return R.memoizeWith(function generateString(string) {
    return string.repeat(count)
  })
}

let word = repeat(10)

word('a ') // "a a a a a a a a a a "
word('a ') // "a a a a a a a a a a "

Optimizing Recursion

Stateful function

Memoization

Shaping functions

Module pattern

Mocking classes

;[1, 2, 3].map(increase)

function add(x, y) {
  return x + y
}

function increase(number) {
  return add(1, number)
}

Optimizing Recursion

Stateful function

Memoization

Shaping functions

Module pattern

Mocking classes

const R = require('ramda')

var add = R.curry(function add(x, y) {
  return x + y
})

;[1, 2, 3].map(add(1))

Optimizing Recursion

function reverse(fn) {
  return function (...args) {
    return fn(...args.reverse())
  }
}

function apply(fn) {
  return function (...args) {
    return fn(...args)
  }
}

Stateful function

Memoization

Shaping functions

Module pattern

Mocking classes

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

function binary(fn) {
  return function two(arg1, arg2) {
    return fn(arg1, arg2)
  }
}
function reverse(fn) {
  return function (...args) {
    return fn(...args.reverse())
  }
}

function apply(fn) {
  return function (...args) {
    return fn(...args)
  }
}

Optimizing Recursion

Shaping functions

Module pattern

Mocking classes

var counter = (function (initial) {
  return { increase }

  function increase() {
    return ++initial
  }
})(0)
function reverse(fn) {
  return function (...args) {
    return fn(...args.reverse())
  }
}

function apply(fn) {
  return function (...args) {
    return fn(...args)
  }
}

Optimizing Recursion

Module pattern

Mocking classes

class Counter {
  constructor(initial) {
    this.initial = initial
  }

  increase(step = 1) {
    return (this.initial += step)
  }

  decrease(step = 1) {
    return (this.initial -= step)
  }
}

Optimizing Recursion

Module pattern

Mocking classes

var counter = (function counter(initial = 0) {
  return { increase, decrease }

  function increase(step = 1) {
    return (initial += step)
  }

  function decrease(step = 1) {
    return (initial -= step)
  }
})()

Optimizing Recursion

Nesting functions

Unix philosophy

Nesting functions

Unix philosophy

Ken Thompson

Make each program do one thing well. To do a new job, build afresh rather than complicate old programs by adding new “features.”

Expect the output of every program to become the input to another, as yet unknown, program.

Don't clutter output with extraneous information.

Avoid stringently columnar or binary input formats.

Don't insist on interactive input.

Math philosophy

Function composition is a mathematical term where two functions are called successively, the first function's output is passed as an input to the second function.

z = f . g
z = f(g(x))

Unix philosophy

Function composition is a mathematical term where two functions are called successively, the first function's output is passed as an input to the second function.

z = f . g
z = f(g(x))
f(x) = x^2+2x
g(x) = x^3+2x^2+x

Math philosophy

Function composition is a mathematical term where two functions are called successively, the first function's output is passed as an input to the second function.

z = f . g
z = f(g(x))
f(x) = x^2+2x
g(x) = x^3+2x^2+x
z = f(g(3))

Math philosophy

Function composition is a mathematical term where two functions are called successively, the first function's output is passed as an input to the second function.

z = f . g
z = f(g(3))
f(48) = 48^2+2*48
g(3) = 3^3+2*3^2+3
z = 2400

Math philosophy

OOP Philosophy

z = f . g
z = f(g(3))
f(48) = 48^2+2*48
g(3) = 3^3+2*3^2+3
z = 2400
import axios from 'axios'

const { data } = await axios.get('http://jsonplaceholder.typicode.com/todos')

const ab = data
  .slice(0, 5)
  .filter((todo) => todo.completed)
  .map((todo) => todo.title)

console.log(ab)

Function composition, nesting functions may be known to you as function chaining where.

FP implementation

import axios from 'axios'
import R from 'ramda'

const { data } = await axios.get('http://jsonplaceholder.typicode.com/todos')

const collectTitles = R.compose(
  R.map((todo) => todo.title),
  R.filter((todo) => todo.completed),
  R.slice(0, 5)
)

console.log(collectTitles(data))

Function composition, nesting functions may be known to you as function chaining where.

Pipelines & composition

function pipe(...fns) {...}

function compose(...fns) {...}

/* What's the difference between piping and composing? */

Function composition, nesting functions may be known to you as function chaining where.

function compose(...fns) {...}

const add = (x) => x + 1
const double = (x) => x * 2
const decrease = (x) => x - 1

// compose(add, double, decrease) == compose(compose(add, double), decrease) 
// == compose(add, compose(double, decrease))

Composition Associativity

import R from 'ramda'

const modulo = R.curry((y) => (x) => x % y)

const isOdd = R.compose(R.equals(1), modulo(2))
import axios from 'axios'
import R from 'ramda'

const { data } = await axios.get('http://jsonplaceholder.typicode.com/todos')

const collectTitles = R.compose(
  R.map((todo) => todo.title),
  R.filter((todo) => todo.completed),
  R.slice(0, 5)
)

console.log(collectTitles(data))

Revisiting point-free

import R from 'ramda'

const modulo = R.curry((y) => (x) => x % y)

const isOdd = R.compose(R.equals(1), modulo(2))

Recursion

Recursion

Recursion

Recursion

Recursion

Recursion

A recursive function is a function that calls itself within its inner scope
but given a smaller input than the original one passed

function inception() {
  inception();
}

// inception();

Recursion flaws

Recursive functions can consume whole a lot of memory and much much time to execute some tasks

Also can be tricky concept to grab your head around at first.

Last but not least, its syntax may seem awkard to anyone.

Recursion

Why the hell bother to learning something that awful then!?

Recursion

Although recursion can be a pain in the ass, but it comes with whole lot pros

  • Implemented in most of algorithms
  • Commonly used with data structures
  • Repetitive successive tasks
  • Can be optimized to be linear time
  • Can be optimized to use less memory
  • Some tasks are much easier to do with it
  • Unlike iteration, recursion is stateless

Recursion

Back to our lovely inception() function

debugging this function shows a huge relation between recursion and call stack

function inception() {
  inception();
}

// inception();

Thus the recursion consumes all the memory we have in our system and causes stack over flow

Recursion anatomy

Base case

Recursion case

The case at which the recursion stops and returns its value to the previous function call

The case at which the recursive function is recursing, till it reaches the base case and exits

Recursion workflow

Execution context

Execution context

Execution context

Execution context

Global Execution Context

Call stack

Recursion workflow

Execution context

Execution context

Execution context

Execution context

Global Execution Context

Call stack

Factorial explained

Execution context

Execution context

Execution context

Execution context

Global Execution Context

Call stack

factorial(5)

factorial(4)

factorial(3)

factorial(2)

factorial(1)

5

*

3

*

4

*

2

*

Factorial explained

Execution context

Execution context

Execution context

Execution context

Global Execution Context

Call stack

factorial(5)

factorial(4)

factorial(3)

factorial(2)

factorial(1)

5

*

3

*

4

*

2

*

1

2

6

24

120

Optimizing recursion

As mentioned earlier, recursion can consume a lot of memory and takes long time to complete a task

which makes our programs much slower

So, how to improve that mess?
Luckily, recursion can be optimized using Tail Call Optimizations (TCO)

PTC

STC

Optimizing recursion

Trampolines

Optimizing recursion

Trampolines

function trampoline(fn) {
  return function (...args) {
    let result = fn(...args)
    
    while (typeof result == 'function') {
      result = result()
    }
    
    return result
  }
}

Immutability

the state of not changing, or being unable to be changed

Immutability

const user = {
  id: 42,
  full_name: { first_name: 'ahmed', last_name: 'osama' },
  likes: [{}],
  comments: [{}],
  posts: [{}],
}

doSomethingEvil(user) // May modify user?

dbEntity.save(user) // ?????

Immutability

const user = {
  id: 42,
  full_name: { first_name: 'ahmed', last_name: 'osama' },
  likes: [{}],
  comments: [{}],
  posts: [{}],
}

function doSomethingEvil(obj) {
  obj.id = undefined
}

doSomethingEvil(user)

dbEntity.save(user) // Error

Primitive immutability

the state of not changing, or being unable to be changed
var x = 3
let taxRate = 0.3
const PI = 3.14

var first_name = 'ahmed'

Rethinking Const

Const is some how mutable ^^
const PI = 3.14

const arr = [1,2,3]

const todo = {
  task: "Go to sleep",
  status: "delayed"
}

Immutable Objects

const config = Object.freeze({
  db: {},
  mail: {},
})

Immutable Objects

const config = Object.freeze({
  db: {
    username: "root"
  },
  mail: {},
})

function connect(config) {
  config.db.username = "ahmed"
  
  return config
}

Copy, don't mutate

const config = Object.freeze({
  db: {
    username: 'root',
  },
  mail: {},
})

function connect(config) {
  return {
    ...config,
    db: {
      username: 'ahmed',
    },
  }
}

console.log(config)
console.log(connect(config))
console.log(config)

Immutable data structures

Functors

Me, 👉👈

A functor is anything that is mappable
let numbers = [1, 2, 3, 4, 5]

let names = ['ahmed', 'mohamed', 'mahmoud']

let todos = [
  {
    id: 42,
    task: 'Record screen casts',
    completed: true,
  },
  {
    id: 43,
    task: 'Have a walk',
    completed: false,
  },
]

Functors

Me, 👉👈

A functor is anything that is mappable
function objectMapper(mapper, object) {
  let copy = {}
  
  for (let key of Object.keys(object)) {
    copy[key] = mapper(object[key])
  }
  
  return copy
}
Object.prototype.map = function(mapper) {
  let copy = {}
  
  for (let key of Object.keys(this)) {
    copy[key] = mapper(this[key])
  }
  
  return copy
}

Functors

function objectMapper(mapper, object) {
  let copy = {}
  
  for (let key of Object.keys(object)) {
    copy[key] = mapper(object[key])
  }
  
  return copy
}
Object.prototype.map = function(mapper) {
  let copy = {}
  
  for (let key of Object.keys(this)) {
    copy[key] = mapper(this[key])
  }
  
  return copy
}

Me, 👉👈

A functor is anything that is mappable

Functors

Me, 👉👈

A functor is anything that is mappable
function Container(value) {
  this.value = value
}

Functors

Me, 👉👈

A functor is anything that is mappable
function Container(value) {
  this.value = value
}

let functor = new Container(5)

let anotherFunctor = new Container({name: 'ahmed'})

Functors

Me, 👉👈

A functor is anything that is mappable
function Container(value) {
  this.value = value
}

let functor = new Container(5)

let anotherFunctor = new Container([
  {
    userId: 1,
    id: 1,
    title:
      'sunt aut facere repellat provident occaecati excepturi optio reprehenderit',
    body:
      'Lorem ipsum dolor, sit amet consectetur adipisicing elit. A quo voluptatibus',
  },
])

Functors

Me, 👉👈

A functor is anything that is mappable
function Container(value) {
  this.value = value
}

Container.prototype.map = function (mapper) {
  return Container.of(mapper(this.value))
}

Functors

Me, 👉👈

A functor is anything that is mappable
function Container(value) {
  this.value = value
}

Container.prototype.map = function (mapper) {
  return Container.of(mapper(this.value))
}

Container.of = function (value) {
  return new Container(value)
}

Revisiting Map, Filter

Me, uwu

A functor is anything that is mappable
function map(array, mapper) {
  return array.map(mapper)
}

function fitler(array, predicate) {
  return array.filter(predicate)
}

Revisiting Reduce

function reduce(reducer, initialValue, array) {
  return array.reduce(reducer, initialValue)
}

Revisiting Reduce

function map(mapper, array) {
  return array.reduce(
    (list, item) => (list.push(mapper(item)), list),
    []
  )
}


function filter(predicate, array) {
  return array.reduce(
    (list, item) =>
      predicate(item) ? (list.push(item), list) : list,
    []
  )
}

Advanced List ops

function reduce(reducer, initialValue, array) {
  return array.reduce(reducer, initialValue)
}
unique()
flat()
flatMap()
uniqueMap()
merge()
zip()
unique()
flat()
flatMap()
uniqueMap()
merge()
zip()
function unique(array) {
  let uniques = new Set()
  
  for (let item of array) {
    uniques.add(item)
  }
  
  return uniques
}
unique()
flat()
flatMap()
uniqueMap()
merge()
zip()
function unique(array) {
  return array.filter(function (item, index) {
    return array.indexOf(item) === index
  })
}
unique()
flat()
flatMap()
uniqueMap()
merge()
zip()
function unique(array) {
  return array.reduce(function (list, item) {
    return list.indexOf(item) == -1
      ? (list.push(item), list)
      : list
  }, [])
}
unique()
flat()
flatMap()
uniqueMap()
merge()
zip()
function uniqueMap(mapper, array) {
  let uniques = new Set()

  for (let item of array) {
    uniques.add(mapper(item))
  }

  return uniques
}
unique()
flat()
flatMap()
uniqueMap()
merge()
zip()
function uniqueMap(mapper, array) {
  return array.reduce(function (list, item) {
    return list.indexOf(item) == -1
      ? (list.push(mapper(item)), list)
      : list
  }, [])
}
unique()
flat()
flatMap()
uniqueMap()
merge()
zip()
function uniqueMap(mapper, array) {
  return array.reduce(function (list, item) {
    return list.add(mapper(item))
  }, new Set())
}
flat()
flatMap()
uniqueMap()
merge()
zip()
function flatten(array, depth = Math.min()) {
  let result = [];

  array.forEach((item) => {
    if (!Array.isArray(item)) {
      result.push(item);
    } else if (depth === 1) {
      result = result.concat(item);
    } else {
      result = result.concat(flatten(item, depth - 1));
    }
  });

  return result;
}
flat()
flatMap()
merge()
zip()
var flatten = (arr, depth = Infinity) =>
  arr.reduce(
    (list, v) =>
      list.concat(
        depth > 0
          ? depth > 1 && Array.isArray(v)
            ? flatten(v, depth - 1)
            : v
          : [v]
      ),
    []
  )
flat()
flatMap()
merge()
zip()
var flatMap = (mapperFn, arr) =>
  flatten(arr.map(mapperFn), 1)
flat()
flatMap()
var flatMap = (mapperFn, arr) =>
  arr.reduce((list, v) => list.concat(mapperFn(v)), [])
merge()
zip()
flatMap()
merge()
zip()
function zip(array1, array2) {
  var zipped = []
  let arr1 = [...array1]
  let arr2 = [...array2]

  while (arr1.length > 0 && arr2.length > 0) {
    zipped.push([arr1.shift(), arr2.shift()])
  }

  return zipped
}
flatMap()
merge()
zip()
function zip(array1, array2) {
  return array1.map(function makeTuple(item, index) {
    return [item, array2[index]]
  })
}
merge()
zip()
function merge(array1, array2) {
  let merged = []

  let arr1 = [...array1]
  let arr2 = [...array2]

  while (arr1.length > 0 && arr2.length > 0) {
    if (arr1.length > 0) merged.push(arr1.shift())

    if (arr2.length > 0) merged.push(arr2.shift())
  }

  return merged.sort((a, b) => a - b)
}

Advanced List ops

Fusion

merge()
function isEven(number) {
  return number % 2 == 0
}

function double(number) {
  return number * 2
}

function add(x, y) {
  return x + y
}

;[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
  .filter(isEven)
  .map(double)
  .reduce(add) // 84

Fusion

merge()
const isEven = (x) => x % 2 == 0

const double = (x) => x * 2

const add = (x, y) => x + y

const filter = (predicate, array) => array.filter(predicate)

const map = (mapper, array) => array.map(mapper)

const reduce = (reducer, accum, array) =>
  array.reduce(reducer, accum)

reduce(
  add,
  0,
  map(
    double,
    filter(isEven, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
  )
) // 84

Not composable

Transduction

merge()
const R = require('ramda')

const isEven = (x) => x % 2 == 0

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

const transducer = R.compose(
  R.filter(isEven),
  R.map(R.multiply(2))
)

R.transduce(transducer, R.add, 0, numbers) // 84

Transduction

merge()
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

const double = (x) => x * 2
const add = (x, y) => x + y
const isEven = (x) => x % 2 == 0

numbers.reduce(function RemoveOddsThenDoubleAndSum(
  total,
  current
) {
  if (isEven(current)) {
    total = add(total, double(current))
  }

  return total
},
0)

Deriving transduction

merge()
function map(mapper, array) {
  return array.reduce(
    (list, item) => (list.push(mapper(item)), list),
    []
  )
}

...

Deriving transduction

merge()
function map(mapper, array) {
  return array.reduce(
    (list, item) => (list.push(mapper(item)), list),
    []
  )
}

function map(mapper) {
  return function reducer(list, item) {
    return list.concat(mapper(item))
  }
}

Deriving transduction

merge()
function filter(predicate, array) {
  return array.reduce(
    (list, item) =>
      predicate(item) ? (list.push(item), list) : list,
    []
  )
}

function filter(predicate) {
  return function reducer(list, item) {
    return predicate(item) ? list.concat(item) : list
  }
}

Deriving transduction

merge()
function map(mapper) {
  return function reducer(list, item) {
    return list.concat(mapper(item))
  }
}

function filter(predicate) {
  return function reducer(list, item) {
    return predicate(item) ? list.concat(item) : list
  }
}

Deriving transduction

merge()
function map(mappingFn) {
  return function combiner(combineFn) {
    return function reducer(list, v) {
      return combineFn(list, mappingFn(v))
    }
  }
}

function filter(predicateFn) {
  return function combiner(combineFn) {
    return function reducer(list, v) {
      if (predicateFn(v)) return combineFn(list, v)

      return list
    }
  }
}
function compose(...fns) {
  return function composed(v) {
    return fns.reduceRight(function reduceBy(accum, fn) {
      return fn(accum)
    }, v)
  }
}

let transducer = compose(
  map((x) => x),
  filter(isEven),
)

Deriving transduction

merge()
function map(mappingFn) {
  return function combiner(combineFn) {
    return function reducer(list, v) {
      return combineFn(list, mappingFn(v))
    }
  }
}

function filter(predicateFn) {
  return function combiner(combineFn) {
    return function reducer(list, v) {
      if (predicateFn(v)) return combineFn(list, v)

      return list
    }
  }
}
function compose(...fns) {
  return function composed(v) {
    return fns.reduceRight(function reduceBy(accum, fn) {
      return fn(accum)
    }, v)
  }
}

let transducer = compose(
  map((x) => x),
  filter(isEven),
)

Deriving transduction

merge()
function map(mappingFn) {
  return function combiner(combineFn) {
    return function reducer(list, v) {
      return combineFn(list, mappingFn(v))
    }
  }
}

function filter(predicateFn) {
  return function combiner(combineFn) {
    return function reducer(list, v) {
      if (predicateFn(v)) return combineFn(list, v)

      return list
    }
  }
}
function compose(...fns) {
  return function composed(v) {
    return fns.reduceRight(function reduceBy(accum, fn) {
      return fn(accum)
    }, v)
  }
}

let transducer = compose(
  map((x) => x),
  filter(isEven),
)

let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

console.log(numbers.reduce(transducer(add), 0))

Wrapping Up

Wrapping Up

  • Functions side effects point free, pure
  • HOFS: map, filter, reduce, MapReduce ... etc
  • Closures are the most powerful
    • Currying, partial applications
    • Memoization
    • Shaping functions
  • Composition && Piping
  • Recursive functions are optimizable
  • Immutability is for the best
  • Functors && Transducers are data structures operations

What's next?

  • Practice, practice, use FP libraries
  • Data structures using FP
  • Functional Programming design patterns
  • SOLID principles
  • Testing your FP code
  • Asynchronous FP (Rx.js)

What's next?

  • Practice, practice, use FP libraries
  • Data structures using FP
  • Functional Programming design patterns
  • SOLID principles
  • Testing your FP code
  • Asynchronous FP (Rx.js)

SecTheater

SecTheater

Raise up your techinal skills

SecTheater

Made with Slides.com