一等函数与Applicative编程

一等函数与Applicative编程

函数是一等公民

// 函数与数字一样可以存储为变量
let foo = function(params) {
    doSomething();
}

// 函数与数字一样可以存储为数组的一个元素
let arr = [ 0, 1, foo ];

// 函数与数字一样可以作为对象的成员变量
let obj = {
    name: "test",
    age: 19,
    foo
};

// 函数与数字一样可以在使用时直接创建出来
let res = 1 + (function() { return 2; })(); // 3

// 函数与数字一样可以被传递给另一个函数
function weirdAdd(n, f) { return n + f(); }
weirdAdd(123, () => 110); // 233

// 函数与数字一样可以被另一个函数返回
return 0;
return function() { return -1; };

/*
  高阶函数
    以一个函数作为参数
    返回一个函数作为结果
*/

多种 JavaScript 编程方式

  • 命令式编程

    通过详细描述行为的编程方式.

  • 基于原型的面向对象编程

    基于原型对象及其实例的编程方式

  • 元编程

    对 JavaScript 执行模型数据进行编写和操作的编程方式

let { _ } = require("underscore");

// 命令式编程
let lyrics = [];

for (let bottles = 99; bottles > 0; bottles--) {
  lyrics.push(`${bottles} bottles of beer on the wall`);
  lyrics.push(`${bottles} bottles of beer`);
  lyrics.push("Take one down, pass it around");

  if (bottles > 1) {
    lyrics.push(`${bottles - 1} bottles of beer on the wall.`);
  } else {
    lyrics.push(`No more bottles of beer on the wall!`);
  }
}

// 函数式编程
function lyricSegment(n) {
  return _.chain([])
    .push(`${n} bottles of beer on the wall`)
    .push(`${n} bottles of beer`)
    .push(`Take one down, pass it around`)
    .tap((lyrics) => {
      if (n > 1) {
        lyrics.push(`${n - 1} bottles of beer on the wall.`);
      } else {
        lyrics.push(`No more bottles of beer on the wall!`);
      }
    })
    .value();
}

const range = (start, stop, step) =>
  Array.from({ length: (stop - start) / step + 1 }, (_, i) => start + i * step);

function song(start, end, lyricGen) {
  /* return _.reduce(
    _.range(start, end, -1),
    (acc, n) => acc.concat(lyricGen(n)),
    []
  ); */

  return range(start, end, -1).reduce(
    (pre, cur) => pre.concat(lyricGen(cur)),
    []
  );
}

song(9, 0, lyricSegment);

// 基于原型的面向对象编程
let a = {
  name: "a",
  fun() {
    return this;
  },
};

a.fun(); // a{}

let bFunc = function () {
  return this;
};
let b = {
  name: "b",
  bFunc,
};

b.bFunc(); // b{}

// 元编程
/* function Point2D(x, y) {
  this._x = x;
  this._ = y;
} */

class Point2D {
  constructor(x, y) {
    this._x = x;
    this._ = y;
  }
}

new Point2D(0, 1); // Point2D { _x: 0, _: 1 }

/* function Point3D(x, y, z) {
  Point2D.call(this, x, y);
  this._y = y;
} */

class Point3D extends Point2D {
  constructor(x, y, z) {
    super(x, y);
    this._z = z;
  }
}

new Point3D(1, 0, 1); // Point3D { _x: 1, _: 0, _z: 1 }

Applicative 编程

​ Applicative 编程定义为函数A作为参数提供给函数B.

let nums = [1, 2, 3, 4, 5];

function doubleAll(array) {
  // return _.map(array, (n) => n * 2);
  return array.map((n) => n * 2);
}

doubleAll(nums); // [ 2, 4, 6, 8, 10 ]

function average(array) {
  /* let sum = _.reduce(array, (a, b) => a + b);
  return sum / _.size(array); */
  let sum = array.reduce((pre, cur) => pre + cur);
  return sum / array.length;
}

average(nums); // 3

function onlyEven(array) {
  // return _.filter(array, (n) => !Boolean(n % 2));
  return array.filter((n) => !Boolean(n % 2));
}

onlyEven(nums); // [ 2, 4 ]

集合中心编程

_.map({ a: 1, b: 2 }, _.identity); // [ 1, 2 ]
console.log(res);

_.map({ a: 1, b: 2 }, (v, k) => [k, v]); // [ [ 'a', 1 ], [ 'b', 2 ] ]
console.log(res);

// _.keys => Object.keys
_.map({ a: 1, b: 2 }, (v, k, coll) => [k, v, _.keys(coll)]); // [ [ 'a', 1, [ 'a', 'b' ] ], [ 'b', 2, [ 'a', 'b' ] ] ]

Applicative 编程的其他实例

// 1.reduceRight
let nums = [100, 2, 25];
function divi(x, y) {
  return x / y;
}

_.reduce(nums, divi);
nums.reduce(divi); // 2

_.reduceRight(nums, divi); // 0.125

function allOf() {
  return _.reduceRight(arguments, (truth, f) => truth && f(), true);
}

function anyOf() {
  return _.reduceRight(arguments, (truth, f) => truth || f(), false);
}

function T() {
  return true;
}

function F() {
  return false;
}

allOf(); // true
allOf(T, T); // true
allOf(T, F, T); // false

anyOf(); // false
anyOf(F, F); // false
anyOf(F, T, F); // true

// 2.find
_.find(["a", "b", 3, 4, "d"], _.isNumber); // 3

// 3.reject
_.reject(["a", "b", 3, "d"], _.isNumber); // [ 'a', 'b', 'd' ]

function complement(pred) {
  return function () {
    return !pred.apply(null, _.toArray(arguments));
  };
}

_.filter(["a", "b", 3, "d"], complement(_.isNumber)); // [ 'a', 'b', 'd' ]

// 4.all
_.all([1, 2, 3, 4], _.isNumber); // true

// 4.any
_.any([1, 2, "c", 4], _.isString); // true

// 6.sortBy, groupBy, countBy
let people = [
  { name: "Rick", age: 30 },
  { name: "Jake", age: 24 },
];

_.sortBy(people, (p) => p.age); // [ { name: 'Jake', age: 24 }, { name: 'Rick', age: 30 } ]

let albums = [
  { title: "Sabbath Bloody Sabbath", genre: "Metal" },
  { title: "Scientist", genre: "Dub" },
  { title: "Undertow", genre: "Metal" },
];

_.groupBy(albums, (a) => a.genre);
/* 
{
  Metal: [
    { title: 'Sabbath Bloody Sabbath', genre: 'Metal' },
    { title: 'Undertow', genre: 'Metal' }
  ],
  Dub: [ { title: 'Scientist', genre: 'Dub' } ]
}
*/

_.countBy(albums, (a) => a.genre); // { Metal: 2, Dub: 1 }

定义几个Applicative函数

function cat() {
  let head = _.first(arguments);
  if (existy(head)) {
    return head.concat.apply(head, _.rest(arguments));
  } else {
    return [];
  }
}

function construct(head, tail) {
  return cat([head], _.toArray(tail));
}

// Applicative函数
function mapcat(fun, coll) {
  return cat.apply(null, _.map(coll, fun));
}

mapcat((e) => construct(e, [","]), [1, 2, 3]); // [ 1, ',', 2, ',', 3, ',' ]

function butlast(coll) {
  return _.toArray(coll).slice(0, -1);
}

function interpose(inter, coll) {
  return butlast(mapcat((e) => construct(e, [inter]), coll));
}

interpose(",", [1, 2, 3]); // [ 1, ',', 2, ',', 3 ]

数据思考

let zombie = {
  name: "Bub",
  film: "Day of the Dead",
};

_.keys(zombie); // [ 'name', 'film' ]
_.values(zombie); // [ 'Bub', 'Day of the Dead' ]

let books = [
  {
    title: "Chthon",
    author: "Anthony",
  },
  {
    title: "Grendel",
    author: "Gardner",
  },
  {
    title: "After Dark",
  },
];

_.pluck(books, "author"); // [ 'Anthony', 'Gardner', undefined ]

_.pairs(zombie); // [ [ 'name', 'Bub' ], [ 'film', 'Day of the Dead' ] ]

_.object(_.map(_.pairs(zombie), (pair) => [pair[0].toUpperCase(), pair[1]])); // { NAME: 'Bub', FILM: 'Day of the Dead' }

_.invert(zombie); // { Bub: 'name', 'Day of the Dead': 'film' }

_.keys(_.invert({ a: 123, b: 110 })); // [ '110', '123' ]

_.pluck(
  _.map(books, (obj) => _.defaults(obj, { author: "Unknow" }), "author")
); // [ "Anthony", "Gardner", "Unknow" ]

let person = {
  name: "Romy",
  token: "j3983ij",
  password: "tigress",
};

let info = _.omit(person, "token", "password"); // { name: 'Romy' }
let creds = _.pick(person, "token", "password"); // { token: 'j3983ij', password: 'tigress' }

let library = [
  { title: "SICP", isbn: "0262010771", ed: 1 },
  { title: "SICP", isbn: "0262510871", ed: 2 },
  { title: "Joy of Clojure", isbn: "1935182641", ed: 1 },
];

_.findWhere(library, { title: "SICP", ed: 2 }); // { title: 'SICP', isbn: '0262510871', ed: 2 }

_.where(library, { title: "SICP" });
/* 
[
  { title: 'SICP', isbn: '0262010771', ed: 1 },
  { title: 'SICP', isbn: '0262510871', ed: 2 }
]
*/

_.pluck(library, "title"); // [ 'SICP', 'SICP', 'Joy of Clojure' ]

function project(table, keys) {
  return _.map(table, (obj) => _.pick.apply(null, construct(obj, keys)));
}

let editionResults = project(library, ["title", "isbn"]);
/* 
[
  { title: 'SICP', isbn: '0262010771' },
  { title: 'SICP', isbn: '0262510871' },
  { title: 'Joy of Clojure', isbn: '1935182641' }
]
*/

let isbnResult = project(editionResults, ["isbn"]);
/* 
[
  { isbn: '0262010771' },
  { isbn: '0262510871' },
  { isbn: '1935182641' }
]
*/

_.pluck(isbnResult, "isbn"); // [ '0262010771', '0262510871', '1935182641' ]

function rename(obj, newNames) {
  return _.reduce(
    newNames,
    (o, nu, old) => {
      if (_.has(obj, old)) {
        o[nu] = obj[old];
        return o;
      } else {
        return o;
      }
    },
    _.omit.apply(null, construct(obj, _.keys(newNames)))
  );
}

rename({ a: 1, b: 2 }, { a: "AAA" }); // { b: 2, AAA: 1 }

function as(table, newNames) {
  return _.map(table, (obj) => rename(obj, newNames));
}

as(library, { ed: "edition" });
/* 
[
  { title: 'SICP', isbn: '0262010771', edition: 1 },
  { title: 'SICP', isbn: '0262510871', edition: 2 },
  { title: 'Joy of Clojure', isbn: '1935182641', edition: 1 }
]
*/

project(as(library, { ed: "edition" }), ["edition"]); // [ { edition: 1 }, { edition: 2 }, { edition: 1 } ]

function restrict(table, pred) {
  return _.reduce(
    table,
    (newTable, obj) => {
      if (truthy(pred(obj))) {
        return newTable;
      } else {
        return _.without(newTable, obj);
      }
    },
    table
  );
}

restrict(library, (book) => book.ed > 1); // [ { title: 'SICP', isbn: '0262510871', ed: 2 } ]

restrict(
  project(as(library, { ed: "edition" }), ["title", "isbn", "edition"]),
  (book) => book.edition > 1
); // [ { title: 'SICP', isbn: '0262510871', edition: 2 } ]

// SQL 
/* select title, isbn, edition 
from (select ed as edition from library) eds 
where edition > 1; */

总结—-一等函数

  • 它们可以存储在变量中.
  • 它们可以 被存储在数组中的插槽中.
  • 它们可以存储在对象的字段中.
  • 它们可以根据需要来创建.
  • 它们可以被传递到其他函数中.
  • 它们可以被其他函数返回.