如何在 JavaScript 中正确柯里化函数?

2024-01-12

我写了一个简单的curryJavaScript 中的函数在大多数情况下都能正常工作:

const curry = (f, ...a) => a.length < f.length
    ? (...b) => curry(f, ...a, ...b)
    : f(...a);

const add = curry((a, b, c) => a + b + c);

const add2 = add(2);

const add5 = add2(3);

console.log(add5(5));

但是,它不适用于以下情况:

// length :: [a] -> Number
const length = a => a.length;

// filter :: (a -> Bool) -> [a] -> [a]
const filter = curry((f, a) => a.filter(f));

// compose :: (b -> c) -> (a -> b) -> a -> c
const compose = curry((f, g, x) => f(g(x)));

// countWhere :: (a -> Bool) -> [a] -> Number
const countWhere = compose(compose(length), filter);

根据以下问题countWhere定义为(length .) . filter:

(f .) . 是什么意思? g 在 Haskell 中的意思是? https://stackoverflow.com/q/20279306/783743

因此我应该能够使用countWhere如下:

const odd = n => n % 2 === 1;

countWhere(odd, [1,2,3,4,5]);

然而,而不是返回3(数组的长度[1,3,5]),它返回一个函数。我究竟做错了什么?


@Aadit,

我发布此内容是因为您对我的答案分享了评论以函数式方式“组合” javascript 中的函数? https://stackoverflow.com/a/30198265/633183我在那篇文章中没有具体讨论柯里化,因为这是一个非常有争议的话题,并不是我想在那里打开的一个真正的蠕虫罐头。

我会谨慎使用措辞"how to 正确地 curry"当您似乎在实施中添加自己的糖和便利时。

不管怎样,抛开所有这些,我真的不打算让这篇文章成为一个争论/好斗的帖子。我希望能够就 JavaScript 中的柯里化进行公开、友好的讨论,同时强调我们方法之间的一些差异。

无需再费周折...


澄清:

Given f是一个函数并且f.length is n. Let curry(f) be g。我们称之为g with m论据。应该发生什么?你说:

  1. If m === 0然后返回g.
  2. If m < n然后部分申请f to the m新参数,并返回一个新的柯里化函数,该函数接受剩余的n - m论据。
  3. If m === n然后申请f to the m论据。如果结果是函数,则柯里化结果。最后返回结果。
  4. If m > n然后申请f到第一个n论据。如果结果是函数,则柯里化结果。最后将结果应用到剩余的m - n参数并返回新结果。

让我们看一个代码示例@阿迪特·M·沙阿 https://stackoverflow.com/a/27996545/633183的代码实际上是

var add = curry(function(x, y) {
  return function(a, b) {
    return x + y + a + b;
  }
});

var z = add(1, 2, 3);
console.log(z(4)); // 10

这里发生了两件事:

  1. 您正在尝试支持使用可变参数调用柯里化函数。
  2. 您会自动柯里化返回的函数

我不认为这里有太多争论的余地,但人们似乎错过了什么currying http://en.wikipedia.org/wiki/Currying实际上是

通过:维基百科
在数学和计算机科学领域,currying是将带有多个参数(或参数元组)的函数的求值转换为求值函数序列的技术,每个都有一个参数...

我将最后一点加粗,因为它非常重要;序列中的每个函数只需要一个单一参数;不是像您建议的那样的可变参数(0、1 或更多)参数。

你在你的文章中也提到了 Haskell,所以我假设你知道 Haskell 不存在带有多个参数的函数这样的东西。 (注意:采用元组的函数仍然只是采用一个参数(即单个元组)的函数。其原因是深刻的,并且为您提供了具有可变参数的函数所无法提供的表达灵活性。

那么让我们重新问最初的问题:应该发生什么?

嗯,当每个函数只接受 1 个参数时,这很简单。任何时候,如果给出超过 1 个参数,它们就会被丢弃。

function id(x) {
  return x;
}

当我们打电话时会发生什么id(1,2,3,4)?当然我们只得到1回来和2,3,4被完全忽视。这是:

  1. JavaScript 是如何工作的
  2. 维基百科说柯里化应该如何运作
  3. 我们应该如何实施我们自己的curry解决方案

在我们进一步讨论之前,我将使用ES6-style 箭头函数 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions但我还将在这篇文章的底部添加 ES5 的等效项。 (可能是今晚晚些时候。)

另一种柯里化技术

在这种方法中,我们写了一个curry连续返回单参数函数,直到指定所有参数为止的函数

作为这个实施的结果,我们有6多用途功能。

// no nonsense curry
const curry = f => {
  const aux = (n, xs) =>
    n === 0 ? f (...xs) : x => aux (n - 1, [...xs, x])
  return aux (f.length, [])
}
   
// demo
let sum3 = curry(function(x,y,z) {
  return x + y + z;
});
    
console.log (sum3 (3) (5) (-1)); // 7

好的,我们已经看到了curry使用简单的辅助循环实现的技术。它没有依赖项,并且声明性定义不到 5 行代码。它允许部分应用功能,一次 1 个参数,就像柯里化函数应该起作用一样。

没有魔法,没有不可预见的自动柯里化,没有其他不可预见的后果。


但柯里化到底有什么意义呢?

好吧,事实证明,我真的不curry我写的函数。如下所示,我通常以柯里化形式定义所有可重用函数。所以真的,你只需要curry当您想要与某些您无法控制的函数进行交互时,这些函数可能来自库或其他东西;其中一些可能有可变接口!

有请curryN

// the more versatile, curryN
const curryN = n => f => {
  const aux = (n, xs) =>
    n === 0 ? f (...xs) : x => aux (n - 1, [...xs, x])
  return aux (n, [])
};

// curry derived from curryN
const curry = f => curryN (f.length) (f);

// some caveman function
let sumN = function() {
  return [].slice.call(arguments).reduce(function(a, b) {
    return a + b;
  });
};

// curry a fixed number of arguments
let g = curryN (5) (sumN);
console.log (g (1) (2) (3) (4) (5)); // 15

咖喱还是不咖喱?就是那个问题

我们将编写一些示例,其中我们的函数均采用柯里化形式。功能将保持极其简单。每一个都带有1参数,每个参数都有一个返回表达式。

// composing two functions
const comp = f => g => x => f (g (x))
const mod  = y => x => x % y
const eq   = y => x => x === y
const odd  = comp (eq (1)) (mod (2))

console.log (odd(1)) // true
console.log (odd(2)) // false

Your countWhere功能

// comp :: (b -> c) -> (a -> b) -> (a -> c)
const comp = f => g => x =>
  f(g(x))

// mod :: Int -> Int -> Int
const mod = x => y =>
  y % x

// type Comparable = Number | String
// eq :: Comparable -> Comparable -> Boolean
const eq = x => y =>
  y === x

// odd :: Int -> Boolean
const odd =
  comp (eq(1)) (mod(2))

// reduce :: (b -> a -> b) -> b -> ([a]) -> b
const reduce = f => y => ([x,...xs]) =>
  x === undefined ? y : reduce (f) (f(y)(x)) (xs)

// filter :: (a -> Boolean) -> [a] -> [a]
const filter = f =>
  reduce (acc => x => f (x) ? [...acc,x] : acc) ([])

// length :: [a] -> Int
const length = x =>
  x.length

// countWhere :: (a -> Boolean) -> [a] -> Int
const countWhere = f =>
  comp (length) (filter(f));

console.log (countWhere (odd) ([1,2,3,4,5]))
// 3

Remarks

那么咖喱还是不咖喱呢?

// to curry
const add3 = curry((a, b, c) =>
  a + b + c
)

// not to curry
const add3 = a => b => c =>
 a + b + c

随着 ES6 箭头函数成为当今 JavaScript 开发人员的首选,我认为手动柯里化函数的选择是理所当然的。它实际上更短,并且以柯里化形式写出来的开销也更少。

也就是说,您仍然需要与不提供其公开的函数的柯里化形式的库进行交互。对于这种情况,我建议

  • curry and curryN(如上定义)
  • partial (as 此处定义 https://stackoverflow.com/a/42164779/633183)

@Iven,

Your curryN实施非常好。此部分专为您而存在。

const U = f=> f (f)
const Y = U (h=> f=> f(x=> h (h) (f) (x)))

const curryN = Y (h=> xs=> n=> f=>
  n === 0 ? f(...xs) : x=> h ([...xs, x]) (n-1) (f)
) ([])

const curry = f=> curryN (f.length) (f)

const add3 = curry ((x,y,z)=> x + y + z)

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

如何在 JavaScript 中正确柯里化函数? 的相关文章

随机推荐

  • Vim 突出显示 FORTRAN 中奇怪的部分

    我正在使用 VIM 主题 molokai 如果这有什么区别的话 我最近一直在学习 FORTRAN 当我使用 VIM 编写 FORTRAN 程序时 根据我的空白 我有奇怪的颜色 例如 如果我按原样 没有缩进 进行制表符 我只会在单词的一部分上
  • Enumerable.Range - 什么时候使用才有意义?

    编程时 几乎本能地决定何时使用 for 循环或 foreach 但是选择使用 Enumerable Range 的决定因素或问题空间是什么 A For Loop当我们想要迭代一定次数 通过简单的数据类型 来计算 执行重复任务时选择 A Fo
  • 在Java编译器中,哪种类型可以定义为标识符(ID)或关键字(保留字)?

    我有一个简单的问题 在Java编译器中 哪些类型的方法或变量可以被定义为标识符 ID 或关键字 保留字 对于以下示例 ID 应为 add main a b c Test1 关于什么print is printID 或关键字 Example
  • Postgres - 返回 2 个数组的交集的函数?

    在 postgresql 中 如果两个数组具有公共成员 即它们重叠 则可以使用 运算符返回 t true 是否有一个函数 运算符可以返回这些常见成员的内容 即像这样 select arrray intersection ARRAY 1 4
  • 如何从自定义发行版中采样?

    我有一个发行版的pdf 该分布不是标准分布 R 中不存在可从中采样的函数 如何使用 R 从此 pdf 中进行采样 这更多的是一个统计问题 因为它需要采样 但一般来说 您可以采用这种方法来解决问题 查找发行版f 其 pdf 当乘以任何给定常数
  • Hibernate 中的通用 DAO 模式

    在处理 Hibernate 时 我们遵循 Hibernate Doc 中提到的通用 Hibernate DAO 模式 因此 我们目前正在维护两个并行的层次结构 1 对于接口 2 实施 因此 如果我们以这种方式工作 即使除了标准持久性方法之外
  • 如何在 xampp windows [php 7.2] 中安装/启用 GD?

    我不知道如何为 PHP7 2 安装 php gd 有没有办法在 xampp windows 中安装 启用 GD 扩展 我检查了 php ini 文件php gd2 dll但我找不到那条线 PHP7 2 中似乎缺少 GD 有什么建议么 转到
  • 是否可以使用指向参数数量未知的函数的指针?

    我正在编写一个简单的类来衡量函数在时间方面的性能 用户应该能够发送指向他的函数的指针 函数的参数 调用该函数的时间以及我将调用该函数的时间 返回经过的时间 我的问题是我不知道用户的函数需要多少个参数 我想使用可变参数函数来获取未知数量的参数
  • 运行 tf.estimator.train 100 步时,在张量板上仅看到一个步骤

    我有一个通过我自己的自定义构建的自定义估算器model fn 我想跑train并在张量板上查看每个步骤的数据点 但是 无论步骤数如何 每次调用我都只能看到一个数据点 以下是我构建和训练估算器的方法 estimator tf estimato
  • 以给定概率获取伪随机项

    我想在用户登录时给他一个奖品 但它需要有一些稀有的奖品 所以我想使用百分比以不同的机会出现奖品 我想显示其中之一 50 flower 30 book 20 mobile 使用他们拥有的百分比 如果有任何方法使用 Node js 或只是 ja
  • 我应该如何在 ECS 上设置 Traefik?

    简而言之 我已经成功跑了Traefik本地及上AWS ECS但现在我想知道应该如何设置某种负载平衡 以使我的两个具有随机 IP 的服务可供公众使用 我当前在 ECS 上的设置 Internet Load balancer on port 4
  • gcloud 未添加用于连接 GKE 集群的访问令牌

    我创建了一个 GKE 集群并使用以下命令连接到它kubectl运行针对我的集群单击 连接 按钮时出现的命令 gcloud container clusters get credentials cluster name zone us cen
  • SpringServletContainerInitializer 无法转换为 javax.servlet.ServletContainerInitializer

    我正在尝试将基于 xml 的 Spring MVC 应用程序移动到基于 Java 配置的应用程序 似乎与 maven 中可用的各种 java servlet 类不匹配 例如 有些提供 addServlet 方法 有些则不提供 这是我的配置类
  • VS 测试在管道中失败,缺少“Microsoft.NET.Test.Sdk”

    由于以下原因 我的构建失败了视觉工作室测试我的构建管道中的步骤失败 我有一个简单的 NET Core v2 1 类库和关联的 MS 测试库 我的管道有两个步骤 NET Core 构建步骤 以及 Visual Studio 测试步骤 这两个项
  • 将字符串移动到向量中

    有没有办法move将 std string 的内容转换为 std vector 我认为现在语言中有右值引用 这个操作有时会非常有用 It is 理论上可以从一种对象类型移动到另一种对象类型 然而 这些对象类型的设计必须允许这样做 vecto
  • 调整闪亮控件的标签位置

    令我惊讶的是 StackOverflow 上以前没有出现过这个问题 但无论如何 问题是 目前 标签文本 年龄范围 在此处指定 sliderInput inputId age Age Range min 32 max 99 value c 3
  • 为什么camel kafka Producer很慢?

    我使用 apache camel kafka 作为生成消息的客户端 我观察到 kafka 生产者需要 1 毫秒才能推送一条消息 如果我使用骆驼聚合将消息合并到批处理中 那么推送一条消息需要 100 毫秒 安装简述 3 kafka 集群 16
  • 仅在选定的列上使用 sklearn StandardScaler [重复]

    这个问题在这里已经有答案了 我有一个 numpy 数组 X 有 3 列 如下所示 array 3791 2629 0 1198760 113989 0 4120665 0 1 前 2 列是连续值 最后一列是二进制 0 1 我想仅将 Stan
  • 最快的 iPhone Blit 例程?

    我有一个UIView我需要将其位块传送到的子类UIImage 有多种方法可以给这只猫剥皮 具体取决于您喜欢使用哪个系列的 API 我对最快的感兴趣 可不可能是UIImage s drawAtPoint or drawRect 或者也许是基于
  • 如何在 JavaScript 中正确柯里化函数?

    我写了一个简单的curryJavaScript 中的函数在大多数情况下都能正常工作 const curry f a gt a length lt f length b gt curry f a b f a const add curry a