JavaScript-函数式编程

JavaScript 函数式编程

JavaScript 案例

let {_} = require('underscope');

function splat(fun) {
  return (array) => fun.apply(null, array);
}

let addArrayElements = splat((x, y) => x + y);

addArrayElements([1, 2]); // 3

function unsplat(fun) {
  return function() {
    return fun.call(null, _.toArray(arguments));
    // return fun.call(null, [...arguments]);
  };
}

let joinElements = unsplat((array) => array.join(" "));

joinElements(1, 2); // '1 2'

开始函数式编程

以函数为抽象单元

/*function parseAge(age) {
  let a;
  if (!_.isString(age)) {
    throw new Error('Expecting a string');
  }

  console.log('Attempting to parse an age');

  a = parseInt(age, 10);

  if (_.isNaN(a)) {
    console.log(['Could not parse age:', age].join(' '));
    a = 0;
  }

  return a;
}*/

function fail(thing) {
  throw new Error(thing);
}

function warn(thing) {
  // console.log(['WARNING:', thing].join(' '));
  console.log(`WARNING: ${thing}`);
}

function note(thing) {
  // console.log(['NOTE:', thing].join(' '));
  console.log(`NOTE: ${thing}`);
}

function parseAge(age) {
  let a;
  if (!_.isString(age)) {
    fail('Expecting a string')
  }

  note('Attempting to parse an age')

  a = parseInt(age, 10);

  // if (_.isNaN(a)) {
  if (Number.isNaN(a)) {
    // warn(["Could not parse age:", age].join(" "));
    warn(`Could not parse age:${age}`);
    a = 0;
  }

  return a;
}

parseAge('42'); // Attempting to parse an age => 42
parseAge(42); // Error: Expecting a string
parseAge('foo'); // Could not parse age: foo

// 新的行为与旧的行为差别不大, 不同的式现在报告错误、信息和警告的想法已经被抽象化了.

封装和隐藏

​ 在 JavaScript 的对象系统中, 并没有提供直接隐藏数据的方式, 因此使用一种叫做闭包的方式来隐藏数据.

以函数为行为单位

​ 隐藏数据和行为(通常不方便于快速修改)只是一种将函数作为抽象单元的方式. 另外一种方式是提供一种简单地存储和传递基本行为的离散单元.

function isIndexed(data) {
  // return _.isArray(data) || _.isString(data);
  return Array.isArray(data) || _.isString(data);
}

function nth(a, index) {
  // if (!_.isNumber(index)) {
  if (!Number.isInteger(index)) {
    fail("Expecting a number as the index");
  }
  if (!isIndexed(a)) {
    fail("Not supported on non-indexed type");
  }
  if (index < 0 || index > a.length - 1) {
    fail("Index value is out of bounds");
  }

  return a[index];
}

let letters = ["a", "b", "c"];

nth(letters, 1); // "b"
nth("abc", 0); // "a"
nth({}, 2); // Error: Not supported on non-indexed type
nth(letters, 5); // Error: Index value is out of bounds
nth(letters, "a"); // Error: Expecting a number as the index

// Array.prototype.sort 执行字符串的比较
[2, 3, -1, -6, 0, -108, 42, 10].sort(); // [ -1, -108, -6, 0, 10, 2, 3, 42 ]
[2, 3, -1, -6, 0, -108, 42, 10].sort((x, y) =>
  x > y ? 1 : x < y ? -1 : 0
); // [ -108, -6, -1, 0, 2, 3, 10, 42 ]

function compareLessThanOrEqual(x, y) {
  return x > y ? 1 : x < y ? -1 : 0;
}

[2, 3, -1, -6, 0, -108, 42, 10].sort(compareLessThanOrEqual); // [ -108, -6, -1, 0, 2, 3, 10, 42 ]

// 总是返回一个布尔值的函数被称为谓词.
function lessOrEqual(x, y) {
  return x <= y;
}

[2, 3, -1, -6, 0, -108, 42, 10].sort(lessOrEqual); // [ 2, 3, -1, -6, 0, -108, 42, 10 ]

function comparator(pred) {
  return (x, y) => pred(x, y) ? -1 : pred(y, x) ? 1 : 0;
}

[2, 3, -1, -6, 0, -108, 42, 10].sort(comparator(lessOrEqual)); // [ -108, -6, -1, 0, 2, 3, 10, 42 ]

数据抽象

function lameCSV(str) {
  /* return _.reduce(
    str.split("\n"),
    (table, row) => {
      table.push(_.map(row.split(","), (c) => c.trim()));
      return table;
    },
    []
  ); */
  return str.split("\n").reduce((table, row) => {
    table.push(row.split(",").map((c) => c.trim()));
    return table;
  }, []);
}

let s = "name,age,hair\nMerble,35,red\nBob,64,blonde";

let peopleTable = lameCSV(s); 
/* 
[
  [ 'name', 'age', 'hair' ],
  [ 'Merble', '35', 'red' ],
  [ 'Bob', '64', 'blonde' ]
]
*/ 

// _.rest(peopleTable).sort(); // [ [ 'Bob', '64', 'blonde' ], [ 'Merble', '35', 'red' ] ]
peopleTable.slice(1).sort();

function selectNames(table) {
  // return _.rest(_.map(table, _.first))
  return table.slice(1).map((value) => value[0]);
}

function selectAges(table) {
  // return _.rest(_.map(table, _.second))
  return table.slice(1).map((value) => value[1]);
}

function selectHairColor(table) {
  // return _.rest(_.map(table, (row) => nth(row, 2)))
  return table.slice(1).map((value) => value[2]);
}

let mergeResults = _.zip;

function zip(array) {
  let { length } = [...arguments][0],
    outer = [];

  for (let j = 0; j < length; j++) {
    let inner = [];
    for (let i = 0; i < arguments.length; i++) {
      const element = arguments[i][j];
      inner.push(element);
    }
    outer.push(inner);
  }

  return outer;
}
// mergeResults = zip;

selectNames(peopleTable); // [ 'Merble', 'Bob' ]
selectAges(peopleTable); // [ '35', '64' ]
selectHairColor(peopleTable); // [ 'red', 'blonde' ]
mergeResults(selectNames(peopleTable), selectAges(peopleTable)); // [ [ 'Merble', '35' ], [ 'Bob', '64' ] ]

函数式 JavaScript 初试

function existy(x) {
  // 使用 != 可以区分 null, undefined 和其他所有对象.
  return x != null;
}

/* 
  existy(null) => false   existy(undefined) => false    existy({}.notHere) => false   
  existy((function() {})()) => false    existy(0) => true   existy(false) => true
*/

function truthy(x) {
  return x !== false && existy(x);
}

/* 
  truthy(false) => false   truthy(undefined) => false   truthy(0) => true    truthy(' ') => true
*/

function doWhen(cond, action) {
  return truthy(cond) ? action() : undefined;
}

function myResult(obj, path, fallback) {
  if (!_.isArray(path)) path = [path];
  var length = path.length;
  if (!length) {
    return _.isFunction(fallback) ? fallback.call(obj) : fallback;
  }
  for (var i = 0; i < length; i++) {
    var prop = obj == null ? void 0 : obj[path[i]];
    if (prop === void 0) {
      prop = fallback;
      i = length; // Ensure we don't continue iterating.
    }
    obj = _.isFunction(prop) ? prop.call(obj) : prop;
  }
  return obj;
}

function executeIfHasField(target, name) {
  return doWhen(existy(target[name]), () => {
    let result = myResult(target, name);
    console.log(`The result is ${result}`);
    return result;
  });
}

executeIfHasField([1, 2, 3], "reverse"); // [ 3, 2, 1 ]
executeIfHasField({ foo: 42 }, "foo"); // 42
executeIfHasField([1, 2, 3], "notHere"); // undefined

[null,undefined,1,2,false].map(existy) // [ false, false, true, true, true ]
[null,undefined,1,2,false].map(truthy) // [ false, false, true, true, false ]

总结

​ 一种用于构建 JavaScript 应用程序的技术称为 “函数式编程”.

  • 确定抽象, 并为其构建函数
  • 利用已有的函数来构建更为复杂的抽象
  • 通过将现有的函数传给其他的函数来构建更加复杂的抽象