JavaScript咖喱:有哪些实际应用?

时间:2008-09-22 08:22:06

标签: javascript function currying partial-application

我不认为我已经讨好了。我理解它的作用,以及如何做到这一点。我想不出我会用它的情况。

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

One of the answers提及动画。像slideUpfadeIn这样的函数将一个元素作为参数,通常是一个curried函数,返回高阶函数,内置默认的“动画函数”。为什么这比使用某些默认值更高的函数更好?

使用它有什么缺点吗?

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

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


因此,根据答案,currying和部分应用一般是便利技术。

如果您经常通过使用相同的配置调用高级函数来“精炼”高级函数,则可以使用更高级别的函数来设置(或使用Resig的部分)来创建简单,简洁的辅助方法。

16 个答案:

答案 0 :(得分:112)

这是interesting AND practical use of currying in JavaScript that uses closures

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的{​​{1}}扩展名,但正如您所看到的那样只使用Function(没什么太花哨的):

apply

答案 1 :(得分:33)

@Hank Gay

回应EmbiggensTheMind的评论:

我想不出一个实例currying - 本身 - 在JavaScript中很有用;它是一种将具有多个参数的函数调用转换为函数调用链的技术,每个调用都有一个参数,但JavaScript在单个函数调用中支持多个参数。

在JavaScript中 - 我假设大多数其他实际语言(不是lambda演算) - 但通常与部分应用程序相关联。 John Resig explains it better,但要点是有一些逻辑可以应用于两个或多个参数,而你只知道其中一些参数的值。

您可以使用部分应用程序/ 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" );

答案 2 :(得分: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绑定到值:

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

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

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

答案 3 :(得分:5)

同意Hank Gay - 它在某些真正的函数式编程语言中非常有用 - 因为它是必要的部分。例如,在Haskell中,您根本无法将多个参数带到函数中 - 您无法在纯函数式编程中执行此操作。你一次拿一个参数并建立你的功能。在JavaScript中,尽管有像“转换器”这样的人为例子,但它根本就没有必要。这是相同的转换器代码,无需curry:

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”中提到过一些关于currying的历史和实际用法的内容,而不是他的无言的言论。在读完之后的最长时间里,我感到很困惑,直到我学习功能编程并意识到它来自哪里。

经过一番思考之后,我认为在JavaScript中有一个有效的用例:如果你正在尝试使用JavaScript编写纯函数式编程技术。看起来像是一个罕见的用例。

答案 4 :(得分:2)

这不是魔术或任何东西......只是匿名函数的一个愉快的简写。

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

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

对部分(MochiKit术语的调用。我认为其他一些库给函数一个.curry方法做同样的事情)看起来比匿名函数更好,噪音更小。

答案 5 :(得分:2)

答案 6 :(得分:1)

至于使用它的图书馆,总是Functional

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

答案 7 :(得分:1)

我想说,最有可能的是,JS中的所有动画库都在使用currying。而不是必须为每个调用传递一组受影响的元素和一个函数,描述元素应该如何表现,而不是传递给更高阶函数,以确保所有的时序,它通常更容易让客户发布,作为公共API一些像“slideUp”,“fadeIn”这样的函数只接受元素作为参数,而这只是一些curried函数,它返回高阶函数,内置默认的“动画函数”。

答案 8 :(得分:1)

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

答案 9 :(得分:1)

我知道它的旧线程,但我必须说明如何在javascript库中使用它:

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

示例:

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

部分申请:

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

Currying:

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!

差:

在curry之后,我们得到一个没有参数预绑定的新函数。

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

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

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

答案 10 :(得分:0)

我同意有时候你想通过创建一个总是有第一个参数值填充的伪函数来推动滚动。幸运的是,我遇到了一个名为jPaq的全新JavaScript库(h { {3}})提供此功能。关于该库的最好的事情是,您可以下载自己的构建,其中只包含您需要的代码。

答案 11 :(得分:0)

我刚刚编写了一个jPaq示例,其中显示了curry函数的一些很酷的应用程序。请在此处查看:Currying Up String Functions

答案 12 :(得分:0)

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

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

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

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

此外,如果性能成为一个问题(但这个lib非常轻松),使用本机函数重写很容易。

答案 13 :(得分: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

答案 14 :(得分:0)

通过与承诺合作,再次抨击它。

(免责声明:JS noob,来自Python世界。即使在那里, 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);
}

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

我仍然只能在编码时获得信息 - 在我的情况下,我有许多可能的批次,但我不知道哪一个失败了w.o.解析有关失败网址的服务器响应。

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

现在,让我们稍微改变一下并使用可重用的通用失败处理程序,但也可以在运行时使用已知的代码时调用上下文和运行时 curry 从事件中获得的信息。

    ... 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

答案 15 :(得分:0)

我在https://softwareengineering.stackexchange.com/questions/384529/a-real-life-example-of-using-curry-function

问了类似的问题

但是只有在我使用ramda之后,我才最终意识到咖喱的用处。因此,我将辩称,如果我们需要将功能链接在一起以一次一步地处理某些输入数据,例如Favoring Curry中的Promise链示例,通过“功能优先,数据最后”使用咖喱,代码看起来确实干净!