【函数式编程】组合和管道

组合和管道

写在前面

本文的目的在于使用函数式编程解决常见问题,本文仅代表个人对函数式编程的一些粗浅认识,仅供参考,如有错漏,欢迎指出。

函数式编程理念

函数式编程的两个原则:
每个程序只做好一件事,与其在旧程序中添加新属性,不如重新构建程序。
每个程序的输出应该是另一个程序的输入

组合函数

组合函数的优势在于:利用基础函数,不用重新创建新的函数就能解决问题。
如何把两个或多个函数组合起来?我们先从简单的入手

组合两个函数

const compose = (a, b) => (c) => a(b(c));
// 对输入值转float然后四舍五入
const va = parseFloat("3.56");
const vb = Math.round(va);
const composeFn = compose(Math.round, parseFloat);
console.log(va); // 3.56
console.log(vb); // 4
console.log(composeFn("3.56"));
console.log(compose(Math.round, parseFloat)("3.56")); // 4

说明一下:parseFloat, Math.round分别是两个函数,且恰好是两个单参数函数,可以通过组合的方式调用
需要注意的两点:
1.传入的函数的执行顺序是从右往左的
2.在调用composeFn前,parseFloat, Math.round是不会被调用的

引入curry和partial

上面只能应用于单参数函数,如何应用多参数函数?有什么应对措施吗?有,我们可以引入curry函数来把多参数函数转为单参数函数

举个栗子

// 将字符串转成数字,再乘以20
const fn = (x, y) => x * y;
const va = parseInt("5.6", 10); // 5
const vb = fn(5, 20); // 100

const partialFn = _.partial(parseInt, _, 10);
const curryFn = _.curry(fn)(20);
const composeFn = compose(curryFn, partialFn);
console.log(composeFn("5.6")); // 100

这里引入了lodash的curry/partial函数
partialFn是偏函数,单参数参数
curryFn是固定一个参数的函数,单参数函数
这样一来可以扩展到组合两个任意纯函数

组合多个函数

上面的函数只能组合两个函数,下面来重写组合函数

// const compose =
//   (...fns) =>
//   (value) =>
//     _.reduce(fns.reverse(), (acc, fn) => fn(acc), value);
// 为了方便理解写成以下形式
const compose = (...fns) => {
  return (value) => {
    const func = (acc, fn) => fn(acc);
    return _.reduce(fns.reverse(), func, value);
  };
};

简单说明一下:
fns存储所有要组合的函数,fns之所以执行reverse()反转,是因为组合函数是从右往左执行
func表示接受参数并执行方法
value是组合函数的参数,表示初始值
reduce遍历每个函数,把初始值传入执行后的值作为下一个函数的传入的值

下面来运用组合函数解决问题

// 计算文字的单词数量
const split = (str) => str.split(" ");
const count = (word) => word.length;
const wordCount = compose(count, _.map, split);
console.log(wordCount("Thank you for reading my blog")); // 6

这里组合了三个函数,分别是split,_.map,count,先分割字符串为数组,遍历数组,计算每个数组项的长度,每个函数都极其简单,都组合起来就能实现复杂的方法。

管道

管道与组合实际上是一样的,区别在于数据流的不同,组合是从右往左,管道是从左往右,管道函数的定义如下:

const pipe = (...fns) => {
  return (value) => {
    const func = (acc, fn) => fn(acc);
    return _.reduce(fns, func, value);
  };
};

这里没有fns.reverse(),顺序是从左往右的,比较符合实际开发的应用

// 计算文字的单词数量
const split = (str) => str.split(" ");
const count = (word) => word.length;
const wordCount = pipe(split, _.map, count);
console.log(wordCount("Thank you for reading my blog")); // 6

组合的优势

组合函数其实是符合结合律的,如:
x * (y * z) = (x * y)* z

所以可以对其中的几个函数进行组合,得到的结果一致

const mapCount = pipe(_.map, count);
const wordCoun2 = pipe(split, mapCount);
console.log(wordCoun2("Thank you for reading my blog")); // 6

const splitMap = pipe(split, _.map);
const wordCoun3 = pipe(splitMap, count);
console.log(wordCoun3("Thank you for reading my blog")); // 6

identity函数

最后再讲一下组合函数的调试技巧,有这样的函数:

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

这个函数接受参数,把这个参数打印后原样返回,因此,把这个函数插入到组合函数的任意两个函数中间,能检查函数是否正确执行,对调试很有帮助,就像这样:

const words = "Thank you for reading my blog";
pipe(identity, split, _.map, count)(words); // Thank you for reading my blog
pipe(split, identity, _.map, count)(words); // [ 'Thank', 'you', 'for', 'reading', 'my', 'blog' ]
pipe(split, _.map, identity, count)(words); // [ 'Thank', 'you', 'for', 'reading', 'my', 'blog' ]
pipe(split, _.map, count, identity)(words); // 6

总结

组合是函数式编程的一种特性,通过定义良好的小函数组合出复杂的函数,要比直接对原输入编写复杂函数省力许多。组合与管道的不同在于数据流方向不同。identity函数的原样返回可以用来对函数组合进行调试。

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值