JS 柯里化 (curry)

2023-05-16

用 JS 理解柯里化

函数式编程风格,试图以函数作为参数传递(回调)和无副作用的返回函数(修改程序的状态)。

很多语言采用了这种编程风格。JavaScript,Haskell,Clojure,Erlang 和 Scala 是其中最受欢迎的几种语言。

数式编程风格具有传递和返回函数的能力,它带来了许多概念:

  • Pure Functions(纯函数)
  • Currying(柯里化)
  • Higher-Order functions(高阶函数)

我们将在这里介绍这些概念中的一种 Currying。

在本文中,我们将看到 currying 如何工作,以及它如何在软件开发人员的工作中发挥作用。

提示:您可以将其变为 Bit 组件,而不是复制粘贴可重用的 JS 功能,并快速与团队共享项目。

Bit - 使用代码组件共享和构建
Bit 可帮助您在项目和应用程序之间共享,发现和使用代码组件,以构建新功能和...         bitsrc.io

什么是 Currying?

Currying 是函数式编程中的一个过程,我们可以将具有多个参数的函数转换为顺序嵌套的函数。它返回一个接收下一个参数的新函数。

它不断返回一个新函数(接收当前参数,就像我们之前所说的那样),直到所有参数都用完为止。保留参数 "alive"(通过闭包),并且当返回并执行 currying 链中的最终函数时,所有参数都在执行中使用。

Currying 是将具有多个 arity 的函数转换为具有较少 arity 的函数的过程 -  Kristina Brainwave

注意:术语 arity “是指函数所接收的参数个数”。例如,

function fn (a, b) { // ... } function _fn(a, b, c) { // ... } 
Js

函数 fn 接受两个参数(2-arity 函数),_fn 接受三个参数(3-arity 函数)。

因此,currying 将具有多个参数的函数转换为一系列函数,每个函数都接收一个参数。

我们来看一个简单的例子:

function multiply(a, b, c) { return a * b * c; } 
Js

此函数接收三个数字,将数字相乘并返回结果。

multiply(1, 2, 3) // 6 
Js

请参阅我们如何使用完整参数调用 multiply 函数,来创建一个 curried 版本函数,看看我们如何在一系列调用中调用相同的函数(并得到相同的结果):

function multiply(a) { return (b) => { return (c) => { return a * b * c; } } } log(multiply(1)(2)(3)) // 6 
Js

我们已将 multiply(1,2,3) 函数调用转为 multiply(1)(2)(3) 多个函数调用。

我们已经将一个函数转为一系列函数。为了得到三个数相乘的结果 12 和 3 三个数字一个接一个地传递,每个数字都将在下一个函数的内部调用。我们可以将 multiply(1)(2)(3) 分开以便更好地理解它:

const mul1 = multiply(1); const mul2 = mul1(2); const result = mul2(3); log(result); // 6 
Js

让我们一个接一个地接收参数。我们把 1 传递给了 multiply 函数:

let mul1 = multiply(1); 
Js

它返回这个函数:

return (b) => { return (c) => { return a * b * c; } } 
Js

现在,mul1 保持上面的函数定义,它带有一个参数 b

我们调用了 mul1 函数,传入 2

let mul2 = mul1(2); 
Js

在 mul2 中将返回第三个函数:

return (c) => { return a * b * c; } 
Js

返回的函数现在存储在 mul2 变量中。

从本质上讲,mul2 将是:

mul2 = (c) => { return a * b * c; } 
Js

当 mul2 以 3 作为参数调用时,

const result = mul2(3); 
Js

它把之前传入的参数 a = 1b = 2 带入计算并返回结果 6

log(result); // 6 
Js

作为嵌套函数,mul2 可以访问外部函数 multiply 以及 mul1 作用域内的变量。

这就是为什么 mul2 能使用已经在的函数中定义的变量并执行乘法运算的原因。虽然这些函数早已在内存中被回收 garbage collected,但它的变量仍以某种方式保留 "alive"

您会看到三个数字一次一个地应用于该函数,并且每次都返回一个新函数,直到所有数字都用完为止。

让我们看另一个例子:

function volume(l, w, h) { return l * w * h; } const aCylinder = volume(100, 20, 90); // 180000 
Js

我们有一个计算实心物体体积的函数 volume

柯里化后的版本将接受一个参数并返回一个函数,该函数也将接收一个参数并返回一个函数。这个过程将被循环/继续,直到到达最后一个参数并返回最后一个函数,这将执行与前一个参数和最后一个参数的乘法运算。

function volume(l) { return (w) => { return (h) => { return l * w * h; } } } const aCylinder = volume(100)(20)(90) // 180000 
Js

与我们在 multiply 函数中所使用的一样,最后一个函数只接受 h ,但是它将执行的操作所用到的其他函数作用域内的变量早已被函数返回。因为闭包 Closure ,这些参数仍然有效。

currying 背后的想法是接收一个函数并返回一个特殊函数的函数。

数学中的 Currying

我有点喜欢数学例证? 维基百科给出了进一步展示 currying 的概念。让我们用自己的例子来看看它。如果我们有一个等式:

f(x,y) = x^2 + y = z

有两个变量 x 和 y。如果这两个变量给出值 x=3 和 y=4,将得出 z 的值。

如果我们在 f(x,y) 中替换 y 为 4 和 x 为 3

f(x,y) = f(3,4) = x^2 + y = 3^2 + 4 = 13 = z

我们得到结果 13

我们可以柯里化 f(x,y) 在一系列函数中提供变量:

h = x^2 + y = f(x,y)
hy(x) = x^2 + y = hx(y) = x^2 + y

[hx => w.r.t x] and [hy => w.r.t y]

注意:hx 是下标为 x 的 h 变量,hy 是下标为 y 的 h 变量。wrt 是关系表达式。

如果我们修正等式 hx(y) = x^2 + y 中的变量 x=3  ,它将返回一个以 y 作为变量的新等式:

h3(y) = 3^2 + y = 9 + y

注意:h3 是下标为3的 h

它和下面的表达式是一样的:

h3(y) = h(3)(y) = f(3,y) = 3^2 + y = 9 + y

该值尚未解决,它返回了一个 9 + y 接收另一个变量 y 的新方程式。接下来,我们传入 y=4

h3(4) = h(3)(4) = f(3,4) = 9 + 4 = 13

y 在变量链中是最后一个变量,加法操作是在前一个变量 x = 3 仍保留并且值被解析的情况下执行的,最终得到结果 13

实际上,我们将等式 f(x,y) = 3^2 + y 柯里化为一系列方程:

3^2 + y -> 9 + y

f(3,y) = h3(y) = 3^2 + y = 9 + y
f(3,y) = 9 + y
f(3,4) = h3(4) = 9 + 4 = 13

最后得出结果。

哇!!这是一些数学问题,如果你觉得这个不够清楚?。你可以阅读? 维基百科的全部具体内容。

Currying 和 Partial Function 应用

现在,有些人可能会开始认为 curried 函数的嵌套函数的数量取决于它接收的参数的数量。是的,那就是 curry。

我可以设计柯里化函数 volume 是这样的:

function volume(l) { return (w, h) => { return l * w * h; } } 
Js

所以它可以像这样调用:

const hCy = volume(70); hCy(203, 142); hCy(220, 122); hCy(120, 123); 
Js

要么

volume(70)(90, 30); volume(70)(390, 320); volume(70)(940, 340); 
Js

我们刚刚定义了一个专门的函数来计算长度为 70l)的任意圆柱体的体积。

它需要 3 个参数并具有 2 个嵌套函数,不像我们以前的版本需要 3 参数并具有 3个嵌套函数。

这个版本不是柯里化。我们刚刚部分应用了 volume 函数。

Currying 和 Partial Application 是相关的,但它们有不同的概念。

部分应用程序将函数转换为具有较少参数的另一个函数。

function acidityRatio(x, y, z) { return performOp(x, y, z); } | V function acidityRatio(x) { return (y,z) => { return performOp(x,y,z) } } 
Js

注意:我故意省略了 performOp 函数的实现。这里没有必要全部展示。但你必须知道 currying 和部分应用背后的概念。

这是 acidityRatio 函数的部分应用。不涉及到柯里化的问题。acidityRatio 函数部分应用于接受较少的 arity,期望参数少于其原始函数。

为了实现柯里化,它会是这样的:

function acidityRatio(x) { return (y) => { return (z) => { return performOp(x, y, z); } } } 
Js

Currying 根据函数的参数个数创建嵌套函数。每个函数都接收一个参数。如果没有参数,那就没有柯里化了。

Currying 适用于具有2个以上的参数的函数 —— Wikipedia
Carrying 将函数转换为函数序列,每个函数都只有一个参数。

可能存在这样一种情况,即 currying 和部分应用彼此相遇。假设我们有一个功能:

function div(x, y) { return x/y; } 
Js

如果我们部分应用它。我们将得到:

function div(x) { return (y) => { return x/y; } } 
Js

此外,currying 将给我们相同的结果:

function div(x) { return (y) => { return x/y; } } 
Js

虽然 currying 和 部分应用函数给出了相同的结果,但它们是两个不同的实体。

就像我们之前说的那样,currying 和部分应用是相关的,但实际上并没有相同的设计。他们之间的共同点是依靠闭包来工作。

Currying 有用吗?

当然有用,当您想要时,currying 会派上用场:

1. 编写可以轻松重用和配置的小代码模块,就像我们使用 npm 一样:

例如,您拥有一家商店?并希望为您的客户提供10%?折扣:

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

当一个客户购买价值500美元的商品时,你会给他的折扣会是:

const price = discount(500, 0.10); // 50 // $500 - $50 = $450 
Js

你看,从长远来看,我们会发现自己每天都在计算10%的折扣。

const price = discount(1500,0.10); // $150 // $1,500 - $150 = $1,350 const price = discount(2000,0.10); // $200 // $2,000 - $200 = $1,800 const price = discount(50,0.10); // $5 // $50 - $5 = $45 const price = discount(5000,0.10); // $500 // $5,000 - $500 = $4,500 const price = discount(300,0.10); // $30 // $300 - $30 = $270 
Js

我们可以柯里化 discount 函数,并不总是添加0.10的折扣:

function discount(discount) { return (price) => { return price * discount; } } const tenPercentDiscount = discount(0.1); 
Js

现在,我们只需计算您的客户购买商品的价格:

tenPercentDiscount(500); // $50 // $500 - $50 = $450 
Js

同样,有些优惠客户比另一些优惠客户更重要 - 我们称之为超级客户。我们希望为超级客户提供20%的折扣。

我们使用我们的柯里化后的 discount 函数:

const twentyPercentDiscount = discount(0.2); 
Js

我们通过向柯里化函数 discount 中传入 0.2 即 20% 的折扣值,为我们的超级客户设置新函数 。

返回的函数 twentyPercentDiscount 将用于计算我们的超级客户的折扣:

twentyPercentDiscount(500); // 100 // $500 - $100 = $400 twentyPercentDiscount(5000); // 1000 // $5,000 - $1,000 = $4,000 twentyPercentDiscount(1000000); // 200000 // $1,000,000 - $200,000 = $600,000 
Js

2. 避免频繁调用具有相同参数的函数:

例如,我们有一个计算圆柱体积的函数:

function volume(l, w, h) { return l * w * h; } 
Js

碰巧仓库中的所有气缸的高度都为100米。你会看到,你会反复调用 h 为 100 的函数:

volume(200,30,100) // 2003000l volume(32,45,100); //144000l volume(2322,232,100) // 53870400l 
Js

要解决这个问题,你可以调整 volume 函数(就像我们之前做的那样):

function volume(l) { return (w) => { return (h) => { return l * w * h; } } } 
Js

我们可以为特定高度的圆柱定义一个特定的函数:

const hCylinderHeight = volume(100); hCylinderHeight(200)(30); // 600,000l hCylinderHeight(2322)(232); // 53,870,400l 
Js

General Curry Function 通用柯里化函数

让我们开发一个函数,它接受任何函数并返回函数的 curried 版本。

要做到这一点,我们将有这个方法(虽然你自己的方法可能与我的不同):

function curry(fn, ...args) { return (..._arg) => { return fn(...args, ..._arg); } } 
Js

我们在这做了什么?我们的柯里化函数接受我们想要柯里化的函数(fn)和可变数量的参数(... args)。rest 运算符用于将 fn 之后的参数收集到 ... args 中。

接下来,我们返回一个函数,该函数还将其余参数收集为... _args。该函数调用原始函数 fn 传入  ...args 和  ..._args 使用 spread 运算符作为参数,然后将该值返回给用户。

我们现在可以使用我们自己的 curry 函数来创建特定的函数。

让我们使用 curry 函数来创建一个更具体的函数(计算长度为100m的柱的体积):

function volume(l, h, w) { return l * h * w; } const hCy = curry(volume, 100); hCy(200, 900); // 180000l hCy(70, 60); // 4200l 
Js

结论

闭包使 currying 在 JavaScript 中实现成为可能。它能够保留已经执行的函数的状态,使我们能够创建工厂函数 - 工厂函数可以为其参数添加特定值。

围绕 currying,closures 和函数式编程问题是非常棘手的。但我向你保证随着时间的推移和不断的练习?,你将渐渐掌握它,看看它是一件多么值得做的事?

转载于:https://www.cnblogs.com/yk123/p/10119936.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JS 柯里化 (curry) 的相关文章

  • micropython安装ros_ROS2与STM32入门教程-microROS的freertos版本

    ROS2与STM32入门教程 micro ros的freertos版本 说明 xff1a 介绍如何安装使用micro ros 测试开发板 xff1a olimex stm32 e407 步骤 xff1a 安装ros2版本foxy xff0c
  • C#中通过com组件操作excel不能关闭的问题

    问题 xff1a 当用如下代码操作完Excel xff0c 虽然调用了Application的Quit 方法 xff0c 但发现Excel进程并没退出 object missing 61 System Reflection Missing
  • 交叉编译的概念及交叉编译工具的安装

    目录 一 什么是交叉编译 二 为什么要交叉编译 xff1f 三 交叉编译链的安装 四 相关使用方法 五 软连接 一 什么是交叉编译 交叉编译是指将一种编程语言编写的程序编译成另一种编程语言的程序 xff0c 通常是在不同的操作系统或硬件环境
  • .cn根服务器被攻击之后

    如果是互联网行业的人员应该知道 xff0c 8月25日凌晨 xff0c 大批的 cn 域名的网站都无法访问 xff0c 当然包括weibo cn等大型网站 个人比较奇怪的一件事情是 xff0c 微博PC网页版是 www weibo com
  • [UML]UML系列——包图Package

    系列文章 UML UML系列 用例图Use Case UML UML系列 用例图中的各种关系 xff08 include extend xff09 UML UML系列 类图Class UML UML系列 类图class的关联关系 xff08
  • VBA编程中的 sheet1 与 sheets(1)的区别

    自己理解 sheet1是一个专有名词 xff0c 不是任何对象的属性 xff0c 只能单独使用 xff0c 特指代码所在工作簿的那个sheet1 和顺序无关 xff0c 是固定的一个表 xff0c sheets 1 则和顺序有关 参考资料
  • python练习笔记——计算1/1-1/3+1/5-1/7……的和

    1 1 1 3 43 1 5 1 7 43 求100000个这样的分式计算之为是多少 xff1f 将此值乘以4后打印出来 xff0c 看看是什么 xff1f num list 61 count 61 1 i 61 1 while True
  • Django Model获取指定列的数据

    model一般都是有多个属性的 xff0c 但是很多时候我们又只需要查询特定的某一个 xff0c 这个时候可以用到values和values list 利用values查询 from attendence models import Emp
  • HIVE自定义函数的扩展

    作者简介 淳敏 xff0c 物流架构师同时也是一位team leader xff0c 工作认真负责 xff0c 曾在休假期间 面向大海编程 xff0c 不明觉厉 在Hive中 xff0c 用户可以自定义一些函数 xff0c 用于扩展Hive
  • Flink Window分析及Watermark解决乱序数据机制深入剖析-Flink牛刀小试

    版权声明 xff1a 本套技术专栏是作者 xff08 秦凯新 xff09 平时工作的总结和升华 xff0c 通过从真实商业环境抽取案例进行总结和分享 xff0c 并给出商业应用的调优建议和集群环境容量规划等内容 xff0c 请持续关注本套博
  • 使用Network Recycle Bin启用映射网络驱动器上的回收站

    前言 在内网环境中我们经常会使用NAS或者Samba在Windows中映射网络驱动器 xff0c 方便局域网用户实时共享交换数据 但当存储在网络或映射网络上的任何文件被删除时 xff0c 该文件将被永久删除 它不会去到本地计算机回收站 xf
  • 为强化机器学习性能,ARM推出两款新GPU Mali-G52和Mali-G31

    ARM于近期推出了两款图形处理器产品 xff0c 分别为Mali G52以及Mali G31 xff0c 主要应用于主流移动市场 由于移动端AI计算 图形处理需求的与日俱增 xff0c GPU之于手机SoC的作用日渐凸显 xff0c ARM
  • 判断python字典某个键的值是否为空

    2019独角兽企业重金招聘Python工程师标准 gt gt gt code if dict get key 0 61 61 0 值即为空 code 转载于 https my oschina net u 2254175 blog 37213
  • javascript中的==和===

    判断两个变量是否相等是程序设计中非常重要的运算 在处理原始值时 xff0c 这种运算相当简单 xff0c 但涉及对 象 xff0c 任务就稍有点复杂 ECMAScript提供了两套运算符处理这个问题 xff0c 等号和非等号用于处理原始值
  • 如何检测资源泄露

    Window上我们常见的资源泄露包括内存和对象句柄泄露 xff0c 下面讨论下对各类泄露的检测方法 关于内存泄漏 xff0c 我以前写过2篇文章 xff1a C 43 43 中基于Crt的内存泄漏检测 xff0c 基于WinDbg的内存泄漏
  • WPF触屏Touch事件在嵌套控件中的响应问题

    前几天遇到个touch事件的坑 xff0c 记录下来以增强理解 具体是 想把一个listview嵌套到另一个listview xff0c 这时候如果list view xff08 子listview xff09 的内容过多超过容器高度 xf
  • 设计模式-工厂模式

    xl echo编辑整理 xff0c 欢迎转载 xff0c 转载请声明文章来源 欢迎添加echo微信 微信号 xff1a t2421499075 进行交流学习 百战不败 xff0c 依不自称常胜 xff0c 百败不颓 xff0c 依能奋力前行

随机推荐

  • IBM AIX5.3 linux下C/C++实现HTTPS接口

    最近在工作中需要开发一个Https接口 xff0c 其不同于http soap等协议 xff0c 可以直接组织报文并发送 xff0c 不存在加密 xff0c 认证和获取密钥等安全操作 且之前开发的项目没有开发过这类接口 xff0c 所以当时
  • c语言把网络字节序转换成小端,网络编程字节序转换问题

    一 xff1a 大小端 一 大小端区别 字节 区别是依据 xff1a 计算机系统在存储数据时起始地址是高地址仍是低地址 小端 xff1a 从低地址开始存储 大端 xff1a 从高地址开始存储 补充 xff1a 这里大小端是按字节区别的 xf
  • [转载]Linux C 字符串函数 sprintf()、snprintf() 详解

    一 sprintf 函数详解 在将各种类 型的数据构造成字符串时 xff0c sprintf 的强大功能很少会让你失望 由于 sprintf 跟 printf 在用法上几乎一样 xff0c 只是打印的目的地不同而已 xff0c 前者打印到字
  • http Authorization

    MDN 文档 HTTP协议中的 Authorization 请求消息头含有服务器用于验证用户代理身份的凭证 xff0c 通常会在服务器返回401 Authorization lt type gt lt credentials gt curl
  • linux编译动态库未定义,GCC链接库的一个坑:动态库存在却提示未定义动态库的函数...

    背景 在GCC中已经指定链接库 xff0c 然而编译时却提示动态库函数未定义 xff01 测试出现的错误提示如下 xff1a GMPY 64 13 48 tmp gcc o test L lmylib test c tmp ccysQZI3
  • .inf右键没有安装菜单项解决办法

    打开我的电脑 xff0c 工具菜单中的文件夹选项 切换至文件类型选项卡 xff0c 在其中找到inf文件 xff0c 点高级按钮 xff0c 双击安装 I xff0c 没有新建一个 按如下内容修 改 用于执行操作的应用程序C WINDOWS
  • C++ : 编译单元、声明和定义、头文件作用、防止头文件在同一个编译单元重复引用、static和不具名空间...

    转 自 xff1a http www cnblogs com rocketfan archive 2009 10 02 1577361 html 1 编译单元 xff1a 一个 cc或 cpp文件作为一个编译单元 xff0c 生成 o 2
  • request方法

    2019独角兽企业重金招聘Python工程师标准 gt gt gt 1request概述 request是Servlet service 方法的一个参数 xff0c 类型为javax servlet http HttpServletRequ
  • Eclipse调试:改变颜色, 背景与字体大小 和xml字体调整

    Eclipse背景颜色修改 xff1a 操作界面默认颜色为白色 对于我们长期使用电脑编程的人来说 xff0c 白色很刺激我们的眼睛 xff0c 所以我经常会改变workspace的背景色 xff0c 使眼睛舒服一些 设置方法如下 xff1a
  • ASM汇编常用跳转指令-极速查

    作者 xff1a 逆向驿站 微信公众号 xff1a 逆向驿站 知乎 xff1a 逆向驿站 若不是老鸟 xff0c 是不是经常为各种JXX汇编跳转指令查资料 xff1f 影响效率 xff0c 更影响潜意识整体分析的 34 灵光一现 34 本公
  • Android签名机制及原理

    Android签名机制及原理 Android系统在安装APK的时候 xff0c 首先会检验APK的签名 xff0c 如果发现签名文件不存在或者校验签名失败 xff0c 则会拒绝安装 xff0c 所以应用程序在发布之前一定要进行签名 给APK
  • chrome扩展获取页面dom对象信息

    chrome扩展 xff0c 在popup页面 xff0c 给页面对象绑定点击事件 xff0c 获取当前tab加载页面的DOM对象信息 本chrome扩展功能主要用于获取百度搜索输入框中用户输入的关键字 效果如下 源代码如下 注意 xff1
  • CentOS 7之FirewallD与iptables的区别

    2019独角兽企业重金招聘Python工程师标准 gt gt gt FirewallD 即Dynamic Firewall Manager of Linux systems xff0c Linux系统的动态防火墙管理器 xff0c 是 ip
  • goland编辑器护眼背景颜色设置

    2019独角兽企业重金招聘Python工程师标准 gt gt gt 打开goland软件 菜单栏 file settings 打开如下 搞定 色调 xff1a 85 xff1b 饱和度 xff1a 123 xff1b 亮度 xff1a 20
  • 让IE8支持HTML5及canvas功能!

    让IE8支持HTML5及canvas功能 xff01 微软出的IE9支持HTML5 xff0c 但因为不支持XP系统 xff0c 暂时我还用不了 即使能用 xff0c 现阶段如果开发HTML5页面 xff0c 并考虑到兼容性问题的话 xff
  • 卷积神经网络(CNN)的训练过程

    卷积神经网络的训练过程 卷积神经网络的训练过程分为两个阶段 第一个阶段是数据由低层次向高层次传播的阶段 xff0c 即前向传播阶段 另外一个阶段是 xff0c 当前向传播得出的结果与预期不相符时 xff0c 将误差从高层次向底层次进行传播训
  • 编程常用英语词汇大全

    编程常用英语词汇大全 很实用的编程英语词库 xff0c 共收录一千五百余条词汇 第一部分 xff1a application 应用程式 应用 应用程序 application framework 应用程式框架 应用框架 应用程序框架 arc
  • [LTMP搭建] Centos 6.5 安装配置 Tengine

    接上篇 xff1a http www cnblogs com antarctican p 3752812 html 安装PHP 一 安装依赖的扩展 记得前几天编译tengine xff0c 不使用 with http lua module
  • .net项目移植后的虚拟目录的配置问题

    VS NET 2003 开发环境打开此项目 xff08 MyWeb xff09 时 xff0c 出现如果如下问题 xff1a 无法从 Web 服务器获取项目文件 无法打开 Web 项目 MyWeb 文件路径 C Inetpub wwwroo
  • JS 柯里化 (curry)

    用 JS 理解柯里化 函数式编程风格 xff0c 试图以函数作为参数传递 xff08 回调 xff09 和无副作用的返回函数 xff08 修改程序的状态 xff09 很多语言采用了这种编程风格 JavaScript xff0c Haskel