函数式编程

2024/1/31 JavaScript

# 柯里化

​ 指的是将一个多参数的函数拆分成一系列函数,每个拆分后的函数都只接受一个参数。

function add (a, b) {
  return a + b;
}
add(1, 1) // 2
//柯里化就是将上面的函数拆分成两个函数,每个函数都只接受一个参数。
function add (a) {
  return function (b) {
    return a + b;
  }
}
// 或者采用箭头函数写法
const add = x => y => x + y;
const f = add(1);
f(1) // 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 函数合成

​ 指的是将多个函数合成一个函数。

const compose = f => g => x => f(g(x));
const f = compose (x => x * 4) (x => x + 3); //一个函数合成器,用于将两个函数合成一个函数
f(2) // 20
1
2
3

# 参数倒置

​ 指的是改变函数前两个参数的顺序。

var divide = (a, b) => a / b;
var flip = f.flip(divide);
flip(10, 5) // 0.5 参数倒置以后得到的新函数结果就是5除以10,结果得到0.5
flip(1, 10) // 10
var three = (a, b, c) => [a, b, c];
var flip = f.flip(three);
flip(1, 2, 3); // => [2, 1, 3]
//如果原函数有3个参数,则只颠倒前两个参数的位置
let f = {};
f.flip =
  fn =>
    (a, b, ...args) => fn(b, a, ...args.reverse());
1
2
3
4
5
6
7
8
9
10
11
12

# 执行边界

​ 指的是函数执行到满足条件为止。如果满足条件就返回结果,否则不断递归执行。

let condition = x => x > 100; //执行到x大于100为止
let inc = x => x + 1; //x初值为0时,会一直执行到101
let until = f.until(condition, inc);
until(0) // 101 
condition = x => x === 5; //执行到x等于5为止
until = f.until(condition, inc); //所以x最后的值是 5
until(3) // 5
//执行边界的实现如下
let f = {};
f.until = (condition, f) =>
  (...args) => {
    var r = f.apply(null, args);
    return condition(r) ? r : f.until(condition, f)(r);
  };
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 队列操作

​ 队列操作包括以下几种。

  • head: 取出队列的第一个非空成员。
  • last: 取出有限队列的最后一个非空成员。
  • tail: 取出除了“队列头”以外的其他非空成员。
  • init: 取出除了“队列尾”以外的其他非空成员。
f.head(5, 27, 3, 1) // 5
f.last(5, 27, 3, 1) // 1
f.tail(5, 27, 3, 1) // [27, 3, 1]
f.init(5, 27, 3, 1) // [5, 27, 3]
//这些方法的实现如下
let f = {};
f.head = (...xs) => xs[0];
f.last = (...xs) => xs.slice(-1);
f.tail = (...xs) => Array.prototype.slice.call(xs, 1);
f.init = (...xs) => xs.slice(0, -1);
1
2
3
4
5
6
7
8
9
10

# 合并操作

​ 合并操作分为concat和concatMap两种。前者就是将多个数组合成一个,后者则是先处理一下参数,然后再将处理结果合成一个数组。

f.concat([5], [27], [3]) // [5, 27, 3]
f.concatMap(x => 'hi ' + x, 1, [[2]], 3) // ['hi 1', 'hi 2', 'hi 3']
//这两种方法的实现代码如下
let f = {};
f.concat =
  (...xs) => xs.reduce((a, b) => a.concat(b));
f.concatMap =
  (f, ...xs) => f.concat(xs.map(f));
1
2
3
4
5
6
7
8

# 配对操作

​ 配对操作分为zip和zipWith两种方法。zip操作将两个队列的成员一一配对,合成一个新的队列。如果两个队列不等长,较长的那个队列多出来的成员会被忽略。zipWith操作的第一个参数是一个函数,然后会将后面的队列成员一一配对,输入该函数,返回值就组成一个新的队列。

let a = [0, 1, 2];
let b = [3, 4, 5];
let c = [6, 7, 8];
f.zip(a, b) // [[0, 3], [1, 4], [2, 5]]
f.zipWith((a, b) => a + b, a, b, c) // [9, 12, 15] 第一个参数是一个求和函数,它将后面三个队列的成员一一配对进行相加
//这两个方法的实现如下
let f = {};
f.zip = (...xs) => {
  let r = [];
  let nple = [];
  let length = Math.min.apply(null, xs.map(x => x.length));
  for (var i = 0; i < length; i++) {
    xs.forEach(
      x => nple.push(x[i])
    );
    r.push(nple);
    nple = [];
  }
  return r;
};
f.zipWith = (op, ...xs) =>
  f.zip.apply(null, xs).map(
    (x) => x.reduce(op)
  );
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
上次更新: 2024/4/13 08:06:14