JavaScript 函数式编程03-组合与管道

305 阅读3分钟

函数式编程就是将每个不同的计算部分分散封装到不同的函数内,等到使用的时候再一一拿出来并按照一定的顺序进行执行,就类似于管道一样,共同组合成一个完整的函数。

组合:每个程序的输出可以是另一个程序的输入,每个基础命令结合起来即可完成复杂的任务。

例如:处理数组 let arr = [2, 3, 7, 8]; 将其中大于5的值取出来并减一,可以使用:

map(filter(arr, i => i > 5), i => i-1)

如上,filter的输出作为map函数的输入,两个函数组合起来可以轻易解决某些问题。数据就像是在管道内流动一样,从出口到另一个入口。那我们便可以抽象出代码逻辑,让我们不再关心中间状态,只需要输入初始数据即可,实现的类似这样的函数被称为 compose 函数

const compose = (a, b) => c => a(b(c));

利用compose可以实现有趣的事情:

const str2num = s => parseInt(s);
const int = n => Math.floor(n);

const str2int = compose(int, str2num);

str2int("3.1415926"); // 3

如果使用 compose 函数来解决多参数函数问题比较麻烦,需要借助偏函数来实现。(你可以将其中的偏函数变体单独储存为一个函数,方便语义化)具体实现如下:

compose(partial(map)(i => i-1), partial(filter)(i => i > 5))(arr);

偏函数、map、filter等函数之前的文章已给出代码实现!

可能你会想,实现它有什么用?直接将所有代码都封装进一个函数不就行?

是的,在某些人看来,命令式的做法更方便、更符合思考逻辑。但是当你阅读别人的代码,或者阅读自己好长时间前写的代码时,命令式的代码需要一定的时间去理清其中的逻辑,而函数是的编程则很好看懂其中逻辑(或许在您看来命令式更易懂或者在于函数命名是否清除)。而且,函数式的代码易于写测试代码,您可以将每一部分都单独进行测试,而当您重新写一个函数的时候,复用这些小函数可以安全使用,因为它们已被测试安全!

组合与管道

上面实现的 compose 函数只能实现两个函数的组合,为了实现多个参数的组合,需要重写函数。实现逻辑是将给定的函数参数数组整合为一个并返回,这可以利用 reduce 来实现:

const compose = (...func) => value => reduce(func.reverse(), (acc, fn) => fn(acc), value);

使用:

compose(partial(map)(i => i-1), partial(filter)(i => i > 5), partial(map)(i => i + 3))(arr);
// => [ 5, 9, 10 ]

实现到这,你可能会发掘 compose 函数中数据流是从右到左流,而不是从左到右。而似乎从左到右执行似乎才符合逻辑,这样数据流从左到右的函数则被称为 pipe 管道函数。其实现方式类似于compose:

const pipe = (...func) => value => reduce(func, (acc, fn) => fn(acc), value);

使用管道解决问题:

pipe(partial(map)(i => i + 3), partial(filter)(i => i > 5), partial(map)(i => i-1))(arr);
// => [ 5, 9, 10 ]

组合 compose 与管道 pipe 是相似的,他们只是数据流的方向相反,作用是一致的,都是整合小的函数,实现完整的功能。

使用组合与管道,是有利于调试的,数据在不同的函数中流动,你完全可以在任何地方加一个拦截器调试数据:

const log = data => {
  console.log(data);
  return data;
}

拦截数据:

pipe(partial(map)(i => i + 3), 
     log, // 输出:[ 5, 6, 10, 11 ]
     partial(filter)(i => i > 5), partial(map)(i => i-1))(arr);

这对于调试代码、定位bug等有很大的帮助。