【函数式编程】基于JS进行函数式编程(三)柯里化 | 偏函数 | 组合与管道

简介: 【函数式编程】基于JS进行函数式编程(三)柯里化 | 偏函数 | 组合与管道

相关文章

【函数式编程】基于JS 进行函数式编程(一)引入 | 什么是函数式编程 | 函数式编程的优点

【函数式编程】基于JS进行函数式编程(二)高阶函数 | 函数代替数据传递 | 函数是一等公民 | 闭包 | 使用高阶函数实现抽象 | 数组的高阶函数

【函数式编程】基于JS进行函数式编程(三)柯里化 | 偏函数 | 组合与管道

【函数式编程】基于JS进行函数式编程(四)函子 | MayBe函子 | Monad函子


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

概念

一元函数

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

const fn = (x)=>x;

二元函数

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

const add =(x,y)=>x+y;

变参函数

定义:接受可变数量参数的函数,称为变参函数。

在es5中我们可以通过arguments来捕获调用变参函数的额外参数。

在es6中,我们可以使用扩展运算符:"..."实现变参函数。如:

const varfn = (a,...varparms)=> {
  console.log(a);
  console.log(varparms);
}
varfn(1,2,3); // 1 , [2,3],我们把[2,3]称为额外参数

柯里化

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

柯里化允许我们把函数与传递给这个函数的参数相结合,产生出一个新的函数。

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

let add1 = add.curry(1);
console.log(add1(3));

再如:

const add =(x,y)=>x+y; //二元函数
进行柯里化:
const addCurry = x => y=>x+y;
addCurry(2)(3); //5

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

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函数定义

const curry = (binaryFn)=> {
  return function(firstArg) {
    return function(secondArg){
      return binaryFn(firstArg,secondArg);
    };
  };
};
let autoCurriedAdd = curry(add) //通过curry函数把add函数转换为一个柯里化函数
autoCurriedAdd(2)(3); //5

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

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

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)

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函数应用于任何含有多个参数的函数。如:

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、每个程序的输出应该是另一个尚未可知的程序的输入。

管道

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

如图:

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

函数式组合

如下示例代码:

map(filter(arg,(item)=>item.rating[0]>4.5),(item)=>{
  return {}
})

我们看到,上面代码中filter输出的数据被作为输入参数传递给map函数。

这种创建一个函数,通过把一个函数的输出作为输入发送给另一个函数的方式把两个函数组合起来,我们称为函数式组合。组合的思想,就是把小函数组合成一个大函数。

示例:

//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)或序列。

//pipe函数,compose函数的复制品,修改了数据流
const pipe = (...fns) => 
  (value) => 
    reduce(fns,(acc,fn)=>fn(acc),value);


相关文章
|
5月前
|
前端开发 JavaScript 数据处理
深入学习JavaScript ES8函数式编程:特性与实践指南
深入学习JavaScript ES8函数式编程:特性与实践指南
46 0
|
11天前
|
JavaScript 前端开发 测试技术
JavaScript中的函数式编程:纯函数与高阶函数的概念解析
【4月更文挑战第22天】了解JavaScript中的函数式编程,关键在于纯函数和高阶函数。纯函数有确定输出和无副作用,利于预测、测试和维护。例如,`add(a, b)`函数即为纯函数。高阶函数接受或返回函数,用于抽象、复用和组合,如`map`、`filter`。函数式编程能提升代码可读性、可维护性和测试性,帮助构建高效应用。
|
4月前
|
前端开发 JavaScript 数据处理
深入学习JavaScript ES8函数式编程:特性与实践指南
深入学习JavaScript ES8函数式编程:特性与实践指南
70 0
|
4月前
|
存储 JavaScript 前端开发
JavaScript函数柯里化
JavaScript函数柯里化
|
5月前
|
JavaScript 前端开发 索引
JavaScript函数式编程【进阶】
JavaScript函数式编程【进阶】
25 1
|
5月前
|
存储 JavaScript 前端开发
JavaScript函数式编程[入门]
JavaScript函数式编程[入门]
28 1
|
7月前
|
设计模式 JSON 前端开发
前端面试必看(手写Promise+js设计模式+继承+函数柯里化等)JavaScript面试全通关(1/3)
前端面试必看(手写Promise+js设计模式+继承+函数柯里化等)JavaScript面试全通关(1/3)
42 0
|
7月前
|
缓存 JavaScript 前端开发
带你读《现代Javascript高级教程》十四、JavaScript函数式编程(1)
带你读《现代Javascript高级教程》十四、JavaScript函数式编程(1)
|
7月前
|
JavaScript 前端开发 测试技术
带你读《现代Javascript高级教程》十四、JavaScript函数式编程(2)
带你读《现代Javascript高级教程》十四、JavaScript函数式编程(2)
|
10月前
|
JavaScript 前端开发
函数式编程在 JavaScript 中的实践与优势
函数式编程是一种编程范式,它将计算过程看作是数学函数的计算,并强调使用纯函数来处理数据,避免副作用。在 JavaScript 中,函数是一等公民,这意味着函数可以像变量一样被传递和操作。函数式编程在 JavaScript 中被广泛应用,它提供了许多优势。