JavaScript curry: 实际应用是什么?

我想我还没吃过咖喱。我知道它是干什么的,也知道怎么做。我只是想不出有什么情况我会用它。

在 JavaScript 中您在哪里使用了局部套用(或者主库在哪里使用了它) ?欢迎使用 DOM 操作或通用应用程序开发示例。

其中一个答案提到了动画。像 slideUpfadeIn这样的函数接受一个元素作为参数,通常是一个 curry 函数,返回内置默认“动画函数”的高阶函数。为什么这比只应用具有一些默认值的 high-up 函数要好呢?

使用它有什么缺点吗?

按照要求,这里有一些关于 JavaScript 套用的好资源:

我会在评论中添加更多内容。


因此,根据答案,咖喱和局部应用一般是方便的技术。

如果您经常通过使用相同的配置调用高级函数来“精炼”它,那么您可以粗略(或使用 Resig 的部分)高级函数来创建简单、简洁的 helper 方法。

46920 次浏览

至于使用它的库,总是有 功能性的

它在 JS 中什么时候有用?也许在其他现代语言中它也同样有用,但我只能在部分应用程序中使用它。

我想说的是,很可能 JS 中的所有动画库都使用了局部套用。不必为每个调用传递一组受影响的元素和函数,描述元素应该如何行为,传递给一个更高阶的函数,这个函数将确保所有的计时工作,通常对客户来说更容易发布,作为公共 API,一些像“ slideUp”,“ fadeIn”这样的函数只接受元素作为参数,这些函数只是一些用默认的“动画函数”内置的高阶函数返回的局部函数。

这不是什么神奇的东西,只是匿名函数的一个简称。

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

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

对 part (莫奇基特术语)的调用。我认为其他一些库给函数一个。Curry 方法,它做同样的事情)看起来比匿名函数好一点,也没有那么吵。

我发现类似 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绑定到一个值:

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

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

局部套用类似于局部套用,不同之处在于局部套用返回的函数只接受一个参数(据我所知)。

@ Hank Gay

对于 EmbiggensTheMind 的评论:

我想不出有哪个实例的 咖喱ーー单独使用ーー在 JavaScript 中是有用的; 它是一种技术,用于将包含多个参数的函数调用转换为包含每个调用的单个参数的函数调用链,但 JavaScript 在单个函数调用中支持多个参数。

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

您可以使用部分应用程序/局部套用来修复那些已知值,并返回一个只接受未知值的函数,以便稍后当您实际拥有希望传递的值时调用。这提供了一种巧妙的方法,可以避免重复调用相同的 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" );

举个例子。

我使用 JQuery 检测了一些字段,这样我就可以看到用户在做什么。代码如下:

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

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

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

我同意,有时候您希望通过创建一个伪函数来开始工作,这个伪函数将始终填充第一个参数的值。幸运的是,我遇到了一个全新的名为 jPaq (hHttp://jpaq.org/)的 JavaScript 库,它提供了这种功能。这个库最好的一点是,您可以下载自己的构建,其中只包含您需要的代码。

JavaScript 函数在其他函数语言中称为 lamda。它可以用来构建一个新的 api (更强大或更复杂的函数) ,以基于另一个开发人员的简单输入。咖喱只是技巧之一。您可以使用它来创建一个简化的 api 来调用一个复杂的 api。如果您是使用简化的 api 的开发人员(例如,您使用 jQuery 进行简单的操作) ,则不需要使用 curry。但是如果您想创建简化的 api,curry 是您的朋友。你必须编写一个 javascript 框架(像 jQuery,mootools)或者库,然后你才能欣赏它的力量。我在 http://blog.semanticsworks.com/2011/03/enhanced-curry-method.html写了一个增强的 curry 函数。您不需要通过 curry 方法来进行局部套用,它只是帮助进行局部套用,但是您总是可以通过编写函数 A (){}来返回另一个函数 B (){}来手动进行此操作。为了使它更有趣,使用函数 B ()返回另一个函数 C ()。

这是 在 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"

这依赖于 Functioncurry扩展,尽管你可以看到它只使用 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)));
}
}

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

解释一些应用程序的演讲/会议 Http://www.youtube.com/watch?v=hacn3jyqoyy

更新的 Functional.js 库: Https://github.com/loop-recur/functionaljs 一些好的助手(抱歉,这里是新的,没有声誉: p) : /loop-recur/PreludeJS

我最近经常使用这个库来减少 jsIRC 客户机助手库中的重复。这是很好的东西-真的有助于清理和简化代码。

另外,如果性能成为一个问题(但是这个 lib 非常简单) ,那么使用本机函数重写就很容易了。

我知道它的老线程,但是我将不得不展示如何在 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”,如果没有绑定,任何函数的默认值都将是窗口范围。

建议: 没有必要重新发明轮子。部分应用程序/绑定/咖喱是密切相关的。你可以看到上面的区别。在任何地方使用这个意思,人们都会认识到你在做什么,而且你不得不使用更少的代码。

可以使用本机绑定实现快速、单行解决方案

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

同意 Hank Gay 的观点——它在某些真正的函数式编程语言中非常有用——因为它是必不可少的一部分。例如,在哈斯克尔,你不能对一个函数使用多个参数——在纯函数式编程中你不能这样做。每次只取一个参数,构建函数。在 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

我非常希望道格拉斯·克罗克福特在《 JavaScript: 好的部分》(JavaScript: The Good Part)一书中提到 curry 的历史和实际用法,而不是他的随意评论。在读完这篇文章之后的很长一段时间里,我都感到困惑,直到我开始学习函数式编程,并意识到这就是它的由来。

经过深思熟虑之后,我假定 JavaScript 中有一个有效的套用用例: 如果您试图使用纯函数式编程技术使用 JavaScript 编写代码。不过看起来是个罕见的用例。

另一个尝试,从工作与承诺。

(免责声明: JS noob,来自 Python 世界。即使在那里,咖喱也不是经常使用,但它有时也会派上用场。所以我抄袭了套用函数-参见链接)

首先,我从一个 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);
}

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

我仍然只有在编码时可用的信息-在我的情况下,我有一个可能的批次,但我不知道哪一个已经失败。解析服务器响应失败的网址。

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

开始吧,控制台输出是:

控制台:

批量生产失败了,伙计 Js (第109行) 状态: 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);
};
}

控制台:

对象{ user _ msg = “追加错误批处理运行,伙计。您正在调用: Run ACL now”} Js (第117行) 回复,状态: 404 Js (第118行)

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

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

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

但是只有在我使用 Ramda之后,我才最终意识到咖喱的有用性。因此,我认为,如果我们需要链接函数一起处理一些输入数据一次一步,例如在文章 喜欢咖喱中的承诺链示例,使用 curry“函数优先,数据最后”,代码看起来干净!

考虑一下 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);

在这里你有一个实际的例子,我们正在使用咖喱目前。 Https://www.joshwcomeau.com/react/demystifying-styled-components/

基本上,他正在创建一个穷人样式的组件,并使用局部套用来“预加载”标记的名称时,为它创建一个新的样式。