Factorial

Different ways to define factorial function. Using it because it is good example of recursive function.

Imperative implementation

const fact = n => {
  let res = 1
  for (let i = 1; i <= n; i++) {
    res = res * i
  }
  return res
}

Recursive implementation without TCO

const fact = n => n < 2 ? 1 : n * fact(n-1)

//fact(50000)
//RangeError: Maximum call stack size exceeded.

Recursive implementation with TCO

Requires ES6 tail call optimisation

'use strict';

const factIter = (product, counter, maxCount) => {
  if (counter > maxCount) return product
  return factIter(counter * product, counter + 1, maxCount)
}

const fact = n => factIter(1, 1, n)

//fact(50000)
//Infinity

Recursive implementation with trampoline

Unsafe hack with eval

const trampoline = (fn) => {
  let newFn = eval(fn.toString().replace(new RegExp(fn.name + '\\(([^\\)]*)\\)'), '[$1]'))
  return function() {
    let result = newFn.apply(null, arguments)
    while (result instanceof Array) result = newFn.apply(null, result)
    return result
  }
}

const _factIter = (product, counter, maxCount) => {
  if (counter > maxCount) return product
  return _factIter(counter * product, counter + 1, maxCount)
}

const factIter = trampoline(_factIter)

Straightforward implementation with function (note: () => {} will not work here):

const trampoline = (fn) => {
  return function () {
    let result = fn.apply(null, arguments)
    while (result instanceof Function) result = result()
    return result
  }
}

const _factIter = (product, counter, maxCount) => {
  if (counter > maxCount) return product
  return () => _factIter(counter * product, counter + 1, maxCount)
}

const factIter = trampoline(_factIter)

const fact = n => factIter(1, 1, n)

//fact(50000)
//Infinity

Recursive, but not recursive implementation

Formally fact function is not recursive at the moment of declaration, but instead we rely on closures. Original idea is from Strachey paper.

let g
const fact = n => n < 2 ? 1 : n * g(n-1)
g = fact

Obviously it is still recursive by nature, if we expand last line:

g = n => n < 2 ? 1 : n * g(n-1)

Fixed-point combinator implementation

Most known fixed-point combinator is Y combinator, but it will not work in JS, because of order of evaluation. So we will use Z combinator.

// The Y combinator is defined as:
// Y          = λf.(λx.f (x x))(λx.f (x x))
// The Z combinator is defined as:
// Z          = λf.(λx.f (λv.((x x) v))) (λx.f (λv.((x x) v)))

const Z       = f => (x => f (v => ((x (x)) (v)))) (x => f (v => ((x (x)) (v))))
const factgen = fact => n => n < 2 ? 1 : n * fact(n-1)
const fact    = Z(factgen)

Effective implementation

Despite the fact it is often used as example of recursive function, it is very ineffective way to calculate it. Check out this link for effective implementations.

Real numbers?

Use Euler’s gamma function

Haskell variations

See “The Evolution of a Haskell Programmer”.