函数式方程简介

Intro to Functional Programming

Posted by Ericteen on January 15, 2018

Principles

函数式编程的特点

  • 可预测性 (Predictable)
  • 安全 (safe)
  • 透明 (Transparent)
  • 模块化 (modular)
  • 数据可缓存

Predictable

  • 纯函数
  • 易于测试

纯函数是这样一种函数,即相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用。

比如 slice 和 splice ,两个函数的作用相似。对于 slice 函数,相同的输入它能保证相同的输出,符合纯函数的定义。而 splice 函数,却会对原数组产生影响,即可观察到的副作用。

副作用是指在计算结果的过程中,系统状态的一种变化,或者与外部世界进行的可观察的交互。

副作用包含但不限于

  • 更改文件系统
  • 往数据库中插入记录
  • 发送一个 http 请求
  • 可变数据
  • 打印日志
  • 获取用户输入
  • DOM 查询
  • 访问系统状态

概括来讲,只要跟函数外部环境发生的交互就都是副作用。函数式编程的哲学就是假定副作用是造成不当行为的主要原因。这并非是说要禁止使用一切副作用,而是让它们在可控的范围内发生。

const add = (x, y) => x + y;
add(3, 2); // 5
add(1, 2); // 3
let name = 'Jack';
const getName = () => name; // 依赖全局变量
const setName = (newName) => { // 修改了全局变量
  name = newName;
}
const PrintUpperName = () => {
  console.log(name.toUpperCase()); // 修改了全局变量
}

Imperative & Declarative

imperative

function doubleNums(nums) {
  const doubled = [];
  const l = nums.length;

  for (let i = 0; i < l; i++) {
    doubled.push(nums[i] * 2);
  }

  return doubled;
}

declarative

function doubleNums(nums) {
  return nums.map(num => num * 2);
}

Immutable

不修改原始数据,生成新的数据

const hobbies = [
  'programming',
  'reading',
  'music',
];

const firstTwo = hobbies.splice(0, 2);
console.log(firstTwo); // ['programming', 'reading']
console.log(hobbies); // ['music']

Object.freeze & immutable.js

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

const firstTwo = bobbies.splice(0, 2); // TypeError
// 不推荐使用的方式
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  moveBy(dx, dy) {
    this.x += dx;
    this.y += dy;
  }
}

const point = new Potin(0, 0);
point.moveBy(5, 5);
point.moveBy(-2, 2);
console.log([point.x, point.y]); // [3, 7]

// 推荐
const createPoint = (x, y) => Object.freeze([x, y]);
const movePointBy = ([x, y], dx, dy) => {
  return Object.freeze([x + dx, y + dy]);
}

let point = createPoint(0, 0);
point = movePointBy(point, 5, 5);
point = movePointBy(point, -2, 2);
console.log(point); // [3, 7]

优点

  • 安全性
  • Free Undo/Redo logs - Redux
  • 清晰的数据流
  • 内存使用率低
  • 并发安全

缺点(可以通过 Immutable.js 库减轻影响)

  • 冗余代码
  • 创建更多的对象
  • 更多的垃圾回收(Garbage Collection)
  • 更多的内存使用

First Class Function JS 的最大优点

const multiply = (x, y) => x * y;

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

const evens = [1, 2, 3].map(x => x * 2);

偏函数

const partilalFromBind = (fn, ...args) => {
  return fn.call(null, ...args);
};

const partial = (fn, ...args) => {
  return (...otherArgs) => {
    fn(...args, ...otherArgs);
  };
};

柯里化

const add = x => y => x + y;

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

const curryIt = function(uncurried) {
  const slice = Array.prototype.slice;
  const params = slice.call(arguments, 1);
  return function() {
    return uncurried.apply(this, params.concat(
      slice.call(arguments, 0);
    ))
  }
}

合并以上知识点

const map = fn => array => array.map(fn);
const multiply = x => y => x * y;
const pluck = key => object => object[key];

const discount = multiply(0.88);
const tax = multipy(1.0925);

const customRequest = request({
  headers: {'X-custom': 'myKey'}
});

customRequest({
  url: '/cart/items'
})
  .then(map(pluck('price')))
  .then(map(discount))
  .then(map(tax));

// OR 执行顺序从左到右
customRequest({
  url: '/cart/items'
})
  .then(map(
    compose(
      tax,
      discount,
      pluck('price'),
  )))

compose 函数

const processWord = compose(hypenate, reverse, toUpperCase);
const words = ['hello', 'functional', 'programming'];
const newWords = words.map(processWord);
console.log(newWords); // ["OLL-EH", "LANOI-TCNUF", "GNI-MMARGORP"]

compose implementaion

const compose = (...fns) => {
  if (fns.length === 0) {
    return arg => arg;
  }

  if (fns.length === 1) {
    return fns[0];
  }

  return fns.reduce((a, b) => (...args) => a(b(...args)));
}

用迭代的方法解决 需要用 for 循环来解决的问题

// imperial way
const factorial = (n) => {
  let result = 1;

  while(n > 1) {
    result *= n;
    n--;
  }

  return result;
}

// declarative way
// 始终保持对上一个函数的引用,会使 call stack 不断积累
const factorial = (n) => {
  if (n < 2) return 1;
  return n * factorial(n - 1);
}

// 尾调用优化
// 返回值为函数,调用完之后出栈,call stack 中始终只有当前函数
const factorial = (n, accum = 1) => {
  if (n < 2) return accum;
  return factorial(n - 1, accum * n);
}

Mostly Adequated Guide

Functional Programming in ES6

Learning Functional Programming with JavaScript