【函数式编程】基于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);


相关文章
|
4月前
|
数据处理 开发者 数据格式
Nest.js 实战 (四):利用 Pipe 管道实现数据验证和转换
这篇文章介绍了Nest.js框架中管道的概念和使用。管道是一种强大的功能,用于在请求数据到达控制器方法之前对其进行预处理,如转换、验证、清理等。文章详细解释了数据转换、数据验证、错误处理和一致性等管道的主要用途,并通过代码示例演示了如何使用内置管道和自定义管道。最后,文章总结了管道在提升应用健壮性和安全性方面的作用,认为合理利用管道可以加速开发周期,提高软件质量。
Nest.js 实战 (四):利用 Pipe 管道实现数据验证和转换
|
2月前
|
缓存 JavaScript 前端开发
了解js基础知识中的作用域和闭包以及闭包的一些应用场景,浅析函数柯里化
该文章详细讲解了JavaScript中的作用域、闭包概念及其应用场景,并简要分析了函数柯里化的使用。
了解js基础知识中的作用域和闭包以及闭包的一些应用场景,浅析函数柯里化
|
2月前
|
JavaScript 前端开发
JS 偏函数、函数柯里化~
该文章介绍了JavaScript中偏函数和函数柯里化的概念、实现方法和使用场景,通过代码示例展示了如何创建预设参数的函数以及如何将多参数函数转换成单参数函数的链式调用。
17 0
JS 偏函数、函数柯里化~
|
3月前
|
存储 JavaScript 前端开发
JavaScript——函数式编程Functor(函子)
JavaScript——函数式编程Functor(函子)
22 0
|
3月前
|
JavaScript 前端开发
JS : 柯里化 分布传参给函数
JS : 柯里化 分布传参给函数
|
5月前
|
JavaScript 前端开发 测试技术
JavaScript进阶-高阶函数与柯里化
【6月更文挑战第18天】在JavaScript中,高阶函数和柯里化是函数式编程的核心。高阶函数接收或返回函数,常用于数组操作和事件处理。柯里化将多参数函数转化为单参数的逐次求值过程,用于参数预绑定和函数组合。两者能简化逻辑、提高复用性,但也需注意易错点,如混淆参数、过度柯里化,应适度使用并配合测试保证正确性。通过实践和使用工具,如lodash的`_.curry`,能更好地利用这些技术。
47 5
|
5月前
|
前端开发 JavaScript 开发者
函数式编程在JavaScript中的应用
【6月更文挑战第10天】本文探讨了函数式编程在JavaScript中的应用,介绍了函数式编程的基本概念,如纯函数和不可变数据。文中通过示例展示了高阶函数、不可变数据的使用,以及如何编写纯函数。此外,还讨论了函数组合和柯里化技术,它们能提升代码的灵活性和可重用性。掌握这些函数式编程技术能帮助开发者编写更简洁、可预测的JavaScript代码。
|
6月前
|
JavaScript 前端开发
JavaScript 的数组方法 map()、filter() 和 reduce() 提供了函数式编程处理元素的方式
【5月更文挑战第11天】JavaScript 的数组方法 map()、filter() 和 reduce() 提供了函数式编程处理元素的方式。map() 用于创建新数组,其中元素是原数组元素经过指定函数转换后的结果;filter() 则筛选出通过特定条件的元素生成新数组;reduce() 将数组元素累计为单一值。这三个方法使代码更简洁易读,例如:map() 可用于数组元素乘以 2,filter() 用于选取偶数,reduce() 计算数组元素之和。
46 2
|
6月前
|
JavaScript 前端开发 测试技术
JavaScript中的函数式编程:纯函数与高阶函数的概念解析
【4月更文挑战第22天】了解JavaScript中的函数式编程,关键在于纯函数和高阶函数。纯函数有确定输出和无副作用,利于预测、测试和维护。例如,`add(a, b)`函数即为纯函数。高阶函数接受或返回函数,用于抽象、复用和组合,如`map`、`filter`。函数式编程能提升代码可读性、可维护性和测试性,帮助构建高效应用。
|
6月前
|
前端开发 JavaScript
前端 JS 经典:函数管道
前端 JS 经典:函数管道
28 0
下一篇
无影云桌面