大家是不是看我上篇博文有点蒙。用的的curry和compose是什么鬼,怎么那么神奇。上篇博文也是主要用到了这两个函数。
那今天我们来聊一下curry和compose,这两个东西是函数式编程很重要的东西,基本每个稍微复杂点的例子都要涉及这两个函数。
什么是curry呢?
---函数柯里化。就是这个东西了。举一个简单的例子。

  1. var _console=curry(function(x){
  2. console.log(x);
  3. })
  4. _console("hello"); //hello

其实就这个作用,先定义函数,后传参数。_console保存了这个函数,每次调用这个函数的时候传给它参数。是不是很简单呢?
看一下curry的实现。

  1. function curry(fn){
  2. return function(f){
  3. return fn(f);
  4. }
  5. }

只是返回一个函数那么简单,这里还涉及fn的控制权反转,类似redux的dispatch,传入dispatch使用它。
那么当这个curry函数有2个参数的时候,curry就得变为:

  1. function curry(fn){
  2. return function(f){
  3. return function(g){
  4. return fn(f,g);
  5. }
  6. }
  7. }

那么有3个参数呢?

  1. function curry(fn){
  2. return function(f){
  3. return function(g){
  4. return function(h){
  5. return fn(f,g,h);
  6. }
  7. }
  8. }
  9. }

那么当我想一次传2个参数,剩下一个传1个参数怎么办?

  1. function curry(fn){
  2. return function(f){
  3. return function(g,h){
  4. return fn(f,g,h);
  5. }
  6. }
  7. }

那么我改主意了,我不确定每次传多少个参数了,我不确定一共传多少个参数了。怎么办?

自动柯里化函数,你会发现_.curry完美的解决了这个问题,甚至可以在你传一个空参数的时候忽略它。
今天我们不研究_.curry的实现,有大神可以分享一下~
我们今天写一个自动柯里化函数。
怎么写?什么思路呢?它的难点在哪?
首先我们需要知道自动柯里化函数的执行过程和普通柯里化函数的执行过程是一样的,即有多少参数返回多少的function,取到函数的参数的长我们知道,

但是函数又不是数组,可以用一层for循环return出来,只能改变思路,函数的循环就是递归了。
它的难点在哪?

我们需要在每次执行一次()的时候获取他的参数,在递归里我们可以声明一个外部变量而储存每次执行的参数。

但是这个是不行的,因为每次递归都需要返回一个function,这意味着你无法返回上一层了。那我们只能把执行的参数当函数的参数传进去了。
直接把自动柯里化给你们(复制可测试)

  1. function curry (fn, length, args) {
  2.     length = length || fn.length; //保存函数的参数数量
  3.     args = args || [];
  4.     return function(){
  5.         var _args = args.slice(0),
  6.             arg, i;
  7.         for(i = 0; i < arguments.length; i++) {//遍历_args,存储每次执行的参数
  8.             arg = arguments[i];
  9.             _args.push(arg);
  10.         }
  11.         if(_args.length < length) {
  12.             return curry.call(this, fn, length, _args); //递归调用自己
  13.         } else {
  14.             return fn.apply(this, _args);
  15.         }
  16.  }
  17. }

可以看到解决return函数个数的是

  1. if(_args.length < length) {
  2. return curry.call(this, fn, length, _args);
  3. }else {
  4. return fn.apply(this, _args);
  5. }

_arg储存了从开始到现在的所有的参数,如果参数”够了“(一个小知识,函数的length==函数参数的个数),就调用fn,把所有参数都传给他,

如果不够,就继续递归,递归的时候呢,把当前的参数传到下一层函数里。
难点就在于每次递归回来都要遍历一下当前的参数,把它push到_args里面。
这样做也避免了空参数的情况,当传入一个空参数,比如这样调用()。_args不变。

大家有空可以研究一下_.curry的实现过程是不是类似呢?-0-

下一个主角,compose,这个函数在fp里面叫组合函数,它能把里面的参数都组合起来,一起调用。类似流式

  1. function compose(fn1,fn2){
  2. return function(arg){
  3. fn2(fn1(arg));
  4. }
  5. }
  6. var gen= compose(function(b){console.log(b);return b+"be deal with b";} , function(a){console.log(a);return a+"be deal with a";} )
  7. gen("holle"); //hello hello deal with b;

gen就像一个水管,gen("holle");就是往水管里注入了水。
它会从compose左边的函数开始执行,hello是数据流,流过2个函数。第一个函数的返回值会传入到第二个参数继续执行。
在函数式编程里,compose是避免命令式的重要一环,fp要做到的每一个函数都是”纯“的,脏的东西交给使用者。把每个纯的函数组合到compose里面,就像gulp的pipe()一样,每个函数就像一个插件,来处理数据流。
那么我想从右到左处理数据流,

  1. function compose(fn1,fn2){
  2. return function(arg){
  3. fn1(fn2(arg));
  4. }
  5. }

那么和curry一样,我不想只能传2个函数,我想传任意函数怎么办?

这里我们不写自动组合函数了,因为在我分析redux源码的时候发现了这个函数,redux帮我们写了~我们就只来分析一下就好了。
先上一下源码。redux不跟lodash一样一个函数跳来跳去,lodash想只看一部分是不好懂的。redux的compose函数(复制可测试)。

  1. function compose() {
  2. for (var _len = arguments.length, funcs = Array(_len), _key = 0; _key < _len; _key++) {
  3. funcs[_key] = arguments[_key]; //把参数都复制到funcs数组里。
  4. }
  5.  
  6. return function () {
  7. if (funcs.length === 0) {
  8. return arguments.length <= 0 ? undefined : arguments[0]; //处理无参数的情况,返回undefined
  9. }
  10.  
  11. var last = funcs[funcs.length - 1]; //这是最后一个处理函数
  12. var rest = funcs.slice(0, -1); //取除了最后一个剩下的处理函数
  13.  
  14. return rest.reduceRight(function (composed, f) {//每次都执行当前处理函数传入基数
  15. return f(composed);
  16. }, last.apply(undefined, arguments)); //执行最后一个处理函数,返回值作为reduce的基数。
  17. };
  18. }

他的原理很简单,就是把arguments的所有函数都执行一次,把上个函数的返回值传入。这里巧妙的用了数组的reduceRight函数。
你换成reduce也可以,那这个last要换成first,这样就是从左到右执行啦。处理顺序的问题。

它俩是很有用的,例子可以看上篇博文~~点我
学好这两个函数,我们再打开函数式编程的大门吧~

javascript柯里化及组合函数~的更多相关文章

  1. JavaScript函数式编程(纯函数、柯里化以及组合函数)

    JavaScript函数式编程(纯函数.柯里化以及组合函数) 前言 函数式编程(Functional Programming),又称为泛函编程,是一种编程范式.早在很久以前就提出了函数式编程这个概念了 ...

  2. 【 js 基础 】【 源码学习 】柯里化和箭头函数

    最近在看 redux 的源码,代码结构很简单,主要就是6个文件,其中 index.js 负责将剩余5个文件中定义的方法 export 出来,其他5个文件各自负责一个方法的实现. 大部分代码比较简单,很 ...

  3. JAVAScript柯里化、部分应用参数终极理解

    一.柯里化 在定义柯里化.部分应用参数的概念前,首先必须对闭包有深入的了解和定义,闭包一句话说清楚:函数返回值为函数. 柯里化的定义:将多参函数分解为按步骤接受单个参数的函数,如下代码: var mo ...

  4. JavaScript柯里化(currying)

    参考: https://www.jianshu.com/p/33392cb4b055 https://ruby-china.org/topics/38385 https://stackoverflow ...

  5. javascript 柯里化

    先看一下代码 function add(){ var sum=0; for(var i=0;i<arguments.length;i++){ sum+=arguments[i]; } retur ...

  6. 函数柯里化实现sum函数

    需求 实现sum函数,使其可以传入不定长参数,以及不定次数调用 //示例 console.log(sum(1,2)(3)()) //6 console.log(sum(2,3,4,5)(1,2)(3) ...

  7. javascript柯里化

    function curry(fn){ var slice = Array.prototype.slice; var arr = slice.call(arguments,1); return fun ...

  8. 柯里化函数之Javascript

    柯里化函数之Javascript 定义 依据定义来说,柯里化就是将一个接收"多个"參数的函数拆分成一个或者很多个接收"单一"參数的函数.定义看起来是比較抽象的. ...

  9. 浅析 JavaScript 中的 函数 currying 柯里化

    原文:浅析 JavaScript 中的 函数 currying 柯里化 何为Curry化/柯里化? curry化来源与数学家 Haskell Curry的名字 (编程语言 Haskell也是以他的名字 ...

随机推荐

  1. anaconda + VSCode + 生产环境配置

    1. 修改jupyter notebook 默认路径: 进入anaconda 命令行, jupyter notebook --generate-config   生成配置文件, 该文件在    本机用 ...

  2. 通过爬虫程序深入浅出java 主从工作模型

    随手做的爬虫程序在   https://github.com/rli07/master_java/blob/master/spider.zip  可下载. 这是我做的系统学习图, 可以参考一下 系统架 ...

  3. 【360图书馆】插入U盘自动攻击:BadUSB原理与实现

    插入U盘自动攻击:BadUSB原理与实现       漏洞背景 “BadUSB”是今年计算机安全领域的热门话题之一,该漏洞由Karsten Nohl和Jakob Lell共同发现,并在今年的Black ...

  4. mybatis一级缓存详解

    mybatis缓存分为一级缓存,二级缓存和自定义缓存.本文重点讲解一级缓存 一:前言 在介绍缓存之前,先了解下mybatis的几个核心概念: * SqlSession:代表和数据库的一次会话,向用户提 ...

  5. mybatis源码分析(二)------------配置文件的解析

    这篇文章中,我们将讲解配置文件中 properties,typeAliases,settings和environments这些节点的解析过程. 一 properties的解析 private void ...

  6. js的日期操作:String转date日期格式、求日期差

    一.在js中String类型转成date格式 var date = new Date("2018-9-21 14:58:43");//就是这么简单 二.date转String类型就 ...

  7. Ionic1.x项目中的Installing npm packages问题

    与npm远程源有关,可以通过cnpm来解决: 一.ionic start myApp blank --skip-npm(跳过Installing npm packages会产生的问题): 二.然后进入 ...

  8. 原 线程池中shutdown()和shutdownNow()方法的区别

    参考:shutdown和shutdownNow的区别 shutDown() 当线程池调用该方法时,线程池的状态则立刻变成SHUTDOWN状态.此时,则不能再往线程池中添加任何任务,否则将会抛出Reje ...

  9. Python——Message控件

    一.messagebox的方法: showerror   : 错误提示对话框 showinfo  :  信息提示对话框 showwarning   : 警告对话框 askokcansel   :确认或 ...

  10. struts2 的struts.xml配置文件

    <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE struts PUBLIC "-/ ...