Functional Programming

in JavaScript

函数式编程本质

  • 支持 高阶函数 (higher-order functions)

  • 追求 函数调用没有副作用 (no side-effect)

Related Concepts

  • First-class & Higher-order Functions

  • Pure Functions

  • Currying

  • Function Composition

  • Hindley-Milner

  • Pattern Matching

  • Lambda Calculus

  • Lazy Evaluation

  • Functor、Applicative Functor & Monad

First-class Functions

First-class functions are values, and can be used in every way that typical expressions can.

assigning to variables


const foo = funxtion() { return 'Hey, js-functional' }

foo() // => 'Hey, js-functional'

storing in data structures

 

const obj = {
  foo: function() { return 'Hey, js-functional' },
}

obj.foo() // => 'Hey, js-functional'

returning by functions


const bar = function() {
  return function() { return 'Hey, js-functional' }
}

const foo = bar()
foo() // => 'Hey, js-functional'

passing as arguments to other functions


const baz = function(func) { return func() }

baz(function() { return 'Hey, js-functional' })
// => 'Hey, js-functional'

Higher-order functions

A higher-order function is a function that does at least one of the following:

  • takes one or more functions as arguments.
  • returns a function as its result.

function noisy(fun) {
  return function() { return fun.apply(null, arguments) }
}

noisy(Boolean)(0) // => false
noisy(Boolean)(1) // => true

Pure Functions

A function where the return value is only determined by its input values, without observable side effects. ​

  • Given the same input, will always return the same output.
  • Produces no side effects.
  • Relies on no external state.

Math.random() // => 0.1599823728657992
Math.random() // => 0.10539394558259518

// `Math.random()` is not pure.


Math.sqrt(2) // => 1.4142135623730951

A function is only pure if, given the same input, it will always produce the same output.

关于函数副作用(side-effect)

变更了更高作用域的的变量


let glob = 1
function foo(x) { return ++glob + x }
console.log(foo(1)) // => 3

关于函数副作用(side-effect)

“隐晦”地修改了引用参数


let glob = 1
const obj = {glob}
function foo(x) { return ++x.glob }
foo(obj)
console.log(glob) // => 2

Benefits of Pure Functions

  • Cacheable & Memoization
  • portable, easier to test & parallelize
  • Referential Transparent
  • can be lazy

memoize技术: 递归函数性能优化


/**
  f(0) = 0
  f(1) = 1
  f(n) = f(n-1) + f(n-2) (n > 1)
**/

function fibonacci(n) {
  if (n === 0 || n === 1) return n
  return fibonacci(n-1) + fibonacci(n-2)
}

console.log(fibonacci(7))
console.log(fibonacci(40))

以存储空间换执行效率


const fibonacci = (function() {
  const cache = {}

  return function fib(n) {
    if (n in cache) return cache[n]
    return (cache[n] = (n === 0 || n === 1) ? n : fib(n-1) + fib(n-2))
  }
})()

console.log(fibonacci(7))
console.log(fibonacci(40))

memoize函数的JS实现


function memoize(func) {
  const memo = {}
  const slice = Array.prototype.slice

  return function() {
    const args = slice.call(arguments)

    if (args in memo) return memo[args]
    return (memo[args] = func.apply(this, args))
  }
}

const memoizedFibonacci = memoize(fibonacci)

console.log(memoizedFibonacci(7))
console.log(memoizedFibonacci(40))

引用透明(Referential Transparent)

An expression is said to be referentially transparent if it can be replaced with its corresponding value without changing the program’s behavior.


# 中国的首都是个美丽的城市。


# 北京是个美丽的城市。

function foo(n, fun) { return fun(n) + fun(n) }

foo(10, fibonacci)

举例:函数推导


function foo(n, fun) { return fun(n) * 2 }

foo(10, fibonacci)

# 编程领域
fibonacci(n) + fibonacci(n) => 2*fibonacci(n)

# 数学领域
f(x) = g(x) + g(x) = 2 * g(x)

如何理解 Curry 化函数?

In mathematics and computer science, currying is the technique of translating the evaluation of a function that takes multiple arguments (or a tuple of arguments) into evaluating a sequence of functions, each with a single argument.

 

—— https://en.wikipedia.org/wiki/Currying     

f\left(a,b,c \right )=>f\left(a\right)\left(b\right)\left(c\right)
f(a,b,c)=>f(a)(b)(c)f\left(a,b,c \right )=>f\left(a\right)\left(b\right)\left(c\right)

一個函數当且仅当只能容许拥有一个参数

假設, js 語法有這樣的限制:


    function max(a, b) {
      return a > b ? a : b;
    }

    max(10086, 10010);
    // => 10086

function max(a) {
  return function(b) {
    return a > b ? a : b;
  }
}

max(10086)(10010);
// => 10086

继续举例

function addFourNumbers(a, b, c, d) {
  return a + b + c + d;
}

addFourNumbers(1, 3, 5, 7);
// => 16

function curriedAddFourNumbers(a) {
  return function(b) {
    return function(c) {
      return function(d) {
        return a + b + c + d;
      }
    };
  }
}

curriedAddFourNumbers(2)(4)(6)(8);
// => 20

借助一些工具:ramdajs

const R = require('ramda')

const addFourNumbers = function(a, b, c, d) {
    return a + b + c + d
}

const curriedAddFourNumbers = R.curry(addFourNumbers)

curriedAddFourNumbers(1)(3)(5)(7)
//=> 16

// 或
const f = curriedAddFourNumbers(1)
const g = f(3)
const h = g(5)
h(7)
//=> 16

Why Curry Needs

S=f\left(a,b,c \right )=\sqrt{p\left ( p-a \right )\left ( p-b \right )\left ( p-c \right )}\left( p=\frac{a+b+c}{2}\right)
S=f(a,b,c)=p(pa)(pb)(pc)(p=a+b+c2)S=f\left(a,b,c \right )=\sqrt{p\left ( p-a \right )\left ( p-b \right )\left ( p-c \right )}\left( p=\frac{a+b+c}{2}\right)

Heron's Formula

Calculate the area of a triangle while only knowing the length of all three sides.



// 根据三条边的长度判断是否能够构成一个三角形
function isTriangle(a, b, c) {
  return (a>0 && b > 0 && c > 0 && a+b > c && a+c > b && b+c > a)
}


// 根据三角形三条边的边长计算其面积
function heronFormula(a, b, c) {
  if (!isTriangle(a, b, c)) throw new Error('Illegal triangle')

  const p = (a+b+c) / 2

  return Math.sqrt(p*(p-a)*(p-b)*(p-c))
}


// 计算已知一条边长为3,其它两条边未知的的三角形面积
function foo(x, y) {
  return heronFormula(3, x, y)
}

/**
 *
 * 如果已知边长为4,其它两条边未知的求其三角形面积
 * 是不是要再实现一遍与foo(x,y)结构完全类似的函数?
 *
 **/

const R = require('ramda')

// 对 heronFormula() 函数进行Curry化
const curriedHeronFormula = R.curry(heronFormula)


// 〔新函数〕计算其中一个边长为3的三角形面积
const foo = curriedHeronFormula(3)

// 〔新函数〕计算其中两条边长分别为3,4的三角形面积
const bar = foo(4)

// 〔结果〕计算边长分别为3,4,5的三角形面积
console.log(bar(5))
// => 6

通过Curry化,能够更加精简地实现函数复用

当然,Curry本质意义在于:将函数完全变成「接受一个参数;返回一个值」的固定形式,这样对于讨论和优化更加方便。

Partial Functions

Function Composition

\left(f\cdot g \right)\left(x \right)=f\left(g\left(x \right) \right)
(fg)(x)=f(g(x))\left(f\cdot g \right)\left(x \right)=f\left(g\left(x \right) \right)

const compose = (f, g) => x => f(g(x))

pointfree


const getAdminEmails = function(users) {
  const emails = [];
  for (var i = 0; i < users.length; i++) {
    if (users[i].role === 'admin') {
      emails.push(users[i].email)
    }
  }
  return emails
}

pointfree


const getAdminEmails = users =>
    users
      .filter(u => u.role === 'admin')
      .map(u => u.email)

pointfree


const getAdminEmails = users =>
    users
      .filter(u => u.role === 'admin')
      .map(u => u.email)

Hindley-Milner

大家都知道 1加1等于2;但是一个苹果加一个板凳等于什么呢?

λ-calculus

Lazy Evaluation

js-functional

By Ivan Lyons

js-functional

js-functional

  • 2,601