Compose & Pipe - 函数式编程

前言

今天主要想和你们分享一下函数组合Function Composition的概念以及一些实践。函数组合应该是函数式编程中最重要的几个概念之一了~ 因此如下的学习内容十分重要~git

工具函数

备注:若是对接下来使用的partial & curry的概念不熟悉,能够回去看个人第一篇有介绍哦~函数式编程入门实践 —— Partial/Curry。(由于接下来就会用到)github

compose

若是咱们想,对一个值执行一系列操做,并打印出来,考虑如下代码:编程

import { partial, partialRight } from 'lodash';

function add(x, y) {
  return x + y;
}

function pow(x, y) {
  return Math.pow(x, y);
}

function double(x) {
  return x * 2;
}

const add10 = partial(add, 10);
const pow3 = partialRight(pow, 3);

console.log(add10(pow3(double(2)))); // 74
复制代码

备注:partialRightpartial见名知意,至关因而彼此的镜像函数。redux

_.partialRight: This method is like _.partial except that partially applied arguments are appended to the arguments it receives.数组

无需否定,这段示例代码的确毫无心义。可是为了达成这一系列操做,我最终执行了这一长串嵌套了四层的函数调用:console.log(add10(pow3(double(2))))。(说实话,个人确以为有点难以阅读了...),若是更长了,怎么办?可能有的同窗会给出如下答案:bash

function mixed(x) {
  return add10(pow3(double(2)))
}
console.log(mixed(2)); // 74
复制代码

的确,看似好了点,可是也只是将这个冗长的调用封装了一下而已。会不会有更好的作法?app

function compose(...args) {
  return result => {
    const funcs = [...args];
    while(funcs.length > 0) {
      result = funcs.pop()(result);
    }
    return result;
  };
}

compose(console.log, add10, pow3, double)(2) // 74
复制代码

欧耶!咱们经过实现了一个简单的compose函数,而后发现调用的过程compose(console.log, add10, pow3, double)(2)居然变得如此优雅!多个函数的调用从代码阅读上,多层嵌套被拍平变成了线性!(固然实际上本质上仍是嵌套的函数调用的)。koa

固然,关于compose的更加函数式的实现以下:函数式编程

function compose(...funcs) {
  return result => [...funcs]
    .reverse()
    .reduce((result, fn) => fn(result), result);
}
复制代码

那么有同窗可能也发现了,上述compose以后的函数是只能够传递一个参数的。这无疑显得有点蠢?难道不能够优化实现支持多个参数么?函数

考虑如下代码:

function compose(...funcs) {
  return funcs
    .reverse()
    .reduce((fn1, fn2) => (...args) => fn2(fn1(...args)));
}
复制代码

细心观察,经过将参数传递进行懒执行,从而巧妙的完成了这个任务!示例以下:

function multiply(x, y) {
  return x * y;
}

compose(
  console.log, add10, pow3, multiply
)(2, 5); // 1010
复制代码

固然上述代码最终也能够这么写:

compose(
  console.log, 
  partial(add, 10),
  partialRight(pow, 3),
  partial(multiply, 5)
)(2); // 1010
复制代码

pipe

那么学习完了composepipe又是什么呢?首先在刚刚学习compose函数时,可能有同窗会以为有点小别扭,由于compose从左到右传递参数的顺序恰好和调用顺序相反的!

(固然若是说帮助理解记忆的话,compose传参的顺序就是咱们书写函数嵌套的顺序,在脑海里把console.log(add10(pow3(double(2))))这一长串里的括号去了,是否是就是参数的顺序了~)

回到话题,pipe是什么?同窗们有没有使用过命令行,好比我经常使用的一个命令,将当前工做路径拷贝到剪切板,随时ctrl + v就可使用了~

pwd | pbcopy
复制代码

固然我木有走题!注意以上的管道符 |,这个其实就是pipe,能够将数据流从左到右传递。

考虑示例代码以下:

function pipe(...args) {
  return result => {
    const funcs = [...args];
    while(funcs.length > 0) {
      result = funcs.shift()(result);
    }
    return result;
  };
}

pipe(
  partial(multiply, 5),
  partialRight(pow, 3),
  partial(add, 10),
  console.log
)(2); // 1010
复制代码

等等,从左到右?好像和compose恰好相反诶!并且这段代码好眼熟啊!将pop方法换成了shift方法而已!

那么实际上等价于:

const reverseArgs = func => (...args) => func(...args.reverse());
const pipe = reverseArgs(compose);
复制代码

哈咱们避免了重复无心义的代码!固然不管是compose仍是pipe,本质上咱们都将命令式的代码转换成了声明式的代码,对一个值的操做能够理解为值在函数之间流动

2 --> multiply --> pow --> add --> console.log

多么优雅呀~

使用递归来实现compose!

递归版本的compose本质上更接近概念,可是可能也会让人难以理解。了解一下也不错~

代码以下:

function compose(...funcs) {
  const [fn1, fn2, ...rest] = funcs.reverse();
  
  function composed(...args) {
    return fn2(fn1(...args));
  };

  if (rest.length === 0) return composed;

  return compose(
    ...rest.reverse(),
    composed
  );
}
复制代码

redux & koa-compose

redux以及koa其实都是有中间件的思想,组合中间件的compose原理和上述代码也相差不远。你们能够稍微阅读如下两个连接的源码,代码都很简短,但都验证了compose的概念只要在实际开发中运用得当的话,能够发挥强大的魔力!

小结

因此,学习函数式编程并非让本身看起来有多么聪明,也不是为了迷惑队友(哈哈),也不是单纯为了学习而学习。它的实际意义在于,给函数调用穿上语义化的衣服,让实际的应用代码最终更可读友好,利于维护~ 固然与此同时,也会训练本身写出声明式的代码。

(话说React Hooks和FP很搭配啊~ 过段时间也想在这个话题上分享一下)

谢谢你们(●´∀`●)~

相关文章
相关标签/搜索