前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >【基于 JS 函数式编程-3】柯里化 | 偏函数 | 组合与管道

【基于 JS 函数式编程-3】柯里化 | 偏函数 | 组合与管道

作者头像
前端修罗场
发布2023-10-07 18:38:34
发布2023-10-07 18:38:34
33000
代码可运行
举报
文章被收录于专栏:Web 技术Web 技术
运行总次数:0
代码可运行

如题,理解柯里化和偏应用,能帮助我们在函数式组合中进行应用。

概念

一元函数

定义: 只接受一个参数的函数,称为一元函数。如:

代码语言:javascript
代码运行次数:0
复制
const fn = (x)=>x;

二元函数

定义:接受两个参数的函数,称为二元函数。如:

代码语言:javascript
代码运行次数:0
复制
const add =(x,y)=>x+y;

变参函数

定义:接受可变数量参数的函数,称为变参函数。 在es5中我们可以通过arguments来捕获调用变参函数的额外参数。 在es6中,我们可以使用扩展运算符:"..."实现变参函数。如:

代码语言:javascript
代码运行次数:0
复制
const varfn = (a,...varparms)=> {
	console.log(a);
	console.log(varparms);
}
varfn(1,2,3); // 1 , [2,3],我们把[2,3]称为额外参数

柯里化

定义:柯里化(Curry,以数学家Haskell Curry命名),常被翻译为“局部套用”,是把一个多参函数转换为一系列单参函数并进行调用的过程。

代码语言:javascript
代码运行次数:0
复制
柯里化允许我们把函数与传递给这个函数的参数相结合,产生出一个新的函数。

如:下列代码中,add1是把1传递给add函数的curry方法后创建的一个新函数。

代码语言:javascript
代码运行次数:0
复制
let add1 = add.curry(1);
console.log(add1(3));

再如:

代码语言:javascript
代码运行次数:0
复制
const add =(x,y)=>x+y; //二元函数
进行柯里化:
const addCurry = x => y=>x+y;
addCurry(2)(3); //5

但是,Javascript自己并没有Curry方法。我们可以通过给Function.prototype扩展此功能:

代码语言:javascript
代码运行次数:0
复制
Function.method('curry',function(){
	let slice = Array.prototype.slice,
		args = slice.apply(arguments),
		//arguments并非真正的数组,没有concat方法,要避开这个问题,
		//我们必须在两个arguments数组上应用数组的slice方法。
		//这样能产生出拥有concat方法的常规数组
		that = this;
		return function() {
			return that.apply(null,args.concat(slice.apply(arguments)));
		}
})

curry函数定义

代码语言:javascript
代码运行次数:0
复制
const curry = (binaryFn)=> {
	return function(firstArg) {
		return function(secondArg){
			return binaryFn(firstArg,secondArg);
		};
	};
};
let autoCurriedAdd = curry(add) //通过curry函数把add函数转换为一个柯里化函数
autoCurriedAdd(2)(3); //5

但是,有人会问:柯里化有什么用处呢

因为有时候我们可能想把多个函数及带有多个参数的函数柯里化,所以,下面我们重构一下curry函数

代码语言:javascript
代码运行次数:0
复制
let curry = (fn)=> {
	if(typeof fn!=='function') {
		throw Error('No function provided!');
	}
	return function curriedFn(...args) {
		if(args.length<fn.length){ //检查通过...args传入的参数长度是否小于函数参数列表的长度。如果是,进入if,否则调用整个函数。
			return function() {
				return curriedFn.apply(null,args.concat([].slice.call(arguments)));//使用concat函数连接一次传入一个的参数,并递归调用curriedFn。
				//除此之外,由于args是类数组,并没有concat方法,
				//所以,需要应用数组的slice方法。
			};
		}
		return fn.apply(null,args);//直接调用整个函数
	};
};
const multiply = (x,y,z) =>x*y*z;
curry(multiply)(3)(2)(1);//6

偏应用

偏函数(partial)

代码语言:javascript
代码运行次数:0
复制
const partial = function(fn,...partialArgs) {
	let args = partialArgs;//捕获传入函数的参数 args= [undefined,10]
	return function(...fullArguments) {//闭包函数,接受一个fullArguments的参数
	//fullArguments指向 console.log('1');
		let arg = 0;
		for(let i = 0;i < args.length&& arg<fullArguments.length;i++) {
			if(args[i] === undefined) {
				args[i] = fullArguments[arg++];
			}
		}
		return fn.apply(null,args);
	};
};
let delayTenMs = partial(setTimeout,undefined,10);
delayTenMs(()=>console.log('1'));

我们可以将partial函数应用于任何含有多个参数的函数。如:

代码语言:javascript
代码运行次数:0
复制
let obj = {foo:"xxx",bar:"yyy"};
JSON.stringify(obj,null,2);
转换为应用偏函数:
let prettyPrintJson = partial(JSON.stringify,undefined,2);
prettyPrintJson({foo:"xxx",bar:"yyy"});//"{"foo":"xxx","bar":"yyy"}"

上面我们说了柯里化和偏函数,但是需要注意的是:

柯里化和偏函数并不是同时需要。这主要取决于API是如何定义的。如果API如,map、filter一样定义,我们可以使用curry函数解决问题。但是,如果不是为curry函数设计的函数,如setTimeout,有时填充函数的前两个参数和最后一个参数会使中间的参数处于一种未知状态(undefined)!我们选择partial更合适!

组合与管道

概念

在Unix中有这么一套思想: 1、每个程序只做好一件事情。为了完成一项新的任务,重新构建要好于在复杂的旧程序中添加新”属性“。在函数式编程中,”接受一个参数并返回数据“正是遵循了该条思路。 2、每个程序的输出应该是另一个尚未可知的程序的输入。

管道

管道允许我们通过组合一些函数去创建一个能够解决问题的新函数。

如图:

管道在两个函数之间扮演了桥梁的角色。

函数式组合

如下示例代码:

代码语言:javascript
代码运行次数:0
复制
map(filter(arg,(item)=>item.rating[0]>4.5),(item)=>{
	return {}
})

我们看到,上面代码中filter输出的数据被作为输入参数传递给map函数。 这种创建一个函数,通过把一个函数的输出作为输入发送给另一个函数的方式把两个函数组合起来,我们称为函数式组合。组合的思想,就是把小函数组合成一个大函数。 示例:

代码语言:javascript
代码运行次数:0
复制
//compose函数
const compose =(a,b)=> {
	(c)=>a(b(c)) //b的输出作为a的输入
}
let number = compose(Math.round,parseFloat);
number("3.56");//4

compose函数会首先执行b,并将b的返回值作为参数传递给a。该函数调用的方向是从右至左的,即先执行b,再执行a。

管道/序列

从左至右处理数据流的过程称为管道(pipeline)或序列。

代码语言:javascript
代码运行次数:0
复制
//pipe函数,compose函数的复制品,修改了数据流
const pipe = (...fns) => 
	(value) => 
		reduce(fns,(acc,fn)=>fn(acc),value);
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概念
    • 一元函数
    • 二元函数
    • 变参函数
  • 柯里化
  • 偏应用
    • 偏函数(partial)
  • 组合与管道
    • 概念
      • 管道
    • 函数式组合
    • 管道/序列
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档