首页 > 其他分享 >JS 中的函数式 - 常用概念

JS 中的函数式 - 常用概念

时间:2024-04-15 17:35:53浏览次数:27  
标签:常用 compose 函数 JS curriedFn params 柯里化 fn

一、柯里化 - currying

柯里化的意思是将一个多元函数,转换成一个依次调用的单元函数,用表达式表示: f(a,b,c) → f(a)(b)(c)   

核心代码实现:

export function curry(fn) {
  let len = fn.length; // 收集目标函数的参数个数(总)

  return function curriedFn(...params) {
    // 说明当前收集到的参数还没有达到目标函数需要接收到的,需要返回一个新的函数继续去收集
    if (params.length < len) {
      return function () {
        // 一个关键是下面这个 arguments 是属于当前这个匿名函数的
        // 另外一个关键是 params.concat(...), 这里其实应用了尾调用优化
        return curriedFn.apply(
          null,
          params.concat([].slice.call(arguments))
        )
      }
    }

    return fn.apply(null, params);
  }
}

 

理解难点在于  curriedFn 中的逻辑判断为何需要返回一个匿名函数(以  anonymousFn  来指代)   比如 sum(1, 2, 3) 这种函数,经过柯里化后并调用的形式是 curry(sum)(1)(2)(3) 这样: 当然这种形式可以分解成四句:
let curriedSum = curry(sum); // curriedSum 是一个函数
let tmpA = curriedSum(1); // tmpA 是一个函数
let tmpB = tmpA(2); // tmpB 是一个函数
let tmpC = tmpB(3); // tmpC 则是最终的运算结果,就是原函数 sum(1, 2, 3) 的运算结果,即不是一个函数
接下来逐句分析:
  • 执行第一句, curry 函数执行并形成闭包(暂时命名成  curry-[fn] ),len 记录为 3,返回 curriedFn ,即  curriedSum  就是 curriedFn
  • 执行第二句,其实执行的是 curriedFn(1) ,此时 params = [1] ,进入逻辑判断, curriedFn 形成一层闭包(暂时命名成 curriedFn-[1] ),返回匿名函数,即 tmpA 就是这个匿名函数
  • 执行第三句,运行这个匿名函数,用伪代码表示如下,这句有两个关键点:
    let tmpA = function () {
      return curriedFn.call(null, params.concat([].slice.call(arguments)))
    }
    
    tmpA(2);
    1. 首先是 arguments,这个变量属于这个匿名函数,而在此时变量的值就是  [2] (传入的变量就是 2,如果是 tmpA(2, 3)  这种,那么 arguments = [2, 3] )
    2. 其次 params.concat([].slice.call(arguments)) ,这个结合缓存的结果就是 [1].concat([2]) ,即 [1, 2] ,然后将这个值作为参数进行递归 curriedFn([1, 2]) ,所以说这里其实应用了尾递归优化

所以经过运算,最终 tmpB 也是那个匿名函数,而第二句中不是形成了一个 curriedFn-[1] 的闭包嘛,此时也被释放,而随着 curriedFn([1, 2]) 的执行,形成一个新的闭包(暂时命名成  curriedFn-[1, 2] ),在这个新的闭包中, params = [1, 2] 

  • 执行第四句,走的是 curriedFn 中  return fn.apply(null, params);  这句,得出最终的运算结果,这里就不详叙述

其他

当然,柯里化的含义比较严格,只有 f(a,b,c) => f(a)(b)(c)  这样的形式才能叫真正的柯里化 而 f(a, b, c) => f(a)(b, c) 或者 f(a, b, c) => f(a, b)(c) 这种其实叫部分函数应用 即柯里化可以实现部分函数应用,但是柯里化不等于部分函数应用  

参考文档

二、组合 - compose

组合指的是将多个函数组合成一个函数,这样一个函数的输出就可以作为另一个函数的输入,从而实现多个函数的链式调用

组合compose可以提高代码的可读性和可维护性,减少重复代码的出现,更加便捷的实现函数的复用

用表达式表示: compose(f, g, t) => x => f(g(t(x))) ,进一步结合柯里化则是 compose(f)(g)(t) => x => f(g(t(x))) 

概念上, compose  函数像是 curry 的逆运算,把多个函数链接起来依次调用,不用过于关注其中的执行过程,直接得到最终结果,这样最直观的好处是节省了一堆临时变量  

核心代码实现:

// 普通版
export const compose = (...fns) =>
  (...args) => fns.reduceRight((val, fn) => fn.apply(null, [].concat(val)), args);

// 异步版 
export const compose = (...fns) =>
  (input) => fns.reduceRight((chain, fn) => chain.then(fn), Promise.resolve(input));

// 普通版支持链接后多参数入参,而异步版则只支持单参数(更符合函数式编程思想)

三、管道 - pipe

管道其实是组合的另外一个版本,组合是从右向左依次调用处理函数,使用的是 reduceRight ,而管道则是从左向右依次调用处理函数,使用的是 reduce 函数

 

四、实践经验

一、柯里化中把要操作的数据放到最后

从 柯里化 的代码中可以看出,除开需要柯里化的函数(简称目标函数),最先输入(固定)的参数是变更次数最少的,所以这条的意义其实是按照变更频率从小到大的顺序来编写目标函数,如:
// 推荐
const target = (x, str) => str.split(x);

// 不推荐
const target = (str, x) => str.split(x);

 

二、函数组合中函数要求单输入

函数组合有个使用要点,就是中间的函数一定是单输入的,这个很好理解,因为函数的输出都是单个的(数组也只是一个元素),同时,这也是最符合函数式编程思想的定义函数的方式
即:传给 compose 函数的参数,最好是经过 curry 化的函数
 

三、函数组合的 Debug

// debugger 函数,其中 x 是 reverse 这个函数的计算值
const trace = curry((tip, x) => { console.log(tip, x); return x; });

const fn = compose(toUpperCase, head, trace('after reverse'), reverse);

 

四、多多参考 Ramda.js

标签:常用,compose,函数,JS,curriedFn,params,柯里化,fn
From: https://www.cnblogs.com/cc-freiheit/p/18136055

相关文章

  • 在 C 中定义函数 - 如何定义 C 中的函数
    在C编程中,函数扮演着基本的角色。它们使您能够编写组织良好且易于维护的代码。在本文中,您将学习在C中定义函数的基础知识。(本文视频讲解:java567.com)C中的函数是什么?在编程中,函数是执行特定任务的代码块。函数接受输入,处理它们,执行操作,并产生输出。函数很重要,因为它们组......
  • C++_内存模型和函数以及类
    C++内存模型函数函数与编译器类成员变量class内部通过 static修饰变量时,表示该变量为静态成员变量,必须在类外定义 staticconst修饰变量时,表示该变量为静态成员常量,可以在类内初始化,或者在类外初始化 staticconstexpr修饰变量时,表示该......
  • 使用fabric.js根据坐标生成svg图,并使用echarts显示
    仍然是在图片上特定区域根据数值显示不同的颜色的需求。拖了这么久,最终的解决方案终于定下来了:使用aoi检测设备导出的坐标来标定需显示数值和颜色的区域,如此一来就不需要人操作UI界面来标定数值的显示区域。最终使用echarts显示的方法有2种:地图map+使用坐标标记区域且区域有n......
  • 函数对象,闭包函数及装饰器了解
    函数对象【1】定义函数对象指的是函数可以被当做数据来处理【2】可以直接被引用定义一个函数用一个新的变量名来存,用新的变量名来调用#定义一个函数defadd(x,y):returnx+y#将函数地址绑定给一个变量func=add#通过这个变量找到对应的地址,从而调用函数res......
  • hash()函数在python2和python3的区别
    在Python3中,对于字符串类型的对象,hash()函数会根据当前进程的种子值计算哈希值。这个种子值在每次Python进程启动时都会随机生成。因此,即使是相同的字符串,在不同的Python进程中调用hash()函数会得到不同的哈希值。这种设计的目的是为了增加哈希表的随机性,从而提高安全性......
  • 析构函数与 -O2 优化的一个问题
    在赋值时,我们需要先对原有对象调用析构函数。我的析构函数实现如下:~vector() { for(ptr*itr=begin_p;itr!=finish_p;itr++) { delete*itr; } delete[]begin_p; begin_p=nullptr;finish_p=nullptr;end_p=nullptr; }不使用-O2优化,程序运行正常,调用完析构函......
  • PHP strlen() 和mb_strlen()函数
    <?php   //测试时文件的编码方式要是UTF8   $str='中文a字1符';   echostrlen($str).'<br>';//14   echomb_strlen($str,'utf8').'<br>';//6   echomb_strlen($str,'gbk').'<br>';//8   echomb_s......
  • JSNice:Predicting Program Properties from “Big Code”
    发表:ACMSIGPLANNotices,2015,苏黎世联邦理工学院计算机科学系SoftwareReliabilityLab,AndreasKrause团队(https://scholar.google.com/citations?user=eDHv58AAAAAJ)(https://www.sri.inf.ethz.ch/research/plml)工具:http://jsnice.org内容概括  文章通过“大代码”......
  • js获取元素位置
    js获取元素位置JavaScript中获取元素位置的方法有以下几种实现方式: 使用getBoundingClientRect()方法:constelement=document.getElementById('elementId');constrect=element.getBoundingClientRect();constposition={top:rect.top,lef......
  • python-函数以及函数的返回值
    '''函数:把具有独立功能的代码块组合成一个个小模块作用:提高代码的效率,实现代码重复---流程标准化#可以在不同的地方多次调用,想要使用几次就使用几次,更加灵活,只需要调用,不需要重新定义'''#def函数名():#函数的定义#函数名需要复合标志符的命名规范(必须是字母,下划线,数......