三言两语说透柯里化和反柯里化

news2024/11/24 2:14:08

JavaScript中的柯里化(Currying)和反柯里化(Uncurrying)是两种很有用的技术,可以帮助我们写出更加优雅、泛用的函数。本文将首先介绍柯里化的概念、实现原理和应用场景,然后介绍反柯里化的概念、实现原理和应用场景,通过大量的代码示例帮助读者深入理解这两种技术的用途。

JavaScript中的柯里化

概念

柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。这个技术由数学家Haskell Curry命名。

简单来说,柯里化可以将使用多个参数的函数转换成一系列使用一个参数的函数。例如:

function add(a, b) {
  return a + b; 
}

// 柯里化后
function curriedAdd(a) {
  return function(b) {
    return a + b;
  }
}

实现原理

实现柯里化的关键是通过闭包保存函数参数。以下是柯里化函数的一般模式:

function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    } else {
      return function(...args2) {
        return curried.apply(this, args.concat(args2));
      }
    }
  }
}

curry函数接受一个fn函数为参数,返回一个curried函数。curried函数检查接收的参数个数args.length是否满足fn函数需要的参数个数fn.length。如果满足,则直接调用fn函数;如果不满足,则继续返回curried函数等待接收剩余参数。

这样通过闭包保存每次收到的参数,直到参数的总数达到fn需要的参数个数,然后将保存的参数全部 apply fn执行。

利用这个模式可以轻松将普通函数柯里化:

// 普通函数
function add(a, b) {
  return a + b;
} 

// 柯里化后
let curriedAdd = curry(add); 
curriedAdd(1)(2); // 3

应用场景

  1. 参数复用

柯里化可以让我们轻松复用参数。例如:

function discounts(price, discount) {
  return price * discount;
}

// 柯里化后
const tenPercentDiscount = discounts(0.1); 
tenPercentDiscount(500); // 50
tenPercentDiscount(200); // 20
  1. 提前返回函数副本

有时我们需要提前返回函数的副本给其他模块使用,这时可以用柯里化。

// 模块A
function ajax(type, url, data) {
  // 发送ajax请求
}

// 柯里化后
export const getJSON = curry(ajax)('GET');

// 模块B
import { getJSON } from './moduleA'; 

getJSON('/users', {name: 'John'});
  1. 延迟执行

柯里化函数在调用时并不会立即执行,而是返回一个函数等待完整的参数后再执行。这让我们可以更加灵活地控制函数的执行时机。

let log = curry(console.log);

log('Hello'); // 不会立即执行

setTimeout(() => {
  log('Hello'); // 2秒后执行
}, 2000);

JavaScript中的反柯里化

概念

反柯里化(Uncurrying)与柯里化相反,它将一个接受单一参数的函数转换成接受多个参数的函数。

// 柯里化函数  
function curriedAdd(a) {
  return function(b) {
    return a + b;
  }
}

// 反柯里化后
function uncurriedAdd(a, b) {
  return a + b; 
}

实现原理

反柯里化的关键是通过递归不停调用函数并传入参数,Until参数的数量达到函数需要的参数个数。

function uncurry(fn) {
  return function(...args) {
    let context = this;
    return args.reduce((acc, cur) => {
      return acc.call(context, cur); 
    }, fn);
  }
}

uncurry 接收一个函数 fn,返回一个函数。这个函数利用reduce不停调用 fn 并传入参数,Untilargs所有参数都传给 fn

利用这个模式可以轻松实现反柯里化:

const curriedAdd = a => b => a + b;

const uncurriedAdd = uncurry(curriedAdd);
uncurriedAdd(1, 2); // 3

应用场景

  1. 统一接口规范

有时我们会从其他模块接收到一个柯里化的函数,但我们的接口需要一个普通的多参数函数。这时可以通过反柯里化来实现统一。

// 模块A导出
export const curriedGetUser = id => callback => {
  // 调用callback(user)
};

// 模块B中
import { curriedGetUser } from './moduleA';

// 反柯里化以符合接口
const getUser = uncurry(curriedGetUser); 

getUser(123, user => {
  // use user
});
  1. 提高参数灵活性

反柯里化可以让我们以任意顺序 passes 入参数,增加了函数的灵活性。

const uncurriedLog = uncurry(console.log);

uncurriedLog('a', 'b'); 
uncurriedLog('b', 'a'); // 参数顺序灵活
  1. 支持默认参数

柯里化函数不容易实现默认参数,而反柯里化后可以方便地设置默认参数。

function uncurriedRequest(url, method='GET', payload) {
  // 请求逻辑
}

大厂面试题解析

实现add(1)(2)(3)输出6的函数

这是一道典型的柯里化面试题。解析:

function curry(fn) {
  return function curried(a) {
    return function(b) {
      return fn(a, b);
    }
  }
}

function add(a, b) {
  return a + b;
}

const curriedAdd = curry(add);

curriedAdd(1)(2)(3); // 6

利用柯里化技术,我们可以将普通的 add 函数转化为 curriedAdd,它每次只接收一个参数,并返回函数等待下一个参数,从而实现了 add(1)(2)(3) 的效果。

实现单参数compose函数

compose函数可以将多个函数合并成一个函数,这也是一道常见的柯里化面试题。解析:

function compose(fn1) {
  return function(fn2) { 
    return function(x) {
      return fn1(fn2(x));
    };
  };
}

function double(x) {
  return x * 2;
}

function square(x) {
  return x * x;
}

const func = compose(double)(square);

func(5); // 50

利用柯里化,我们创建了一个单参数的 compose 函数,它每次返回一个函数等待下一个函数参数。这样最终实现了 compose(double)(square) 的效果。

反柯里化Function.bind

Function.bind 函数实现了部分参数绑定,这本质上是一个反柯里化的过程。解析:

Function.prototype.uncurriedBind = function(context) {
  const fn = this;
  return function(...args) {
    return fn.call(context, ...args);
  } 
}

function greet(greeting, name) {
  console.log(greeting, name);
}

const greetHello = greet.uncurriedBind('Hello');
greetHello('John'); // Hello John

uncurriedBind 通过递归调用并传参实现了反柯里化,使 bind 参数从两步变成一步传入,这也是 Function.bind 的工作原理。

总结

柯里化和反柯里化都是非常有用的编程技巧,让我们可以写出更加灵活通用的函数。理解这两种技术的实现原理可以帮助我们更好地运用它们。在编码中,我们可以根据需要决定是将普通函数柯里化,还是将柯里化函数反柯里化。合理运用这两种技术可以大大提高我们的编程效率。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/825879.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[SSM]SpringMVC详解

目录 一、SpringMVC简介 1.1什么是MVC 1.2什么是SpringMVC 1.3SpringMVC优点 1.4SpringMVC优化的方向 1.5SpringMVC执行的流程 1.6基于注解的SpringMVC程序 二、SpringMVC注解式开发 2.1RequestMapping定义请求规则 2.1.1指定模块名称 2.1.2对请求提交方式的定义 2…

好用的低代码开发平台是什么样的?

一、好用的低代码开发平台是什么样的? 从企业角度来说,优化流程,提升企业运行效率;节省成本,提高企业效益;维护方便,即改即用;一键升级,方便实用; 从开发者角…

JVM | 从类加载到JVM内存结构

引言 我在上篇文章:JVM | 基于类加载的一次完全实践 中为你讲解如何请“建筑工人”来做一些定制化的工作。但是,大型的Java应用程序时,材料(类)何止数万,我们直接堆放在工地上(JVM)…

企业如何有效保护文件传输的安全性

文件传输是现代商业世界中每个企业日常操作的必需品。但是,传统的文件传输方式,如电子邮件和网络共享,并不总是安全可靠。黑客攻击、网络钓鱼和数据泄露等风险时刻存在。因此,企业需要采取措施保障文件传输的安全性。本文将介绍如…

Shell脚本学习-case条件语句

case条件语句相当于多分支的if/elif/else条件语句,但是它更规范工整。常被应用于实现系统服务启动脚本等企业应用场景中。 语法结构: case "变量" in值1)指令1...;;值2)指令2...;;*)指令3... esac 说明: 1)case语句…

从 GPU 到 ChatGPT,一文带你理清GPU/CPU/AI/NLP/GPT之间的千丝万缕【建议收藏】

目录 硬件 GPU 什么是 GPU? GPU 是如何工作的? GPU 和 CPU 的区别 GPU 厂商 海外头部 GPU 厂商: 国内 GPU 厂商: nvidia 的产品矩阵 AI 什么是人工智能 (Artificial Intelligence-AI)? 人工智能细分领域 …

手把手教你写代码——基于控制台的通讯录管理系统(多人)(代码详细注释)

写在前面 本文章适合刚开始学习java的同学,不适合已参与java开发的人群!本项目源代码已绑定资源中可免费获取!如果对你有帮助请 栏目介绍 本栏目专为入门java学习者设计的一些简单的入门项目 功能介绍 本项目为简单的基于控制台的通讯录管理系…

音乐节《迷笛音乐节》游玩感

上周,去了烟台,参加音乐节,以前从未参加过,所以趁着本周六周日双休的时候,去游玩了一次。(1)一种新奇体验 对于自己来说,参加音乐节还是一种新奇的体验的,也是疫情放开了…

【MyBatis】初学MyBatis

目录 MyBatis 是什么?MyBatis框架搭建1.添加MyBatis框架2.设置MyBatis配置数据库的相关链接信息xml 保存路径和命名格式 根据MyBatis写法完成数据库的操作MyBatis插件MyBatis传递参数查询${} 和 #{} 有什么区别?SQL注入问题 MyBatis like查询MyBatis多表…

Lombok,一个神奇的存在

1、概述 Lombok主要用于在编译POJO类源文件时通过注解的方式自动为该类生成构造方法、getter/setter、equals、hashcode、toString等方法,有效地简化了POJO类代码,提高了软件的开发速度。 2、安装 a、启动IntelliJ IDEA—>点击CtrlAltS快捷键&…

【LeetCode】链表反转

题目 题目:给定单链表头节点,将单链表的链接顺序反转过来 例: 输入:1->2->3->4->5 输出:5->4->3->2->1 要求:按照两种方式实现 解决办法 方式一:(直接迭…

从0开始自学网络安全(黑客)

前言 黑客技能是一项非常复杂和专业的技能,需要广泛的计算机知识和网络安全知识。你可以参考下面一些学习步骤,系统自学网络安全。 在学习之前,要给自己定一个目标或者思考一下要达到一个什么样的水平,是学完找工作(…

这所211考数一英二,学硕降分33分,十分罕见!

一、学校及专业介绍 合肥工业大学(Hefei University of Technology),简称“合工大”,校本部位于安徽省合肥市,是中华人民共和国教育部直属的全国重点大学,是国家“双一流”建设高校, 国家“211工…

PHP代码审计——实操!

ctfshow PHP特性 web93 八进制与小数点 <?php include("flag.php"); highlight_file(__FILE__); if(isset($_GET[num])){$num $_GET[num];if($num4476){die("no no no!");}if(preg_match("/[a-z]/i", $num)){die("no no no!")…

git 忽略掉不需要的文件

第一步&#xff1a;创建.gitignore文件 touch .gitignore 第二步&#xff1a;使用vi编辑器 输入不需要的文件&#xff0c;或用通配符*来忽视一系列文件 效果&#xff1a;

【Java可执行命令】(十二)依赖分析工具jdeps:通过静态分析字节码并提取相关信息来实现依赖分析 ~

Java可执行命令之jdeps 1️⃣ 概念2️⃣ 优势和缺点3️⃣ 使用3.1 语法格式3.2 jdeps -dotoutput < dir>3.3 jdeps -s3.4 jdeps -v3.5 jdeps -cp < path>3.6 注意事项&#xff1a; 4️⃣ 应用场景&#x1f33e; 总结 1️⃣ 概念 Java中的jdeps命令是一个用于分析类…

使用脱机 MFA确保远程员工的安全

远程工作支持的优势 未更改的企业访问&#xff1a;远程工作支持开辟了访问企业网络和资源以及其中保存的数据的替代方法。应采取必要措施&#xff0c;确保它们保持完整&#xff0c;不受远程破坏企图的影响。提高工作效率&#xff1a;理想情况下&#xff0c;远程工作支持可提高…

程序框架-事件中心模块-观察者模式

1.Monster //触发事件 EventCenter.GetInstance().EventTrigger("MonsterDead",this);2.Player void Start() { EventCenter.GetInstance().AddEventListener("MonsterDead", MonsterDeadDo); }public void MonsterDeadDo(object info) {Debug.Log(&q…

【测试开发】Mq消息重复如何测试?

本篇文章主要讲述重复消费的原因&#xff0c;以及如何去测试这个场景&#xff0c;最后也会告诉大家&#xff0c;目前互联网项目关于如何避免重复消费的解决方案。 Mq为什么会有重复消费的问题? Mq 常见的缺点之一就是消息重复消费问题&#xff0c;产生这种问题的原因是什么呢…

从封面开始,打造一个引人注目的视频作品

在如今的互联网时代&#xff0c;短视频已经成为了人们生活中不可或缺的一部分。而一个吸引人的视频封面可以让你的作品更具吸引力&#xff0c;吸引更多观众的点击。那么&#xff0c;如何制作一个令人印象深刻的视频封面呢&#xff1f;下面就让我们揭秘一些实用技巧吧&#xff0…