JavaScript köri: pratik uygulamalar nelerdir?


172

Henüz köreldiđimi sanmýyorum. Ne yaptığını ve nasıl yapılacağını anlıyorum. Kullanacağım bir durumu düşünemiyorum.

JavaScript'te köriyi nerede kullanıyorsunuz (veya ana kütüphaneler nerede kullanıyor?) DOM manipülasyonu veya genel uygulama geliştirme örnekleri hoş geldiniz.

Cevaplardan biri animasyondan bahsediyor. Gibi fonksiyonlar slideUp, fadeInbir argüman olarak bir öğe almak ve normal yerleşik varsayılan “animasyon fonksiyonu” ile yüksek mertebeden fonksiyonunu dönen bir Körili fonksiyonu vardır. Neden bazı varsayılanlarla daha üstteki işlevi uygulamaktan daha iyi?

Kullanmanın sakıncaları var mı?

Burada istendiği gibi JavaScript curry ile ilgili bazı iyi kaynaklar:

Yorumlarda kırpıldıkça daha fazlasını ekleyeceğim.


Bu nedenle, cevaplara göre, genel olarak körelme ve kısmi uygulama kolaylık teknikleridir.

Bir üst düzey işlevi sık sık aynı yapılandırmayla çağırarak "hassaslaştırıyorsanız", basit, özlü yardımcı yöntemler oluşturmak için üst düzey işlevi köri yapabilir (veya Resig'in kısmi kullanabilirsiniz).


JS kıvrımının ne olduğunu açıklayan bir kaynağa bağlantı ekleyebilir misiniz? bir öğretici veya bir blog gönderisi harika olurdu.
Eric Schoonover

2
svendtofte.com uzun soluklu ama tüm bölümü "ML'de bir kilitlenme kursu" 'ndan atlayıp "Curried JavaScript nasıl yazılır" dan başlarsanız, js'de köriliğe büyük bir giriş olur.
danio

1
Bu, köri ve kısmi uygulamanın gerçekte ne olduğunu anlamak için iyi bir başlangıç ​​noktasıdır: slid.es/gsklee/functional-programming-in-5-minutes
gsklee

1
svendtofte.comÖlü görünüyor bağlantısı - web.archive.org/web/20130616230053/http://www.svendtofte.com/… adresinde olsa WayBack makinesinde buldum Üzgünüz, blog.morrisjohns.com/javascript_closures_for_dummies çalışmıyor çok
phatskat

1
BTW, Resig'in kısmi versiyonu eksiktir (kesinlikle "parada değil"), önceden başlatılmış ("curried") argümanlardan birine undefined değeri verilirse başarısız olacaktır . İyi bir köri işlevi ile ilgilenen herkes orijinali Oliver Steele'nin funcitonal.js'den almalıdır , çünkü bu problemi yoktur.
RobG

Yanıtlar:


35

Eşcinsel

EmbiggensTheMind'in yorumuna yanıt olarak:

JavaScript'te köpürmenin faydalı olduğu bir örneği düşünemiyorum ; birden çok bağımsız değişkeni olan işlev çağrılarını, her çağrı için tek bir bağımsız değişkeni olan işlev çağrıları zincirine dönüştürme tekniğidir, ancak JavaScript tek bir işlev çağrısında birden çok bağımsız değişkeni destekler.

JavaScript'te —ve diğer gerçek dillerin çoğunu varsayıyorum (lambda calculus değil) - genellikle kısmi uygulama ile ilişkilidir. John Resig daha iyi açıklıyor , ama özü o iki veya daha fazla bağımsız değişken uygulanacak bazı mantık var olduğunu ve sadece bu tartışmaların bazıları için bir değer (ler) i biliyorum.

Bilinen bu değerleri düzeltmek ve yalnızca bilinmeyenleri kabul eden bir işlevi döndürmek için kısmi uygulama / köri kullanabilirsiniz, daha sonra geçmek istediğiniz değerlere sahip olduğunuzda daha sonra çağrılmak üzere. Bu, aynı JavaScript yerleşiklerini aynı değerlerle aynı değerlerle tekrar tekrar çağırırken kendinizi tekrar etmekten kaçınmak için şık bir yol sağlar. John'un örneğini çalmak için:

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
Bu gerçekten kötü bir cevap. Kurutma işleminin kısmi uygulama ile ilgisi yoktur. Körleme, fonksiyon kompozisyonunu mümkün kılar. Fonksiyon kompozisyonu fonksiyonun yeniden kullanılmasını sağlar. İşlevlerin yeniden kullanılması kodun sürdürülebilirliğini artırır. İşte bu kadar kolay!

2
@ftor efendim, çok kötü bir cevapsın. Kurutma, fonksiyonları daha lezzetli hale getirmekle ilgilidir. Konuyu açıkça kaçırdınız.
Callat

112

İşte kapanışları kullanan JavaScript'te ilginç ve pratik bir köri kullanımı :

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"

Gördüğünüz gibi yalnızca kullandığı bir curryuzantıya dayanıyor (çok süslü bir şey yok):Functionapply

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
Bu harika! Ben "Lisp programlanabilir bir programlama dilidir" yazan lisp alıntısına benzer görüyorum
santiagobasulto

2
İlginç, ancak bu örnek işe yaramadı. offset+inputolacak undefined + 1.60936senin içinde milesToKmörneğin; ile sonuçlanır NaN.
Nathan Long

3
@Nathan - ofset undefined olamaz - varsayılan olarak 0
AngusC

6
(Şimdi) okuduğum kadarıyla, "curry", Prototip kütüphanesini kullanmadıkça veya kendiniz eklemediğiniz sürece, normalde bir Fonksiyonun hile çantasının bir parçası değildir. Yine de çok güzel.
Roboprog

11
Aynısı ES5 bind () yöntemi ile elde edilebilir. Bind, çağrıldığında orijinal işlevi ilk bağımsız değişkeninin bağlamıyla ve sonraki bağımsız değişken dizisiyle (yeni işleve iletilenlerden önce) çağıran yeni bir işlev oluşturur. Böylece ... var milesToKm = converter.bind (bu, 'km', 1.60936); veya var farenheitToCelsius = dönüştürücü. bağlama (bu, 'derece C', 0.5556, -32); İlk argüman, bağlam, bu, burada anlamsızdır, böylece tanımsız olarak geçebilirsiniz. Tabii ki, temel Fonksiyon prototipini ES5 dışı yedek için kendi bağlama yönteminizle artırmanız gerekir
hacklikecrack

7

functools.partialJavaScript'te python'a daha çok benzeyen fonksiyonlar buldum :

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

Neden kullanmak istersiniz? Bunu kullanmak istediğiniz yaygın bir durum this, bir işlevde bir değere bağlamak istediğiniz zamandır :

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

Şimdi geri arama çağrıldığında, thisişaret eder obj. Bu, olay durumlarında veya yer tasarrufu için faydalıdır, çünkü genellikle kodu kısaltır.

Kıvrılma, kıvrımın döndürdüğü işlevin sadece bir argümanı kabul ettiği (kısmi olarak anladığım kadarıyla) kısmen benzerdir.


7

Hank Gay ile aynı fikirde olmak - Bazı gerçek fonksiyonel programlama dillerinde son derece kullanışlıdır - çünkü bu gerekli bir parçadır. Örneğin, Haskell'de bir işleve birden fazla parametre alamazsınız - bunu saf fonksiyonel programlamada yapamazsınız. Her seferinde bir parametre alırsınız ve işlevinizi geliştirirsiniz. JavaScript'te "dönüştürücü" gibi birbirinden farklı örneklere rağmen bu gereksizdir. İşte aynı dönüştürücü kodu, curry gerekmeden:

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: İyi Parçalar" da Douglas Crockford'a, kaba sözler yerine tarihi ve gerçek körelme kullanımından bahsetmiş olsaydım. Bunu okuduktan sonra en uzun süre, Fonksiyonel programlama çalışana ve oradan geldiğini fark edene kadar şaşırdım.

Biraz daha düşündükten sonra, JavaScript'te köri yapmak için geçerli bir kullanım durumu olduğunu düşünüyorum: JavaScript kullanarak saf fonksiyonel programlama teknikleri kullanarak yazmaya çalışıyorsanız. Yine de nadir bir kullanım durumu gibi görünüyor.


2
Kodunuzu anlamak Mahkum Sıfırlardan daha kolaydır ve aynı sorunu körelmeden veya karmaşık bir şey olmadan çözer. 2 başparmak yukarıya ve neredeyse 100 var. Git rakam.
DR01D

2

Sihir falan yok ... anonim işlevler için hoş bir stenografi.

partial(alert, "FOO!") eşittir function(){alert("FOO!");}

partial(Math.max, 0) karşılık gelir function(x){return Math.max(0, x);}

Kısmi çağrılar ( MochiKit terminolojisi. Bazı kütüphanelerin işlevlere aynı şeyi yapan bir .curry yöntemi verdiğini düşünüyorum) anonim işlevlerden biraz daha hoş ve daha az gürültülü görünüyor.


1

Kullanan kütüphanelere gelince, her zaman İşlevseldir .

JS'de ne zaman yararlıdır? Muhtemelen aynı zamanlarda diğer modern dillerde de yararlıdır, ancak kendimi kullanırken görebildiğim tek zaman kısmi uygulama ile bağlantılı.


Teşekkürler Hank - lütfen genel olarak yararlı olduğunda genişletebilir misiniz?
Dave Nolan

1

Muhtemelen, JS'deki tüm animasyon kütüphanesinin köri kullandığını söyleyebilirim. Her çağrı için, bir dizi etkilenmiş eleman ve bir fonksiyonun, elemanın nasıl davranması gerektiğini açıklamak yerine, tüm zamanlama şeylerini sağlayacak daha yüksek bir sıra fonksiyonuna geçmek zorunda kalmak yerine, müşterinin genel API olarak bazılarını serbest bırakması daha kolaydır. "slideUp", "fadeIn" gibi işlevler, yalnızca öğeleri bağımsız değişken olarak alır ve varsayılan "animasyon işlevi" yerleşik olarak yüksek dereceli işlevi döndüren bazı kıvrımlı işlevlerdir.


Neden highup fonksiyonunu bazı varsayılanlarla çağırmak yerine köri yapmak daha iyi?
Dave Nolan

1
Çünkü, bir "doMathOperation" 'ı, yüksek fonksiyonun destekleyebileceği tüm "varsayılan" ları hayal etmekten ziyade, bir ekleme / çarpma / kare / modül / diğer hesaplamalar ile körüklemek oldukça modülerdir.
gizmo

1

İşte bir örnek.

Kullanıcıların neler yaptığını görebilmem için JQuery ile bir grup alanı kullanıyorum. Kod şöyle görünür:

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

(JQuery olmayan kullanıcılar için, birkaç alanın odağı aldığında veya kaybettiğinde, trackActivity () işlevinin çağrılmasını istiyorum. Anonim bir işlev de kullanabilirim, ancak çoğaltmam gerekir 4 kez çıkardım ve adlandırdım.)

Şimdi bu alanlardan birinin farklı ele alınması gerektiği ortaya çıktı. İzleme altyapımıza iletilecek bu çağrılardan birine bir parametre iletebilmek istiyorum. Currying ile yapabilirim.


1

JavaScript işlevlerine diğer işlevsel dillerde lamda denir. Başka bir geliştiricinin basit girdisine dayanarak yeni bir API (daha güçlü veya karmaşık işlev) oluşturmak için kullanılabilir. Curry, tekniklerden sadece bir tanesidir. Karmaşık bir API'yi çağırmak için basitleştirilmiş bir API oluşturmak için kullanabilirsiniz. Basitleştirilmiş API'yi kullanan geliştiriciyseniz (örneğin, basit manipülasyon yapmak için jQuery kullanırsınız), köri kullanmanız gerekmez. Ancak basitleştirilmiş API'yi oluşturmak istiyorsanız, köri arkadaşınızdır. Bir javascript çerçevesi (jQuery, mootools gibi) veya kütüphane yazmanız gerekir, o zaman gücünü takdir edebilirsiniz. Http://blog.semanticsworks.com/2011/03/enhanced-curry-method.html adresinde gelişmiş bir köri işlevi yazdım.. Köri yapmak için köri yöntemine ihtiyacınız yoktur, sadece köri yapmaya yardımcı olur, ancak başka bir işlev B () {} döndürmek için her zaman A () {} işlevini yazarak manuel olarak yapabilirsiniz. Daha ilginç hale getirmek için, B () işlevini kullanarak başka bir C () işlevine dönün.


1

Eski iş parçacığını biliyorum ama nasıl javascript kütüphanelerinde kullanıldığını göstermek zorunda kalacak:

Bu kavramları somut olarak tanımlamak için lodash.js kütüphanesini kullanacağım.

Misal:

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

Kısmi Uygulama:

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

currying:

var curriedFn = _.curry(fn);

Bağlama:

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

kullanımı:

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!

farkı:

köreldikten sonra, önceden bağlı parametre olmadan yeni bir fonksiyon elde ederiz.

kısmi uygulamadan sonra bazı parametrelerle önceden bağlanmış bir fonksiyon elde ederiz.

Bağlamada, 'bu' yerine kullanılacak bir bağlamı bağlayabiliriz, eğer herhangi bir fonksiyonun bağlı değilse varsayılanı pencere kapsamı olacaktır.

Tavsiye: Tekerleği yeniden icat etmeye gerek yoktur. Kısmi uygulama / ciltleme / körelme çok ilgili. Yukarıdaki farkı görebilirsiniz. Bu anlamı her yerde kullanın ve insanlar ne yaptığınızı anlamada sorun yaşamadan tanıyacak, ayrıca daha az kod kullanmanız gerekecek.


0

Zaman zaman, ilk argümanın değerini her zaman dolduracak bir sahte işlev oluşturarak topu yuvarlatmak istediğinizi kabul ediyorum. Neyse ki, jPaq (h ttp: //) adlı yepyeni bir JavaScript kütüphanesine rastladım. jpaq.org/ ) bu işlevselliği sağlar. Kütüphane ile ilgili en iyi şey, sadece ihtiyacınız olan kodu içeren kendi derlemenizi indirebilmenizdir.



0

Functional.js için bazı kaynaklar eklemek istedim:

Bazı uygulamaları açıklayan konferans / konferans http://www.youtube.com/watch?v=HAcN3JyQoyY

Güncellenmiş Functional.js kütüphanesi: https://github.com/loop-recur/FunctionalJS Bazı güzel yardımcılar (üzgünüm burada yeni, itibar yok: p): / loop-recur / PreludeJS

Bir js IRC istemcileri yardımcı kütüphanesinde tekrarını azaltmak için bu kitaplığı çok yakın zamanda kullanıyorum. Bu harika şeyler - gerçekten kodun temizlenmesine ve basitleştirilmesine yardımcı oluyor.

Ek olarak, performans bir sorun haline gelirse (ancak bu lib oldukça hafiftir), yerel bir işlev kullanarak yeniden yazmak kolaydır.


0

Hızlı, tek satırlık bir çözüm için yerel bağlantı kullanabilirsiniz

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

Vaatlerle çalışmaktan başka bir bıçak daha.

(Feragatname: Python dünyasından gelen JS noob. Orada bile, körük çok fazla kullanılmıyor, ancak bazen kullanışlı olabilir. Bu yüzden körelme fonksiyonunu kapladım - bağlantılara bakın)

İlk olarak, bir ajax çağrısı ile başlıyorum. Başarı üzerinde yapmak için bazı özel işlem var, ama başarısızlık, ben sadece kullanıcıya bir şey çağırmak bazı hataya neden olduğu geribildirim vermek istiyorum . Gerçek kodumda, bir bootstrap panelinde hata geri bildirimini görüntülüyorum, ancak sadece burada günlüğe kaydetmeyi kullanıyorum.

Bunu yapmak için canlı URL’mi değiştirdim.

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

Şimdi, burada bir toplu iş başarısız olduğunu söylemek için, hata işleyicisine bu bilgileri yazmak gerekir, çünkü tüm elde sunucudan bir yanıttır.

Hala sadece kodlama zamanında bilgi var - benim durumumda bir dizi olası toplu var, ama hangisi başarısız başarısız url hakkında sunucu yanıt ayrıştırmak wo başarısız bilmiyorum.

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

Haydi Yapalım şunu. Konsol çıkışı:

konsol:

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

Şimdi, işleri biraz değiştirelim ve yeniden kullanılabilir bir genel hata işleyici kullanalım, aynı zamanda hem kod zamanında bilinen çağrı bağlamı hem de olaydan elde edilebilen çalışma zamanı bilgisi ile çalışma zamanında curried olanı kullanalım .

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

konsol:

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

Daha genel olarak, JS'de yaygın geri arama kullanımının ne kadar olduğu göz önüne alındığında, körelme, sahip olmak için oldukça yararlı bir araç gibi görünmektedir.

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

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.