181

我想我还没学会咖喱。我明白它的作用,以及如何去做。我只是想不出我会使用它的情况。

你在哪里使用 JavaScript 中的柯里化(或者主要库在哪里使用它)?欢迎使用 DOM 操作或一般应用程序开发示例。

其中一个答案提到了动画。类似的函数slideUpfadeIn将元素作为参数,通常是一个柯里化函数,返回内置默认“动画函数”的高阶函数。为什么这比仅仅应用带有一些默认值的更高层函数更好?

使用它有什么缺点吗?

根据这里的要求,有一些关于 JavaScript currying 的好资源:

当他们在评论中出现时,我会添加更多内容。


因此,根据答案,一般来说,柯里化和部分应用是便利技术。

如果您经常通过使用相同配置调用高级函数来“优化”高级函数,则可以对高级函数进行 curry(或使用 Resig 的部分)来创建简单、简洁的辅助方法。

4

17 回答 17

115

这是使用闭包的 JavaScript 中柯里化的一个有趣且实用的用法

function converter(toUnit, factor, offset, input) {
    offset = offset || 0;
    return [((offset + input) * factor).toFixed(2), toUnit].join(" ");
}

var milesToKm = converter.curry('km', 1.60936, undefined);
var poundsToKg = converter.curry('kg', 0.45460, undefined);
var farenheitToCelsius = converter.curry('degrees C', 0.5556, -32);

milesToKm(10);            // returns "16.09 km"
poundsToKg(2.5);          // returns "1.14 kg"
farenheitToCelsius(98);   // returns "36.67 degrees C"

这依赖于 的curry扩展Function,尽管您可以看到它只使用apply(没什么花哨的):

Function.prototype.curry = function() {
    if (arguments.length < 1) {
        return this; //nothing to curry with - return function
    }
    var __method = this;
    var args = toArray(arguments);
    return function() {
        return __method.apply(this, args.concat([].slice.apply(null, arguments)));
    }
}
于 2011-07-28T15:49:08.027 回答
37

@汉克盖伊

回应 EmbiggensTheMind 的评论:

我想不出一个在 JavaScript中单独使用柯里化的例子。它是一种将具有多个参数的函数调用转换为每个调用具有单个参数的函数调用链的技术,但 JavaScript 支持在单个函数调用中使用多个参数。

不过,在 JavaScript 中——我假设大多数其他实际语言(不是 lambda 演算)——它通常与部分应用程序相关联。John Resig更好地解释它,但要点是有一些逻辑将应用于两个或多个参数,并且您只知道其中一些参数的值。

您可以使用部分应用程序/currying 来修复那些已知值并返回一个仅接受未知数的函数,以便稍后在您实际拥有希望传递的值时调用。这提供了一种绝妙的方法来避免重复自己,因为您将一遍又一遍地使用所有相同的值调用相同的 JavaScript 内置函数,但只有一个。窃取约翰的例子:

String.prototype.csv = String.prototype.split.partial(/,\s*/);
var results = "John, Resig, Boston".csv();
alert( (results[1] == "Resig") + " The text values were split properly" );
于 2008-09-22T09:47:20.220 回答
8

同意 Hank Gay - 它在某些真正的函数式编程语言中非常有用 - 因为它是必要的部分。例如,在 Haskell 中,您根本无法将多个参数传递给一个函数 - 在纯函数式编程中您无法做到这一点。您一次获取一个参数并建立您的功能。在 JavaScript 中它是完全没有必要的,尽管像“转换器”这样人为的例子。这是相同的转换器代码,无需柯里化:

var converter = function(ratio, symbol, input) {
    return (input*ratio).toFixed(2) + " " + symbol;
}

var kilosToPoundsRatio = 2.2;
var litersToUKPintsRatio = 1.75;
var litersToUSPintsRatio = 1.98;
var milesToKilometersRatio = 1.62;

converter(kilosToPoundsRatio, "lbs", 4); //8.80 lbs
converter(litersToUKPintsRatio, "imperial pints", 2.4); //4.20 imperial pints
converter(litersToUSPintsRatio, "US pints", 2.4); //4.75 US pints
converter(milesToKilometersRatio, "km", 34); //55.08 km

我非常希望 Douglas Crockford 在“JavaScript: The Good Parts”中提到了柯里化的历史和实际使用,而不是他的随意评论。读完之后很长一段时间里,我都感到困惑,直到我学习函数式编程并意识到这就是它的来源。

经过一番思考,我认为 JavaScript 中的柯里化有一个有效的用例:如果您尝试使用 JavaScript 使用纯函数式编程技术进行编写。不过,这似乎是一个罕见的用例。

于 2015-09-03T15:25:47.820 回答
7

我发现类似于 python 的函数functools.partial在 JavaScript 中更有用:

function partial(fn) {
  return partialWithScope.apply(this,
    Array.prototype.concat.apply([fn, this],
      Array.prototype.slice.call(arguments, 1)));
}

function partialWithScope(fn, scope) {
  var args = Array.prototype.slice.call(arguments, 2);
  return function() {
    return fn.apply(scope, Array.prototype.concat.apply(args, arguments));
  };
}

你为什么要使用它?您想要使用 this 的常见情况是当您想要将this函数绑定到值时:

var callback = partialWithScope(Object.function, obj);

现在当回调被调用时,this指向obj. 这在事件情况下或节省一些空间时很有用,因为它通常会使代码更短。

Currying 类似于 partial,不同之处在于 currying 返回的函数只接受一个参数(据我所知)。

于 2008-09-22T08:49:04.527 回答
2

这不是魔法或任何东西......只是匿名函数的一种令人愉快的简写。

partial(alert, "FOO!")相当于function(){alert("FOO!");}

partial(Math.max, 0)对应于function(x){return Math.max(0, x);}

对 partial 的调用(MochiKit术语。我认为其他一些库为函数提供了一个 .curry 方法,它做同样的事情)看起来比匿名函数稍微好一点,噪音也更少。

于 2008-09-22T08:46:53.783 回答
2

我知道它的旧线程,但我将不得不展示它是如何在 javascript 库中使用的:

我将使用 lodash.js 库来具体描述这些概念。

例子:

var fn = function(a,b,c){ 
return a+b+c+(this.greet || ‘'); 
}

部分应用:

var partialFnA = _.partial(fn, 1,3);

咖喱:

var curriedFn = _.curry(fn);

捆绑:

var boundFn = _.bind(fn,object,1,3 );//object= {greet: ’!'}

用法:

curriedFn(1)(3)(5); // gives 9 
or 
curriedFn(1,3)(5); // gives 9 
or 
curriedFn(1)(_,3)(2); //gives 9


partialFnA(5); //gives 9

boundFn(5); //gives 9!

不同之处:

在柯里化之后,我们得到了一个没有预先绑定参数的新函数。

在部分应用之后,我们得到一个与一些预先绑定的参数绑定的函数。

在绑定中,我们可以绑定一个用于替换“this”的上下文,如果没有绑定任何函数的默认值将是窗口范围。

忠告:没有必要重新发明轮子。部分应用程序/绑定/currying 非常相关。你可以看到上面的区别。在任何地方使用这个含义,人们都会认识到你在做什么,而不会出现理解问题,而且你将不得不使用更少的代码。

于 2015-07-22T10:39:46.840 回答
2

考虑filter功能。你想为它写一个回调。

let x = [1,2,3,4,5,6,7,11,12,14,15];
let results = x.filter(callback);

假设只想输出偶数,所以:

let callback = x => x % 2 === 0;

现在想象我们想要实现我们的callback这样,根据场景它输出高于某个阈值的偶数(这个数字应该是可配置的)。

我们不能轻易地将这样的阈值数作为callback函数的参数,因为filter调用callback并默认传递数组元素和索引。

你将如何实现这一点?

这是一个很好的柯里化用例:

let x = [1,2,3,4,5,6,7,11,12,14,15];
let callback = (threshold) => (x) => (x % 2==0 && x > threshold);

let results1 = x.filter(callback(5)); // Even numbers higher than 5
let results2 = x.filter(callback(10)); // Even numbers higher than 10

console.log(results1,results2);

于 2021-07-29T17:47:35.980 回答
1

至于使用它的库,总是有Functional

什么时候在 JS 中有用?可能同时它在其他现代语言中也很有用,但我唯一能看到自己使用它的时候是与部分应用程序结合使用。

于 2008-09-22T08:26:22.443 回答
1

我会说,很可能,JS 中的所有动画库都在使用柯里化。不必为每次调用传递一组受影响的元素和一个函数,描述元素的行为方式,将其传递给一个更高阶的函数,以确保所有时间的东西,它通常更容易让客户发布,因为公共 API 一些像“slideUp”、“fadeIn”这样的函数,它只接受元素作为参数,这只是一些返回高阶函数的柯里化函数,内置了默认的“动画函数”。

于 2008-09-22T08:27:38.947 回答
1

这是一个例子。

我正在使用 JQuery 检测一堆字段,以便查看用户在做什么。代码如下所示:

$('#foo').focus(trackActivity);
$('#foo').blur(trackActivity);
$('#bar').focus(trackActivity);
$('#bar').blur(trackActivity);

(对于非 JQuery 用户,我的意思是每当几个字段获得或失去焦点时,我希望调用 trackActivity() 函数。我也可以使用匿名函数,但我必须复制它4次,所以我把它拔出来并命名。)

现在事实证明,其中一个字段需要以不同的方式处理。我希望能够在其中一个调用中传递一个参数,以传递给我们的跟踪基础设施。使用咖喱,我可以。

于 2010-07-23T22:01:54.813 回答
1

JavaScript 函数在其他函数式语言中称为 lamda。它可用于根据其他开发人员的简单输入来编写新的 api(更强大或更复杂的功能)。咖喱只是其中一种技术。你可以用它来创建一个简化的api来调用一个复杂的api。如果你是使用简化 api 的开发者(例如你使用 jQuery 做简单的操作),你不需要使用 curry。但是如果你想创建简化的 api,curry 是你的朋友。您必须编写一个 javascript 框架(如 jQuery、mootools)或库,然后您才能体会到它的强大功能。我在http://blog.semanticsworks.com/2011/03/enhanced-curry-method.html写了一个增强的咖喱函数. 你不需要 curry 方法来做柯里化,它只是帮助做柯里化,但你总是可以通过编写函数 A(){} 来手动完成,以返回另一个函数 B(){}。为了让它更有趣,使用函数 B() 来返回另一个函数 C()。

于 2011-03-20T18:52:10.900 回答
0

我同意,有时您希望通过创建一个始终填充第一个参数的值的伪函数来让事情顺利进行。幸运的是,我遇到了一个名为 jPaq 的全新 JavaScript 库(h ttp :// jpaq.org/ ),它提供了这个功能。该库最好的一点是您可以下载自己的构建,其中仅包含您需要的代码。

于 2011-02-07T16:45:52.020 回答
0

我刚刚写了一个 jPaq 示例,它展示了 curry 函数的一些很酷的应用。在这里查看: Currying Up String Functions

于 2011-04-07T23:04:47.097 回答
0

只是想为 Functional.js 添加一些资源:

讲座/会议解释一些应用程序 http://www.youtube.com/watch?v=HAcN3JyQoyY

更新了 Functional.js 库: https ://github.com/loop-recur/FunctionalJS 一些不错的帮手(对不起,这里没有声誉:p):/loop-recur/PreludeJS

我最近一直在使用这个库来减少 js IRC 客户端帮助库中的重复。这是很棒的东西——真的有助于清理和简化代码。

此外,如果性能成为问题(但这个库很轻),只需使用本机函数重写就很容易。

于 2013-06-01T18:21:00.497 回答
0

您可以使用本机绑定进行快速的单行解决方案

function clampAngle(min, max, angle) {
    var result, delta;
    delta = max - min;
    result = (angle - min) % delta;
    if (result < 0) {
        result += delta;
    }
    return min + result;
};

var clamp0To360 = clampAngle.bind(null, 0, 360);

console.log(clamp0To360(405)) // 45

于 2015-08-20T11:17:03.927 回答
0

另一个尝试,来自承诺。

(免责声明:JS noob,来自 Python 世界。即使在那里,currying也没有被太多使用,但它有时会派上用场。所以我抄袭了 currying 函数 - 见链接)

首先,我从一个 ajax 调用开始。我对成功有一些特定的处理,但在失败时,我只想给用户反馈,调用某些东西会导致一些错误。在我的实际代码中,我在引导面板中显示错误反馈,但我只是在这里使用日志记录。

我已经修改了我的实时网址以使其失败。

function ajax_batch(e){
    var url = $(e.target).data("url");

    //induce error
    url = "x" + url;

    var promise_details = $.ajax(
        url,
        {
            headers: { Accept : "application/json" },
            // accepts : "application/json",
            beforeSend: function (request) {
                if (!this.crossDomain) {
                    request.setRequestHeader("X-CSRFToken", csrf_token);
                }
        },
        dataType : "json",
        type : "POST"}
    );
    promise_details.then(notify_batch_success, fail_status_specific_to_batch);
}

现在,为了告诉用户批处理失败,我需要在错误处理程序中写入该信息,因为它得到的只是来自服务器的响应。

我仍然只有在编码时可用的信息 - 就我而言,我有许多可能的批次,但我不知道哪个批次失败了,因为解析服务器响应有关失败的 url。

function fail_status_specific_to_batch(d){
    console.log("bad batch run, dude");
    console.log("response.status:" + d.status);
}

我们开始做吧。控制台输出为:

安慰:

bad batch run, dude utility.js (line 109) response.status:404

现在,让我们稍微改变一下,使用一个可重用的通用故障处理程序,但也是一个在运行时使用已知代码时调用上下文和从事件中可用的运行时信息进行柯里化的处理程序。

    ... rest is as before...
    var target = $(e.target).text();
    var context = {"user_msg": "bad batch run, dude.  you were calling :" + target};
    var contexted_fail_notification = curry(generic_fail, context); 

    promise_details.then(notify_batch_success, contexted_fail_notification);
}

function generic_fail(context, d){
    console.log(context);
    console.log("response.status:" + d.status);
}

function curry(fn) {
     var slice = Array.prototype.slice,
        stored_args = slice.call(arguments, 1);
     return function () {
        var new_args = slice.call(arguments),
              args = stored_args.concat(new_args);
        return fn.apply(null, args);
     };
}

安慰:

Object { user_msg="bad batch run, dude. you were calling :Run ACL now"} utility.js (line 117) response.status:404 utility.js (line 118)

更一般地说,考虑到回调在 JS 中的广泛使用,currying 似乎是一个非常有用的工具。

https://javascriptweblog.wordpress.com/2010/04/05/curry-cooking-up-tastier-functions/ http://www.drdobbs.com/open-source/currying-and-partial-functions-in- javasc/231001821?pgno=2

于 2016-04-08T16:56:47.713 回答
0

我在https://softwareengineering.stackexchange.com/questions/384529/a-real-life-example-of-using-curry-function上问了一个类似的问题

但是只有在我使用了 ramda之后,我才终于体会到了 curry 的用处。所以我认为,如果我们需要将函数链接在一起以一次处理一些输入数据,例如文章Favoring Curry中的 Promise 链示例,通过“函数优先,数据最后”使用 curry,代码看起来确实很干净!

于 2020-12-07T10:30:57.700 回答