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 应用程序的技术称为 “函数式编程”.
- 确定抽象, 并为其构建函数
- 利用已有的函数来构建更为复杂的抽象
- 通过将现有的函数传给其他的函数来构建更加复杂的抽象