JavaScript库里:实际应用是什么?


172

我认为我还没有抱怨过。我了解它的作用以及如何去做。我只是想不出我会用它的情况。

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

答案之一是动画。像功能slideUpfadeIn需要一个元素作为参数,并通常是一个咖喱函数返回带有内置默认的“动画功能”高阶功能。为什么这比仅应用带有某些默认值的upper-up功能更好?

使用它有什么缺点吗?

根据要求,这里提供了一些有关JavaScript currying的良好资源:

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


因此,根据答案,通常使用currying和部分应用是便捷技术。

如果您经常通过使用相同的配置调用它来“完善”一个高级函数,则可以使用(或使用Resig的partial)高级函数来创建简单,简洁的帮助器方法。


您是否可以添加指向描述JS currying是什么的资源的链接?教程或博客文章会很棒。
Eric Sc​​hoonover,

2
svendtofte.com充满了希望,但是如果您从“ ML速成班”中跳过整个部分,而再次从“如何编写咖喱JavaScript”开始,它将成为js中的精妙介绍。
danio

1
这是了解什么是咖喱和部分应用的真正好起点:slid.es/gsklee/functional-programming-in-5-minutes
gsklee 2013年

1
指向该链接的链接svendtofte.com似乎已失效-尽管在web.archive.org/web/20130616230053/http://www.svendtofte.com/上的WayBack机器上找到了该链接… 很抱歉,blog.morrisjohns.com / javascript_closures_for_dummies似乎已关闭太
phatskat 2014年

1
顺便说一句,Resig的partial版本存在缺陷(肯定不是“花钱”),因为如果给预初始化(“ curried”)参数之一赋予undefined值,它可能会失败。任何对良好的currying函数感兴趣的人都应该从Oliver Steele的funcitonal.js获取原始文件,因为它没有这个问题。
RobG

Answers:


35

@汉克·盖伊

回应EmbiggensTheMind的评论:

我想不出一个实例,在JavaScript中,currying本身很有用;它是一种将具有多个参数的函数调用转换为每个调用具有单个参数的函数调用链的技术,但是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" );


6
这确实是一个错误的答案。咖喱与部分应用无关。咖喱使功能组合。功能组合可实现功能重用。重用功能可提高代码的可维护性。就这么简单!

2
@ftor先生,您的回答很不好。咖喱显然是要使功能更美味。您显然错过了重点。
卡勒特

112

是使用闭包的JavaScript中有趣的,实用的curring用法

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)));
    }
}

5
这很棒!我看到它类似于说“ Lisp是一种可编程编程语言”的lisp引用
santiagobasulto

2
有趣,但是此示例似乎不起作用。offset+inputundefined + 1.60936在您的milesToKm示例中;结果NaN
内森·朗

3
@Nathan-偏移量不能不确定-默认为0
AngusC

6
根据我所读的内容(现在),除非您使用Prototype库或自己添加它,否则“ curry”通常不是Function的技巧包中的一部分。不过非常酷。
Roboprog

11
可以使用ES5 bind()方法获得相同的结果。Bind创建一个新函数,该函数在被调用时会使用其第一个参数的上下文以及后续参数序列(在传递给新函数的任何参数之前)调用原始函数。所以你可以做... var milesToKm = converter.bind(this,'km',1.60936); 或var farenheitToCelsius = converter.bind(this,'degrees C',0.5556,-32); 第一个参数,上下文,在这里是不相关的,因此您可以传递未定义的值。当然,您需要使用自己的bind方法来扩展基本Function原型,以实现非ES5后备广告
hacklikecrack 2012年

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类似于局部的,区别在于currying返回的函数只接受一个参数(据我所知)。


7

同意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:好的零件”一书中提到过Curry的历史和实际用法,而不是他的副言。读完这篇文章后的最长时间,我感到困惑,直到我学习函数式编程并意识到那是它的来历。

经过一番思考之后,我假定有一个使用JavaScript的有效用例:如果您尝试使用纯函数式编程技术来编写JavaScript。不过,这似乎是一种罕见的用例。


2
您的代码比Prisoner Zero的代码更容易理解,并且可以解决相同的问题,而不会引起麻烦或其他麻烦。你有2个大拇指,而他有近100个大拇指。
DR01D

2

这不是魔术,也不是任何东西……只是匿名函数的简捷速记。

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

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

局部调用(MochiKit术语。我认为其他一些库为函数提供了一个.curry方法,它执行相同的功能)看起来比匿名函数好一些,也没有那么多噪音。


1

至于使用它的库,总是存在Functional

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


谢谢Hank-请问您一般情况下什么时候有用?
戴夫·诺兰

1

我想说,很可能,JS中的所有动画库都使用currying。不必每次调用都传递一组受影响的元素和一个函数(描述元素的行为方式),而是传递给一个高阶函数,该函数将确保所有计时内容,通常,客户可以更容易地将其作为公共API发布。像“ slideUp”,“ fadeIn”之类的函数,仅将元素作为参数,并且是一些咖喱函数,它们返回默认内置的“动画函数”的高阶函数。


为什么最好使用curryup函数而不是简单地使用一些默认值来调用它呢?
戴夫·诺兰

1
因为它具有更高的模块化程度,因此能够根据需要使用加法/乘法/平方/模数/其他模数来简化“ doMathOperation”操作,而不是想象更高功能可以支持的所有“默认”操作。
gizmo

1

这是一个例子。

我正在使用JQuery来检测一堆字段,以便了解用户的需求。代码如下:

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

(对于非JQuery用户,我是说,每当有几个字段获得焦点或失去焦点时,我都希望调用trackActivity()函数。我也可以使用匿名函数,但是我必须复制它4次,因此我将其拔出并命名。)

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


1

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


1

我知道它的旧线程,但是我将不得不展示如何在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!

区别:

经过curring之后,我们得到了一个没有参数上限的新函数。

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

在绑定中,我们可以绑定一个上下文,该上下文将用于替换“ this”,如果未绑定,则任何功能的默认值都是窗口范围。

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


0

我同意,有时您可能想通过创建一个始终填充第一个参数值的伪函数来使事情如火如荼。幸运的是,我遇到了一个全新的JavaScript库,名为jPaq(h ttp :// jpaq.org/)提供了此功能。关于该库的最好的事情是您可以下载自己的内部版本,其中仅包含所需的代码。



0

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

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

更新的Functional.js库:https : //github.com/loop-recur/FunctionalJS 一些不错的助手(很抱歉,这里没有信誉,p :):/ loop-recur / PreludeJS

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

另外,如果性能成为问题(但是这个库很轻),则可以使用本机函数轻松重写。


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


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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.