函数式编程之 compose 实现

原打算研究下 compose 实现就好了,可是顺藤摸瓜,看到了一个更大的世界。先来看看函数式编程,再来学学 compose 的实现吧。

一、命令式编程 VS 声明式编程

常见的编程范式有:命令式编程(面向过程)、面向对象编程(Class)、声明式编程、函数式编程(声明式编程的一种)等。

为实现 (1+2) * 3 / 4 ,命令式和声明式编程如下:

命令式编程

专注于如何去做的具体过程,为解决某一问题而实现的算法。

a = 1 + 2
b = a * 3
c = b / 4

为了解决问题的一个过程。需要一行行看代码,才能理解要的是什么。

声明式编程

专注于目标,我需要什么。

divide(multiply(add(1, 2), 3), 4)

我需要 add 后的结果 -> multiply 后的结果 -> divide 后的结果。而 ‘‘how’’ 交给具体的函数去做,我们只专注于想要什么。

声明式编程语言,例如 SQL、D3
SQL:我想要一个什么样的关联数据

SELECT * from dogs
INNER JOIN owners
WHERE dogs.owner_id = owners.id

D3:我要的是一个圆,中心是x和y,初始值是0 ,半秒后变换成半径为5

var circles = svg.selectAll('circle')
.data(data)

circles.enter().append('circle')
	.attr('cx', function(d) { return d.x })
	.attr('cy', function(d) { return d.y })
	.attr('r', 0)
	.transition().duration(500)
	.attr('r', 5) 

声明式编程让我们更专注于 “what”, 而不是 “how”,代码看上去更加易读。也有助于我们站在更高层面去解决问题。在合适的场景里,我们可以多去应用声明式编程。

二、函数式编程

强调一对一的映射关系。同一种输入,只会有同一种输出结果。有以下特点:

  • 函数是 “一等公民”:操作的基础是函数,可作为出参入参,给其他函数使用
  • 声明式编程:专注于我要什么,而不是怎么做
  • 惰性执行:只在需要的时候执行,几乎没有无意义中间变量。从头到尾都在写函数
  • 无状态和数据不可变:
    无状态:不依赖外部状态(全局变量、this 指针、IO操作等),同样输入都是同样输出
    数据不可变:不改变原来数据(不修改全局、不修改入参)。想修改一个对象,应该创建新对象去修改。
  • 没有副作用:副作用是随意操纵外部变量,可能导致因为共享状态产生的 bug,却难以寻源。
  • 纯函数:不依赖外部状态(无状态)、没有副作用(数据不变)。便于测试和优化(不互相影响)、便于缓存(一种输入只有一种输出)

三、函数组合 compose

函数式编程中,最不可少的两个操作,便是柯里化和函数组合。这里主要讲讲函数组合。

概念
将多个函数组合成一个去使用。

  • 第一个函数是多元的(接受多个参数),后面的函数都是单元的(接受一个参数)
  • 执行顺序的自右向左的
  • 所有函数的执行都是同步的
let step1 = (val) => val + 1
let step2 = (val) => val + 2
const steps= [step2, step1]
const composeFuc = compose(...steps)
composeFuc(1) // 4: 1+1=2 -> 2+2=4

简单版理解:

const compose = (f, g) => x => f(g(x))

多个参数呢?

成熟版:

function compose(...fn) {
  if (!fn.length) return (v) => v;
  if (fn.length === 1) return fn[0];
  return fn.reduce(
    (pre, cur) =>
      (...args) =>
        pre(cur(...args))
  );
}

分析:

function a() {
    console.log(1);
}
function b() {
    console.log(2);
}
function c() {
    console.log(3);
}

compose(a, b, c)

// 为了得到这种结果 a(b(c()))

// 第一次执行 pre: a, cur: b
d = (...args) => a(b(...args))

// 第二次执行 pre: d, cur: c。 得到 a(b(c()))
(...args) => d(c(...args))  

搭配柯里化使用,效果更加。
柯里化实现:

function curry(fn, ...args) {
  const length = fn.length;
  let allArgs = [...args];
  const res = (...newArgs) => {
    allArgs = [...allArgs, ...newArgs];
    if (allArgs.length === length) {
      return fn(...allArgs);
    } else {
      return res;
    }
  };
  return res;
}

搭配使用:

const split = curry((x, str) => str.split(x));
const join = curry((x, arr) => arr.join(x));
const replaceSpaceWithComma = compose(join(','), split(' ')); // 先传入一个 x,后面再 str 传入。
const replaceCommaWithDash = compose(join('-'), split(','));

replaceSpaceWithComma('a b c') // a,b,c

函数组合的 Debug

const trace = curry((tip, x) => { console.log(tip, x); return x; });
const lastUppder = compose(toUpperCase, head, trace('after reverse'), reverse);

补充:
部分函数应用 vs 柯里化
部分函数应用:固定一部分参数,返回一个更小元的函数(传入更少的参数)。

// 假设一个通用的请求 API
const request = (type, url, options) => ...
// GET 请求
request('GET', 'http://....')
// POST 请求
request('POST', 'http://....')

// 但是通过部分调用后,我们可以抽出特定 type 的 request
const get = request('GET');
get('http://', {..})

参考链接
https://www.cnblogs.com/sunny-lucky/p/14119172.html
https://juejin.cn/post/6844903936378273799#heading-8
https://juejin.cn/post/6968713283884974088

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值