Bir program neden kapatma kullanıyor?


58

Buradaki kapanışları açıklayan birçok gönderiyi okuduktan sonra hala önemli bir kavramı özlüyorum: Neden bir kapanış yazmalı? Bir programcı kapatmanın en iyi şekilde yapabileceği hangi özel görevi yerine getirir?

Swift'deki kapama örnekleri bir NSUrl'ye erişiyor ve ters geocoder kullanıyor. İşte böyle bir örnek. Ne yazık ki, bu kurslar kapanışı göstermektedir; Kod çözümünün neden kapatma olarak yazıldığını açıklamıyorlar.

Beynimi "aha, bunun için bir kapanış yapmalıyım" demeye zorlayabilecek gerçek bir dünya programlama problemine örnek, teorik bir tartışmadan daha bilgilendirici olacaktır. Bu sitede teorik tartışmalar sıkıntısı yoktur.


7
"Son zamanlarda kapatılan kapanışların incelemesi" - bir bağlantıyı mı kaçırıyorsun?
Dan Pichelman

2
Eşzamansız görev hakkındaki bu açıklamayı ilgili cevabın altındaki bir yoruma koymalısınız . Bu, orijinal sorunuzun bir parçası değildir ve yanıtlayan, düzenlemenizden asla haberdar edilmez.
Robert Harvey,

5
Sadece bir haberleşme: Kapanışların kritik noktası, sözcüksel kapsamlaştırmadır (dinamik kapsamlamanın aksine), sözcüksel kapsamlanmayan kapanmalar biraz işe yaramaz. Kapaklara bazen Lexical Kapaklar denir.
Hoffmann

1
Kapaklar, işlevleri başlatmanıza olanak tanır .
user253751

1
Fonksiyonel programlama ile ilgili iyi bir kitap okuyun. Belki SICP
Basile Starynkevitch

Yanıtlar:


33

Her şeyden önce, kapakları kullanmadan imkansız olan hiçbir şey yoktur. Bir kapağı her zaman belirli bir arabirim uygulayan bir nesneyle değiştirebilirsiniz. Bu sadece bir kısalık meselesi ve azaltılmış eşleşme meselesi.

İkincisi, basit bir fonksiyon referansının veya başka bir yapının daha net olacağı durumlarda, kapakların sık sık yanlış kullanıldığını unutmayın. En iyi uygulama olarak gördüğünüz her örneği almamalısınız.

Kapanışların diğer yapılar üzerinde gerçekte parladığı yer, daha üst düzey işlevler kullanırken, gerçekte durumu bildirmeniz gerektiğinde ve kapamalar konusundaki bu JavaScript örneğinde olduğu gibi, bunu bir liner yapabilirsiniz :

// Return a list of all books with at least 'threshold' copies sold.
function bestSellingBooks(threshold) {
  return bookList.filter(
      function (book) { return book.sales >= threshold; }
    );
}

Burada, thresholdkullanıldığı yerde tanımlandığı yerden çok özlü ve doğal bir şekilde iletilir. Kapsamı tam olarak mümkün olduğu kadar küçük. filterMüşterinin tanımladığı verileri bir eşik gibi geçirme ihtimaline izin vermek için yazılmış olması gerekmez. Bu küçük işlevde eşiği iletmek için herhangi bir ara yapı tanımlamamız gerekmez. Tamamen kendine yeten bir şey.

Sen edebilir bir kapatma olmadan bunu yazmak, ama çok daha fazla kod gerektirir ve takip etmek daha zor olacaktır. Ayrıca, JavaScript oldukça ayrıntılı bir lambda sözdizimine sahiptir. Scala'da, örneğin, tüm fonksiyon gövdesi şöyle olacaktır:

bookList filter (_.sales >= threshold)

Ancak ECMAScript 6'yı kullanabilirseniz , şişman ok işlevleri sayesinde , JavaScript kodu bile daha basit hale gelir ve aslında tek bir satıra yerleştirilebilir.

const bestSellingBooks = (threshold) => bookList.filter(book => book.sales >= threshold);

Kendi kodunuzda, geçici değerleri yalnızca bir yerden diğerine iletmek için çok sayıda kazan plakası oluşturduğunuz yerleri arayın. Bunlar, kapatmayla değiştirmeyi düşünmek için mükemmel fırsatlardır.


Kapaklar tam olarak nasıl birleşmeyi azaltır?
sbichenko

Kapatılmadan, verileri iletebilmek için hem bestSellingBookskod hem de filterbelirli bir arabirim veya kullanıcı verileri argümanı gibi kod için kısıtlamalar getirmeniz gerekir threshold. Bu, iki işlevi birbirine çok daha az tekrarlanabilir şekilde bağlar.
Karl Bielefeldt

51

Açıklama yoluyla, kapanışlarla ilgili bu mükemmel blog gönderisinden bir miktar kod ödünç alacağım . Bu JavaScript, ancak çoğu blog gönderisinin, kapaklar hakkında konuşmaktan bahsettiği dil, çünkü kapaklar JavaScript'te çok önemlidir.

Bir diziyi HTML tablosu olarak oluşturmak istediğinizi varsayalım. Böyle yapabilirsin:

function renderArrayAsHtmlTable (array) {
  var table = "<table>";
  for (var idx in array) {
    var object = array[idx];
    table += "<tr><td>" + object + "</td></tr>";
  }
  table += "</table>";
  return table;
}

Ancak, dizideki her öğenin nasıl işleneceği konusunda JavaScript'in insafına sahipsiniz. Oluşturmayı kontrol etmek istiyorsanız, şunu yapabilirsiniz:

function renderArrayAsHtmlTable (array, renderer) {
  var table = "<table>";
  for (var idx in array) {
    var object = array[idx];
    table += "<tr><td>" + renderer(object) + "</td></tr>";
  }
  table += "</table>";
  return table;
}

Ve şimdi istediğiniz görüntüyü döndüren bir işlevi iletebilirsiniz.

Her Tablo Satırında bir toplamı görüntülemek istiyorsanız ne olur? Bu toplamı takip etmek için bir değişkene ihtiyacınız olacak, değil mi? Kapatma, toplam çalışma değişkenini kapayan bir işleyici işlevi yazmanıza izin verir ve toplam iş akışını takip edebilecek bir işleyici yazmanıza olanak sağlar:

function intTableWithTotals (intArray) {
  var total = 0;
  var renderInt = function (i) {
    total += i;
    return "Int: " + i + ", running total: " + total;
  };
  return renderObjectsInTable(intArray, renderInt);
}

Burada gerçekleşen sihir , defalarca çağrılıp çıkarılsa bile değişkene renderInt erişimi koruyor .totalrenderInt

JavaScript'ten daha geleneksel olarak nesne yönelimli bir dilde, bu toplam değişkeni içeren bir sınıf yazabilir ve bir kapatma oluşturmak yerine etrafa iletebilirsiniz. Ancak kapatma işlemi, bunu yapmanın çok daha güçlü, temiz ve zarif bir yoludur.

Daha fazla okuma


10
Genel olarak, "birinci sınıf işlevi == yalnızca bir yöntemle nesne", "Kapatma == yalnızca bir yöntemle nesne ve durum", "nesne == kapatma paketi" diyebilirsiniz.
Jörg W Mittag

İyi görünüyor. Başka bir şey ve bu ukalalık olabilir JavaScript olmasıdır olan nesne yönelimli ve olabilir toplam değişkeni içeren bir nesne oluşturmak ve bu çevrede geçmektedir. Kapaklar çok daha güçlü, temiz ve zarif kalıyor ve aynı zamanda çok daha aptalca bir Javascript oluşturuyor, ancak yazıldığı şekil Javascript'in nesne yönelimli bir şekilde yapamayacağı anlamına gelebilir.
KRyan

2
Aslında, gerçekten bilgili olmak için: Kapanış, JavaScript'i en başta nesne odaklı yapan şeydir! OO veri soyutlama ile ilgilidir ve kapanışlar JavaScript'te veri soyutlama gerçekleştirmenin yoludur.
Jörg W Mittag

@ JörgWMittag: Sadece tek bir yöntemle nesneleri nesneler olarak görebildiğiniz gibi, nesneleri aynı değişkenlerin üzerine kapanan bir kapak koleksiyonu olarak görebilirsiniz: Bir nesnenin üye değişkenleri sadece nesnenin yapıcısının yerel değişkenleri ve nesnenin yöntemleridir. Yapıcı kapsamında tanımlanan ve daha sonra çağrılmak üzere kullanıma sunulan kapaklardır. Yapıcı işlevi, başlatma için kullanılan yöntem adına göre her kapanışta gönderebilen daha yüksek bir işlev (nesne) döndürür.
Giorgio

@Giorgio: Aslında, nesnelerin tipik olarak Scheme'de uygulanma şeklidir ve aslında nesnelerin JavaScript'te nasıl uygulandıklarıdır (aslında Scheme ile olan yakın ilişkisi göz önüne alındığında şaşırtıcı değildir, sonuçta, Brendan Eich aslen bir tasarım için işe alındı. Şema lehçesi ve Netscape Navigator içinde gömülü bir Şema yorumlayıcısı uyguladı ve daha sonra "C ++ 'a benzeyen nesnelerle" ve ardından bu pazarlama gereksinimlerine uyması için en az miktarda değişiklik yaptığı bir dil yapması istendi).
Jörg W Mittag

22

Bunun amacı closuresbasitçe devleti korumaktır ; Bu nedenle adı closure- devlet üzerinde kapatır . Daha fazla açıklama kolaylığı için Javascript kullanacağım.

Genelde bir işlevin vardır

function sayHello(){
    var txt="Hello";
    return txt;
}

değişken (ler) in kapsamı bu işleve bağlı. Dolayısıyla, yürütmeden sonra değişken txtkapsam dışına çıkar. İşlev yürütmeyi bitirdikten sonra erişme veya kullanma yolu yoktur.

Kapanışlar, daha önce de belirtildiği gibi değişkenlerin durumunu korumaya ve böylece kapsamı uzatmaya izin veren dil yapısıdır.

Bu, farklı durumlarda faydalı olabilir. Bir kullanım durumu, daha yüksek dereceli fonksiyonların yapımıdır .

Matematik ve bilgisayar bilimlerinde, üst düzey bir işlev (ayrıca işlevsel biçim, işlev veya işlev) de aşağıdakilerden en az birini yapan bir işlevdir: 1

  • giriş olarak bir veya daha fazla fonksiyon alır
  • işlev çıktılar

Basit ama kabul edilebilir bir şekilde hepsi çok yararlı bir örnek değildir:

 makeadder=function(a){
     return function(b){
         return a+b;
     }
 }

 add5=makeadder(5);
 console.log(add5(10)); 

makedadderBir parametreyi girdi olarak alan ve bir işlev döndüren bir işlev tanımlarsınız . Bir dış işlev function(a){}ve bir function(b){}{} kısım vardır. Bundan başka add5, yüksek dereceli işlev çağrısının sonucu olarak başka bir işlevi de (dolaylı olarak) tanımlarsınız makeadder. makeadder(5)anonim ( ) bir işlev döndürür , bu da sırasıyla 1 parametre alır ve dış işlev parametresinin ve işlev parametresinin toplamını döndürür .

İşin püf noktası, asıl eklemeyi yapan işlevi döndürürken, dış işlev parametresinin ( a) kapsamının korunmuş olmasıdır. add5 hatırlar parametresi olduğu, aoldu 5.

Veya en azından bir şekilde yararlı bir örnek göstermek için:

  makeTag=function(openTag, closeTag){
     return function(content){
         return openTag +content +closeTag;
     }
 }

 table=makeTag("<table>","</table>")
 tr=makeTag("<tr>", "</tr>");
 td=makeTag("<td>","</td>");
 console.log(table(tr(td("I am a Row"))));

Diğer bir yaygın usecase, IIFE = hemen çağrılan işlev ifadesidir. Javascript'te özel üye değişkenlerini taklit etmek çok yaygındır . Bu, özel bir kapsam yaratan bir işlev aracılığıyla yapılır = closure, çünkü tanımın çağrılmasından hemen sonra. Yapı function(){}(). ()Tanımdan sonra parantezlere dikkat edin . Bu, ifşa modül modeliyle nesne oluşturma için kullanılmasını mümkün kılar . İşin püf noktası, bir kapsam yaratıyor ve IIFE'nin yürütülmesinden sonra bu kapsama erişimi olan bir nesneyi döndürüyor.

Addi'nin örneği şuna benziyor:

 var myRevealingModule = (function () {

         var privateVar = "Ben Cherry",
             publicVar = "Hey there!";

         function privateFunction() {
             console.log( "Name:" + privateVar );
         }

         function publicSetName( strName ) {
             privateVar = strName;
         }

         function publicGetName() {
             privateFunction();
         }


         // Reveal public pointers to
         // private functions and properties

         return {
             setName: publicSetName,
             greeting: publicVar,
             getName: publicGetName
         };

     })();

 myRevealingModule.setName( "Paul Kinlan" );

Döndürülen nesne, publicSetNamesırasıyla "özel" değişkenlere erişebilen işlevlere (örneğin ) başvurular içerir privateVar.

Ancak bunlar Javascript için daha özel kullanım durumlarıdır.

Bir programcı kapatmanın en iyi şekilde yerine getirebileceği hangi özel görevi yerine getirir?

Bunun birkaç nedeni var. Biri, fonksiyonel bir paradigmayı takip ettiğinden dolayı, onun için doğal olabilir . Veya JavaScript: o sadece zorunluluktur dilin bazı kültürlerinden kaçınmak için kapanışları güvenmek.


“Kapanmanın amacı basitçe devleti korumaktır; bu nedenle kapama adı - devlet üzerinde kapanır.”: Gerçekten dile bağlı. Dış adlara / değişkenlere yakın kapanır. Bunlar durumu (değiştirilebilecek hafıza konumları), fakat değerleri de (değişmez) belirtir. Haskell'deki kapanışlar durumu korumaz: şu anda ve kapanmanın oluşturulduğu bağlamda bilinen bilgileri korurlar.
Giorgio

1
»Şu anda ve kapanmanın yapıldığı bağlamda bilinen bilgileri« değiştirilip değiştirilememesine ya da değiştirilmemesine rağmen bağımsız olarak korur - devlettir - belki de değişmez durumdur .
Thomas Junk

16

Kapatma için iki ana kullanım durumu vardır:

  1. Asenkron. Diyelim ki biraz zaman alacak bir işi yapmak istediğinizi ve sonra bittiğinde bir şey yapmayı istediğinizi varsayalım. Kodunuzun yapılmasını bekletebilir, bu da daha fazla uygulamayı engeller ve programınızın yanıt vermemesini sağlayabilir veya görevinizi zaman uyumsuz olarak çağırabilir ve "arka planda bu uzun görevi başlat ve tamamladığında bu kapatma işlemini yürüt" diyebilirsiniz. kapatma işlemi tamamlandığında çalıştırılacak kodun bulunduğu yer.

  2. Callbacks. Bunlar ayrıca dile ve platforma bağlı olarak "delegeler" veya "olay işleyicileri" olarak da bilinir. Buradaki fikir, iyi tanımlanmış bazı noktalarda, onu ayarlayan kod tarafından geçirilen bir kapatma işlemini yürüten bir olayı yürütecek olan özelleştirilebilir bir nesneye sahip olmanızdır . Örneğin, programınızın kullanıcı arayüzünde bir düğmeye sahip olabilirsiniz ve kullanıcı düğmeyi tıklattığında yürütülecek kodu tutan bir kapatma vermiş olursunuz.

Kapaklar için başka birçok kullanım alanı var, ancak bunlar iki ana neden.


23
Yani, temelde geri aramalar, çünkü ilk örnek aynı zamanda geri aramadır.
Robert Harvey,

2
@RobertHarvey: Teknik olarak doğru, ama onlar farklı zihinsel modeller. Örneğin, (genel olarak konuşursak), olay işleyicisinin birden çok kez çağrılmasını beklersiniz, ancak zaman uyumsuzluğunuzun devam etmesini yalnızca bir kez ararsınız. Ancak, evet, teknik olarak kapatmayla yapacağınız her şey bir geri aramadır. (Bunu bir ifade ağacına dönüştürmüyorsanız, ancak bu tamamen farklı bir konu.);)
Mason Wheeler

4
@RobertHarvey: Kapakları geri aramalar olarak görmek, sizi gerçekten etkili bir şekilde kullanmanıza engel olacak bir zihin çerçevesine sokar. Kapanış steroidlerin geri çağrılarıdır.
gnasher729

13
@MasonWheeler Bu şekilde koyun, yanlış geliyor. Geri aramaların kapaklarla hiçbir ilgisi yoktur; Elbette bir kapanış içindeki bir fonksiyonu geri çağırabilir veya bir geri arama olarak kapatmayı çağırabilir; ancak geri arama kapatılması gerekli değildir. Geri arama sadece basit bir fonksiyon çağrısıdır. Bir olay uzmanı kendiliğinden bir kapanma değildir. Bir temsilcinin kapatılması gerekli değildir: öncelikle işlev işaretçilerinin C # yöntemidir. Elbette bir kapatma yapmak için kullanabilirsiniz. Açıklaman kesin değil. Mesele devlet üzerinden kapanıyor ve bundan faydalanıyor.
Thomas Junk

5
Belki burada JS'ye özgü çağrışımlar oluyor ve beni yanlış anlıyor, ancak bu cevap bana çok yanlış geliyor. Eşzamansızlık veya geri aramalar, 'çağrılabilir' olan her şeyi kullanabilir. Her ikisi için de kapatma gerekmez - normal bir fonksiyon iyi sonuç verir. Bu arada, işlevsel programlamada tanımlandığı gibi veya Python'da kullanıldığı gibi bir kapanış, Thomas'ın dediği gibi yararlıdır, çünkü bazı durumları, yani bir değişkeni 'kapatır', ve bu değişkene tutarlı bir şekilde erişim sağlayan bir fonksiyon sunar. işlev çağrılsa ve birçok kez çıkılsa bile değer.
Jonathan Hartley

13

Birkaç başka örnek:

Sıralama
Çoğu sıralama işlevi, nesne çiftlerini karşılaştırarak çalışır. Bazı karşılaştırma tekniğine ihtiyaç var. Karşılaştırmanın belirli bir operatörle sınırlandırılması oldukça esnek bir sıralama anlamına gelir. Çok daha iyi bir yaklaşım, bir sıralama fonksiyonunu sıralama fonksiyonunun argümanı olarak almaktır. Bazen durumsuz bir karşılaştırma işlevi iyi sonuç verir (örneğin, bir sayı veya isim listesini sıralama), ancak karşılaştırma durumu gerektiriyorsa ne olur?

Örneğin, bir şehir listesini belirli bir konuma olan mesafeye göre sıralamayı düşünün. Çirkin bir çözüm, bu konumun koordinatlarını global bir değişkende saklamaktır. Bu, karşılaştırma fonksiyonunun kendisini vatansız kılar, fakat global bir değişkenin bedeli karşılığında.

Bu yaklaşım, aynı şehir listesini iki farklı yere olan mesafelerine göre aynı anda sıralayan çoklu ipliklere sahip olmayı önler. Konumu çevreleyen bir kapatma bu sorunu çözer ve global bir değişkene olan ihtiyaçtan kurtulur.


Rastgele sayılar
Orijinal rand()hiçbir argüman almadı. Sözde rasgele sayı üreteçlerinin duruma ihtiyacı var. Bazıları (örneğin, Mersenne Twister) çok fazla devlete ihtiyaç duyar. Basit ama korkunç rand()ihtiyaç duyulan devlet bile . Yeni bir rasgele sayı üretecindeki bir matematik günlüğü makalesini okuyun; kaçınılmaz olarak global değişkenleri göreceksiniz. Tekniğin geliştiricileri için iyi, arayanlar için iyi değil. Bu durumu bir yapının içine yerleştirmek ve yapıyı rasgele sayı üretecine geçirmek, küresel veri probleminin etrafında bir yoldur. OO dışındaki birçok dilde rastgele bir sayı üreteci reentrant yapmak için kullanılan yaklaşım budur. Bir kapatma, bu durumu arayandan gizler. Bir kapatma, basit arama sırasını rand()ve kapsüllenmiş durumun geri dönmesini sağlar.

Bir PRNG'den çok rasgele sayılar var. Rasgelelik isteyen çoğu kişi belli bir şekilde dağıtılmasını istiyor. Rasgele 0 ile 1 arasında sayılarla veya kısaca U (0,1) ile numaralarla başlayacağım. 0 ile biraz maksimum arasında tam sayı üreten herhangi bir PRNG; basitçe (kayan nokta olarak) rasgele tamsayıyı maksimum değere bölün. Bunu gerçekleştirmenin uygun ve genel bir yolu, bir kapatma (PRNG) ve girişler olarak maksimum olan bir kapatma oluşturmaktır. Şimdi U (0,1) için genel ve kullanımı kolay bir rasgele üreteci var.

U (0,1) dışında başka birçok dağıtım vardır. Örneğin, belirli bir ortalama ve standart sapma ile normal bir dağılım. Karşılaştığım her normal dağıtım üreteci algoritması bir U (0,1) üreteci kullanıyor. Normal bir jeneratör oluşturmanın uygun ve genel bir yolu, U (0,1) üretecini, ortalamayı ve standart sapmayı durum olarak içine alan bir kapak oluşturmaktır. Bu, en azından kavramsal olarak, bir argüman olarak kapatılan bir kapanış gerektiren bir kapanış.


7

Kapaklar, run () yöntemini uygulayan nesnelere eşdeğerdir ve tersine, nesneler kapaklarla taklit edilebilir.

  • Kapanmanın avantajı, bir fonksiyonun beklediğiniz her yerde kolayca kullanılabilmesidir: aka yüksek dereceli fonksiyonlar, basit geri aramalar (veya Strateji Kalıbı). Geçici kapaklar oluşturmak için bir arayüz / sınıf tanımlamanıza gerek yoktur.

  • Nesnelerin avantajı, daha karmaşık etkileşimlere sahip olma olasılığıdır: çoklu yöntemler ve / veya farklı arayüzler.

Bu yüzden, kapatma veya nesneler kullanmak çoğunlukla bir stil meselesidir. Kapanışları kolaylaştıran, ancak nesnelerle uygulama yapmak için elverişsiz olan şeylere bir örnek:

 (let ((seen))
    (defun register-name (name)
       (pushnew name seen :test #'string=))

    (defun all-names ()
       (copy-seq seen))

    (defun reset-name-registry ()
       (setf seen nil)))

Temel olarak, yalnızca global kapanmalar yoluyla erişilen gizli bir durumu kapsıyorsunuz: herhangi bir nesneye başvurmanıza gerek yok, yalnızca üç işlev tarafından tanımlanan protokolü kullanın.


  • Bu yorumu yanıtlamak için cevabı genişletiyorum *.

Supercat’in, bazı dillerde, nesnelerin kullanım ömrünü tam olarak kontrol etmenin mümkün olduğu gerçeğine ilişkin ilk yorumuna güveniyorum, aynı şey kapanmalar için de doğru değil. Bununla birlikte, çöp toplanan diller söz konusu olduğunda, nesnelerin ömrü genel olarak sınırlandırılmamıştır ve böylece çağrılmaması gereken dinamik bir bağlamda çağrılabilecek bir kapak inşa etmek mümkündür (akıştan sonra bir kapanmadan okumak). örneğin kapalıdır).

Bununla birlikte, bir kapamanın uygulanmasını koruyacak bir kontrol değişkenini yakalayarak bu tür kötüye kullanımı önlemek oldukça basittir. Daha doğrusu, işte aklımda olan şey şu (Common Lisp'te):

(defun guarded (function)
  (let ((active t))
    (values (lambda (&rest args)
              (when active
                (apply function args)))
            (lambda ()
              (setf active nil)))))

Burada, bir işlev belirleyiciyi alıyoruz functionve her ikisi de adlı yerel bir değişkeni yakalayan iki kapak döndürüyoruz active:

  • ilki delegeye function, sadece activedoğru olduğunda
  • İkincisi ise ayarlar actioniçin nilaka false.

Bunun yerine , kapatılması gerekmediğinde çağrılması durumunda istisna oluşturabilecek (when active ...)bir (assert active)ifade olması elbette mümkündür . Ayrıca, güvenli olmayan kodun , kötü kullanıldığında zaten kendi başına bir istisna atabileceğini unutmayın , bu nedenle böyle bir sargıya nadiren ihtiyacınız olur.

İşte nasıl kullanacağınız:

(use-package :metabang-bind) ;; for bind

(defun example (obj1 obj2)
  (bind (((:values f f-deactivator)(guarded (lambda () (do-stuff obj1))))
         ((:values g g-deactivator)(guarded (lambda () (do-thing obj2)))))

    ;; ensure the closure are inactive when we exit
    (unwind-protect
         ;; pass closures to other functions
         (progn
           (do-work f)
           (do-work g))

      ;; cleanup code: deactivate closures
      (funcall f-deactivator)
      (funcall g-deactivator))))

Devre dışı bırakan kapakların diğer fonksiyonlara da verilebileceğini unutmayın; burada, yerel activedeğişkenler fve arasında paylaşılmaz g; ayrıca, ek olarak active, fsadece ifade eder obj1ve gsadece ifade eder obj2.

Supercat tarafından belirtilen bir diğer nokta, kapakların hafıza sızıntılarına neden olabileceğidir, ancak ne yazık ki, çöp toplanan ortamlarda hemen hemen her şey için geçerlidir. Eğer mevcutlarsa, bu zayıf işaretçilerle çözülebilir (kapatmanın kendisi bellekte tutulabilir, ancak diğer kaynakların çöp toplanmasını engellemez).


1
Yaygın olarak uygulanan kapamaların bir dezavantajı, bir yöntemin yerel değişkeninden birini kullanan bir kapağın dış dünyaya maruz kalmasının bir kez kapanması tanımlayan yöntemin, bu değişken okunduğunda ve yazıldığında kontrolünü kaybedebileceğidir. Çoğu dilde, geçici bir kapanma tanımlamanın, onu bir yönteme geçirmenin ve onu alan yöntem geri döndüğünde bir kez daha var olmayacağının ve çok iş parçacıklı dillerin hiçbir şekilde bulunmadığını garanti etmenin uygun bir yolu yoktur. kapatmanın beklenmeyen bir iş parçacığı bağlamında çalıştırılmayacağını garanti eder.
supercat

@supercat: Objective-C ve Swift'e bir göz atın.
gnasher729

1
@supercat Aynı dezavantaj devletli nesnelerle olmaz mıydı?
Andres F.,

@AndresF .: Durum bilgisi olmayan bir nesneyi geçersiz kılmayı destekleyen bir ambalajın içine sarmak mümkündür; Kod bir özelde List<T>(varsayımsal sınıfta) tutuluyorsa TemporaryMutableListWrapper<T>ve bunu dışsal koda maruz bırakırsa, sargısını geçersiz kılarsa, dış kodun artık herhangi bir şekilde kullanılmayacağından emin olabilirsiniz List<T>. Bir kişi, beklenen amaçlarına ulaştıktan sonra geçersiz kılınmasına izin vermek için kapaklar tasarlayabilir, ancak bu pek de elverişli değildir. Bazı kalıpları uygun hale getirmek için kapaklar mevcuttur ve onları korumak için gereken çaba bunu olumsuzlar.
supercat,

1
@ coredump: C # 'da, iki kapağın ortak değişkenleri varsa, aynı derleyici tarafından oluşturulan nesne her ikisine de hizmet eder, çünkü bir kapama tarafından paylaşılan bir değişkende yapılan değişiklikler diğeri tarafından görülmelidir. Paylaşımdan kaçınılması, her kapatmanın kendi paylaşılmayan değişkenlerini içeren kendi nesnesi olması ve paylaşılan değişkenleri içeren paylaşılan bir nesneye atıfta bulunmasını gerektirecektir. İmkansız değil, ancak çoğu değişken erişimine ek olarak her şey yavaşlatan ek bir dereference seviyesi eklerdi.
supercat,

6

Henüz söylenmemiş bir şey değil, belki daha basit bir örnek.

İşte zaman aşımlarını kullanan bir JavaScript örneği:

// Example function that logs something to the browser's console after a given delay
function delayedLog(message, delay) {
  // this function will be called when the timer runs out
  var fire = function () {
    console.log(message); // closure magic!
  };

  // set a timeout that'll call fire() after a delay
  setTimeout(fire, delay);
}

Burada ne delayedLog()olduğu, çağrıldığında zaman aşımını ayarladıktan hemen sonra geri döndüğü ve zaman aşımının arka planda azalmaya devam ettiğidir.

Ancak zaman aşımı sona erdiğinde ve fire()işlevi çağırdığında, konsol messagebaşlangıçta iletileni gösterecektir delayedLog(), çünkü hala fire()kapatmaya müsait . İstediğiniz delayedLog()kadar farklı bir mesajla arayabilir ve her seferinde gecikebilir ve doğru olanı yaparsınız.

Ancak, JavaScript’in kapanmadığını düşünelim.

Bir yol setTimeout()engelleme yapmaktır - daha çok "uyku" işlevi gibi - bu yüzden delayedLog()zaman aşımı bitinceye kadar kapsamı ortadan kalkmaz. Ama her şeyi engellemek çok hoş değil.

Başka bir yol, messagedeğişkeni kapsamı geçtikten sonra erişilebilecek başka bir kapsamda koymak olacaktır delayedLog().

Global - veya en azından "geniş kapsamlı" - değişkenleri kullanabilirsiniz, ancak hangi mesajın hangi zaman aşımı ile gittiğini nasıl takip edeceğinizi bulmak zorundasınız. Ancak bu sadece sıralı bir FIFO sırası olamaz, çünkü istediğiniz herhangi bir gecikmeyi ayarlayabilirsiniz. Yani "ilk giren, üçüncü çıkan" ya da başka bir şey olabilir. Böylece zamanlanmış bir işlevi, ihtiyaç duyduğu değişkenlere bağlamak için başka yollara ihtiyacınız olacaktır.

Zamanlayıcıyı mesajla "gruplandıran" bir zaman aşımı nesnesini başlatabilirsiniz. Bir nesnenin içeriği az ya da çok yapışan bir kapsamdır. Sonra zamanlayıcının nesnenin bağlamında çalışmasını sağlarsınız, böylece doğru mesaja erişir. Ancak bu nesneyi saklamak zorunda kalacaksınız, çünkü herhangi bir referans olmadan çöp toplanacak (kapaklar olmadan, dolaylı hiçbir referansı da olmayacaktı). Ve zaman aşımına uğradığında nesneyi kaldırmanız gerekir, aksi takdirde sadece yapıştırılır. Böylece, zaman aşımı nesnelerinin bir tür listesine ihtiyacınız olacak ve düzenli olarak "harcanan" nesnelerin kaldırılması için kontrol etmelisiniz - yoksa nesneler listeye kendilerini ekler ve kaldırır ve ...

Yani ... evet, bu sıkıcı olmaya başladı.

Neyse ki, belirli değişkenleri korumak için daha geniş bir kapsam kullanmanız veya nesneleri dolaştırmanız gerekmez. JavaScript'in kapanması nedeniyle, tam olarak ihtiyaç duyduğunuz kapsama sahip oluyorsunuz. messageİhtiyacınız olduğunda değişkene erişmenizi sağlayan bir kapsam . Ve bu sayede delayedLog()yukarıdaki gibi yazılarla kurtulabilirsin .


Buna kapatma derdinde bir sorunum var . Belki de yanlışlıkla kapatılmasını yazıyorum . messageişlev kapsamına dahil edilmiştir fireve bu nedenle başka çağrılarda anılır; ama bunu kazara söyleyelim. Teknik olarak bir kapanış. Zaten +1;)
Thomas Junk

@ThomasJunk Tam olarak takip ettiğimden emin değilim. " Kazara olmayan bir kapanma" nasıl görünür? makeadderYukarıdaki örneğini gördüm, gözlerime göre aynı görünüyor. İki yerine tek bir argüman alan "curried" işlevini döndürürsünüz; aynı araçları kullanarak, sıfır argümanını alan bir işlev yaratıyorum. Sadece iade etmiyorum ama onun setTimeoutyerine geçtim .
Flambino

“Sadece geri vermiyorum« belki de, bu benim için fark yaratan nokta. "Endişemi" açık şekilde ifade edemiyorum;) Teknik açıdan% 100 haklısınız. Referans messageListesi firekapak oluşturur. Ve çağrıldığında setTimeoutkorunan durumu kullanır.
Thomas Junk

1
@ThomasJunk Nasıl biraz farklı kokabileceğini anlıyorum. Endişenizi paylaşmayabilirim :) Ama, ne de olsa, buna "tesadüfi"
demezdim

3

PHP , farklı bir dilde gerçek bir örnek göstermeye yardımcı olmak için kullanılabilir.

protected function registerRoutes($dic)
{
  $router = $dic['router'];

  $router->map(['GET','OPTIONS'],'/api/users',function($request,$response) use ($dic)
  {
    $controller = $dic['user_api_controller'];
    return $controller->findAllAction($request,$response);
  })->setName('api_users');
}

Bu yüzden temelde / api / users URI'si için yürütülecek bir işlevi kayıt ediyorum . Bu aslında bir yığında depolanan biten bir katman işlevidir. Diğer fonksiyonlar etrafına sarılacaktır. Node.js / Express.js gibi oldukça .

Bağımlılık enjeksiyon denir aldığında kap işlevi içinde (kullanımı madde üzerinden) mevcuttur. Bir tür rota eylemi sınıfı yapmak mümkündür, ancak bu kodun bakımı daha basit, daha hızlı ve daha kolaydır.


-1

Bir kapatma , birinci sınıf veri olarak kullanılabilecek değişkenler de dahil olmak üzere rastgele bir kod parçasıdır.

Önemsiz bir örnek iyi eski qsort: Verileri sıralayan bir fonksiyondur. İki nesneyi karşılaştıran bir işleve bir işaretçi vermeniz gerekir. Yani bir fonksiyon yazmalısın. Bu işlevin parametreleştirilmesi gerekebilir, bu da ona statik değişkenler verdiğiniz anlamına gelir. Bu iş parçacığı güvenli değil demektir. DS’desin. Böylece bir fonksiyon işaretçisi yerine kapanması gereken bir alternatif yazıyorsunuz. Parametre problemini anında çözersiniz, çünkü parametreler kapatmanın bir parçası haline gelir. Kodunuzu daha okunaklı hale getirirsiniz, çünkü nesnelerin doğrudan sıralama işlevini çağıran kodla nasıl karşılaştırıldığını yazdınız.

Çok sayıda kazan plakası kodu ve uyarlanması gereken küçük ama önemli bir kod parçası gerektiren bir işlem yapmak istediğiniz birçok durum vardır. Bir fonksiyonu yazarak Demirbaş kodu önlemek kez bir kapatma parametre alır ve çevresindeki tüm Demirbaş kodu yapar ve sonra bir kapak olarak uyarlanabilir için bu fonksiyonu çağırmak ve kod geçebilir. Kod yazmak için çok kompakt ve okunabilir bir yol.

Bazı önemsiz olmayan kodların birçok farklı durumda yapılması gereken bir işleve sahipsiniz. Bu, ya kod çoğaltmayı ya da çarpıtılmış kodu üretmek için kullanılır; böylece önemsiz olmayan kod yalnızca bir kez kullanılabilir. Önemsiz: Bir değişkeni kapatırsınız ve ihtiyaç duyduğunuz her yerde en açık şekilde çağırırsınız.

Çoklu okuma: iOS / MacOS X, "bu kapatma işlemini bir arka plan iş parçasında gerçekleştir", "... ana iş parçasında", "... ana iş parçasında, bundan 10 saniye sonra" gibi işlevler gerçekleştirebilir. Çok okuyucuyu önemsiz kılıyor .

Asenkron çağrı: OP'nin gördüğü şey. İnternete giren herhangi bir çağrı veya zaman alabilecek başka herhangi bir şey (GPS koordinatlarını okumak gibi) sonucu bekleyemeyeceğiniz bir şeydir. Böylece, arka planda bir şeyler yapan işlevleriniz var ve sonra bittiğinde ne yapmaları gerektiğini söyleyen bir kapanış geçiriyorsunuz.

Bu küçük bir başlangıç. Kompakt, okunabilir, güvenilir ve verimli kod üretmek açısından kapakların devrim yaratan beş durumu.


-4

Bir kapatma, kullanılacağı bir yöntem yazmanın kısa yoludur. Ayrı bir yöntem bildirme ve yazma çabasından tasarruf etmenizi sağlar. Yöntemin yalnızca bir kez kullanılacağı ve yöntem tanımının kısa olduğu durumlarda kullanışlıdır. Fonksiyonun adını, dönüş tipini veya erişim değiştiricisini belirtmeye gerek olmadığından faydalar yazarak düşer. Ayrıca, kodu okurken, yöntemin tanımı için başka bir yere bakmak zorunda değilsiniz.

Yukarıdaki, Dan Avidar'ın anlamadığı Lambda İfadelerinin bir özetidir.

Bu, benim için kapanışların kullanımını açıklığa kavuşturdu çünkü alternatifleri (kapatma veya yöntem) ve her birinin yararlarını netleştiriyor.

Aşağıdaki kod bir kez ve yalnızca kurulum sırasında bir kez kullanılır. ViewDidLoad altındaki yerine yazarken, başka bir yerde arama yapmakta zorluk çeker ve kodun boyutunu kısaltır.

myPhoton!.getVariable("Temp", completion: { (result:AnyObject!, error:NSError!) -> Void in
  if let e = error {
    self.getTempLabel.text = "Failed reading temp"
  } else {
    if let res = result as? Float {
    self.getTempLabel.text = "Temperature is \(res) degrees"
    }
  }
})

Ek olarak, programın diğer bölümlerini engellemeden tamamlanmayan bir asenkron işlem sağlar ve bir kapatma, sonraki işlev çağrılarında tekrar kullanmak için bir değer tutacaktır.

Başka bir kapatma; bu bir değer yakalar ...

let animals = ["fish", "cat", "chicken", "dog"]
let sortedStrings = animals.sorted({ (one: String, two: String) -> Bool in return one > two
}) println(sortedStrings)

4
Ne yazık ki, bu kapanışları ve kuzuları karıştırır. Lambdas genellikle kapak kullanır, çünkü genellikle a) çok ters bir şekilde ve b) değişkenler de dahil olmak üzere belirli bir yöntem bağlamında ve buna bağlı olarak tanımlanırsa çok daha faydalıdır. Bununla birlikte, asıl kapamanın temelde birinci sınıf bir işlevi tanımlamak ve daha sonra kullanmak üzere etrafa iletmek olan bir lambda fikri ile hiçbir ilgisi yoktur.
Nathan Tuggy

Bu cevabı oylarken, bunu okuyun ... Ne zaman oy kullanmalıyım? Aşırı derecede özensiz, çaba sarf etmeyen bir yazıyla ya da açıkça ve belki de tehlikeli şekilde yanlış bir cevapla karşılaştığınızda indirimlerinizi kullanın. Cevabım, kapatma kullanımının nedenlerinden bazılarından yoksun olabilir, ancak ne korkunç bir şekilde özensiz ne de tehlikeli bir şekilde yanlıştır. İşlevsel programlama ve lambda gösterimi üzerine odaklanan birçok kapak ve tartışma vardır. Benim cevabımdaki tek şey, işlevsel programlamanın bu açıklamasının kapanışları anlamama yardımcı olduğu. Bu ve kalıcı değer.
Bendrix

1
Cevap belki de tehlikeli değil , ama kesinlikle "açıkça […] yanlış". Tamamlayıcı bir şekilde tamamlayıcı olan ve dolayısıyla sıklıkla kullanılan kavramlar için yanlış terimleri kullanır - tam olarak ayrımın açıklığının zorunlu olduğu yerlerde. Bu, eğer eklenmeden bırakılırsa, bunu okuyan programcılar için tasarım sorunlarına neden olabilir. (Ve söyleyebildiğim kadarıyla, kod örneğinde herhangi bir
kapanma

Nathan, bu kod örneği Swift'de bir kapanış. Benden şüphen varsa, bir başkasına sorabilirsin. Yani, eğer bir kapanışsa, oy kullanır mıydınız?
Bendrix

1
Apple , belgelerinde kapatarak tam olarak ne anlama geldiklerini tam olarak açıkça açıklar . "Kapaklar, etrafınızda dolaşıp kodunuzda kullanılabilecek bağımsız işlev bloklarıdır. Swift'deki kapaklar, C ve Objective-C'deki bloklara ve diğer programlama dillerindeki lambadalara benzer." Uygulamaya ve terminolojiye ulaştığınızda kafa karıştırıcı olabilir .
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.