"Kapanış" ve "lambda" arasındaki fark nedir?

Birisi açıklayabilir mi? Arkasındaki temel kavramları anlıyorum, ancak sıklıkla birbirlerinin yerine kullanıldığını görüyorum ve utanıyorum.

Ve şimdi buradayız, bunlar normal işlevden nasıl farklıdır?

645
21 окт. sker 21 Ekim ayarla 2008-10-21 06:12 '08, 06:12 2008-10-21 06:12
@ 10 cevap

Bir lambda sadece isimsiz bir fonksiyondur - isimsiz bir fonksiyondur. Şema gibi bazı dillerde, adlandırılmış işlevlere eşdeğerdirler. Aslında, fonksiyon tanımı lambda'nın içindeki değişkene bağlanması olarak yeniden yazılmıştır. Python gibi diğer dillerde, aralarında bazı (fazlalık yerine) farklılıklar vardır, ancak aynı şekilde farklı davranırlar.

Bir kapatma , tanımlandığı çevreyi kapatan herhangi bir fonksiyondur . Bu, parametre listesinde olmayan değişkenlere erişebileceği anlamına gelir. örnekler:

 def func(): return h def anotherfunc(h): return func() 

Bu, bir hataya neden olur, çünkü func , çevreyi başka bir anotherfunc - h , tanımsız anotherfunc eşittir. func sadece küresel ortamda kapalıdır. Bu işe yarayacak:

 def anotherfunc(h): def func(): return h return func() 

Burada func başka func ve python 2.3 ve üzeri (veya bunun gibi bir sayı) tanımlandığı için, neredeyse doğru kapanmalara sahip olduklarında (mutasyon hala çalışmıyor), bu başka bir anotherfunc ortamını kapattığı ve değişkenlere erişebileceği anlamına gelir. İçinde. Python 3.1+ 'de, mutasyon ayrıca nonlocal kullanırken de çalışır.

Bir diğer önemli nokta - func , başka bir anotherfunc artık değerlenmemiş olsa bile, Çarşamba günü bir başka anotherfunc kapatmaya devam anotherfunc . Bu kod da işe yarayacak:

 def anotherfunc(h): def func(): return h return func print anotherfunc(10)() 

Bu 10 yazdırır.

Bunun fark ettiğiniz gibi, lambda s ile ilgisi yoktur - bunlar iki farklı (ilgili) kavramlardır.

584
21 окт. Claudiu yanıtladı 21 Eki 2008-10-21 06:58 '08, 06:58 - 2008-10-21 06:58

Çoğu kişi işlevleri düşündüğünde, adlandırılmış işlevleri düşünür:

 function foo() { return "This string is returned from the 'foo' function"; } 

Elbette ismiyle çağırılırlar:

 foo(); //returns the string above 

Lambda ifadeleriyle adsız işlevlere sahip olabilirsiniz:

  @foo = lambda() {return "This is returned from a function without a name";} 

Yukarıdaki örnekte, atandığı değişkenden lambda'yı arayabilirsiniz:

 foo(); 
border=0

Değişkenlere anonim işlevler atamaktan daha yararlıdır, bunları üst düzey işlevlerden veya üst düzey işlevlerden geçirin, yani diğer işlevleri kabul eden / döndüren işlevler. Çoğu durumda, işlevin kullanılması gerekli değildir:

 function filter(list, predicate) { @filteredList = []; for-each (@x in list) if (predicate(x)) filteredList.add(x); return filteredList; } //filter for even numbers filter([0,1,2,3,4,5,6], lambda(x) {return (x mod 2 == 0)}); 

Kapatma, adlandırılmış veya isimsiz bir fonksiyon olabilir, ancak fonksiyonun tanımlandığı alandaki değişkenleri “kapattığında” olduğu bilinir. kapatma, kapatmanın kendisinde kullanılan herhangi bir harici değişken bulunan çevreye hala uygulanır. İşte adlandırılmış kapatma:

 @x = 0; function incrementX() { x = x + 1;} incrementX(); // x now equals 1 

Fazla bir şey gibi görünmüyor, peki ya hepsi farklı bir işlevdeyse, ya da dış işlevi incrementX geçirirseniz ne olur?

 function foo() { @x = 0; function incrementX() { x = x + 1; return x; } return incrementX; } @y = foo(); // y = closure of incrementX over foo.x y(); //returns 1 (yx == 0 + 1) y(); //returns 2 (yx == 1 + 1) 

Böylece işlevsel programlamada durum korumalı nesneler elde edersiniz. "IncrementX" adı gerekli olmadığından, bu durumda lambda kullanabilirsiniz:

 function foo() { @x = 0; return lambda() { x = x + 1; return x; }; } 
161
21 окт. Cevap Mark Cidade 21 Ekim 2008-10-21 06:46 '08, 06:46 - 2008-10-21 06:46

Bu StackOverflow sorusunun cevaplarında bile lambda ve kapanma çevresinde bir sürü kafa karışıklığı var. Uygulamayı belirli programlama dilleri veya diğer yabancı programcılar ile kapatmayı öğrenen rastgele programcılara sormak yerine, kaynağa gidin (her şeyin başladığı yer). Ve lambda ve kapılar, ilk elektronik bilgisayarlar ortaya çıkmadan önce 30'larda Alonso Kilisesi tarafından icat edilen Lambda Calculus'tan geldiğinden, bahsettiğim kaynak budur.

Lambda Matematik, dünyadaki en kolay programlama dilidir. Bu konuda yapabileceğin tek şey:

  • UYGULAMA: Bir ifadeyi diğerine, fx ile ifade etmek.
    (bir işlev çağrısı olarak düşünün, burada f bir işlevdir ve x tek parametredir)
  • ÖZET: Bir ifadede ortaya çıkan bir karakteri, bu karakterin sadece bir "değişken" gibi, değeri doldurmak için bekleyen boş bir alan olduğunu belirtmek için bağlar. Bu, Yunanca harf λ (lambda), sonra sembolik bir isim (örneğin, x ) ve sonra eklenmesiyle yapılır . . Bu ifade daha sonra ifadeyi bir parametre bekleyen bir işleve dönüştürür.
    Örneğin: λx.x+2 , x+2 ifadesini kabul eder ve bu ifadedeki x karakterinin bağlı bir değişken olduğunu bildirir - parametre olarak belirttiğiniz değerle değiştirilebilir.
    Lütfen bu şekilde tanımlanan fonksiyonun adsız olduğunu unutmayın - bir adı yoktur, bu nedenle henüz başvuramazsınız, ancak hemen arayabilir (uygulamayı hatırla?), Beklediği parametreyi ayarlayarak, örneğin: (λx.x+2) 7 . Daha sonra (bu durumda, hazır değer) ifadesi 7 , kullanılan lambda'nın x+2 alt ifadesinde x ile değiştirilir, böylece genel aritmetik kurallarına göre 7+2 indirilir, böylece 7+2 .

Bu yüzden sırlardan birine karar verdik:
lambda , yukarıdaki örnekte λx.x+2 olan anonim fonksiyondur.


Farklı programlama dilleri, işlevsel soyutlama (lambda) için farklı sözdizimlerine sahip olabilir. Örneğin, JavaScript’te şuna benzer:
 function(x) { return x+2; } 

ve hemen bir parametreye uygulayabilirsiniz:

 function(x) { return x+2; } (7) 

veya bu anonim işlevi (lambda) bazı değişkenlere kaydedebilirsiniz:

 var f = function(x) { return x+2; } 

bu aslında f adını verir, ona başvurmanızı ve birkaç kez sonra aramanızı sağlar, örneğin:

 alert( f(7) + f(10) ); // should print 21 in the message box 

Ama onu aramak zorunda değildin. Hemen onu arayabilirsin:

 alert( function(x) { return x+2; } (7) ); // should print 9 in the message box 

LISP'de lambdalar aşağıdaki gibi oluşturulur:

 (lambda (x) (+ x 2)) 

ve böyle bir lambda'yı hemen parametreye uygulayarak çağırabilirsiniz:

 ( (lambda (x) (+ x 2)) 7 ) 

<h / "> Tamam, şimdi başka bir gizemi çözmenin zamanı geldi: kapanış nedir. Bunu yapmak için, lambda ifadelerindeki semboller (değişkenler) hakkında konuşalım.

Dediğim gibi, lambda kısaltmasının yaptığı, alt ifadesinde istenen bir karakterdir, böylece değiştirilebilir bir parametre haline gelir. Böyle bir sembole sınır denir. Fakat ifadede başka karakterler varsa? Örneğin: λx.x/y+2 . Bu ifadede, x sembolü, lambda λx. kısaltmasıyla ilişkilidir λx. ondan önce. Ancak diğer karakter y sınırlanmış değildir - ücretsizdir. Ne olduğunu ya da nereden geldiğini bilmiyoruz, bu yüzden ne anlama geldiğini ve hangi değeri temsil ettiğini bilmiyoruz, bu nedenle, bu ifadeyi, ne anlama geldiğini bulana kadar değerlendiremeyiz.

Aslında, aynı şey diğer iki sembolle olur: 2 ve + . Bu iki karaktere o kadar aşinayız ki, genellikle bilgisayarın onları tanımadığını unuturuz ve örneğin bir yerde tanımlarken ne anlama geldiklerini söylememiz gerekir. kütüphanede veya dilin kendisinde.

İfadenin dışında başka bir yerde tanımlanan, çevreleyen bağlamında, ortamı olarak adlandırılan boş karakterleri düşünebilirsiniz. Çevre, bu ifadenin bir parçası olduğu (Qui-Gon Jinn’in söylediği gibi: “Her zaman büyük bir balık vardır”)) ya da bazı kütüphanelerde ya da dilin kendisinde (ilkel olarak) büyük bir ifade olabilir.

Bu, lambda ifadelerinin iki kategoriye ayrılmasını sağlar:

  • KAPALI ifadeler: Bu ifadelerde ortaya çıkan her karakter, bazı lambda soyutlamaları ile bağlantılıdır. Başka bir deyişle, kendi kendine yeterlidir; çevreleyen bağlamın değerlendirilmesini gerektirmezler. Bunlara ayrıca birleştiriciler de denir.
  • AÇIK ifadeler: Bu ifadelerdeki bazı karakterler ilişkili değildir - yani, içinde bulunan karakterlerin bazıları ücretsizdir ve bazı dış bilgiler gerektirir ve bu nedenle siz bu karakterlerin tanımlarını verinceye kadar değerlendirilemezler.

Tüm bu serbest karakterleri tanımlayan, bazı değerlere bağlayan bir ortam sağlayarak açık bir lambda ifadesini KAPATABİLİRSİNİZ (sayılar, dizeler, adsız işlevler, yani lambdalar, ne olursa olsun ...).

Ve işte kapanış kısmı:
Bir lambda ifadesinin kapatılması, dış ifadede (ortam) tanımlanan ve bu ifadede serbest karakterlere değer veren, onları serbest bırakmayan daha özel bir karakter kümesidir. Hala bazı "tanımsız" boş karakterler içeren açık bir lambda ifadesini, artık boş yazı içermeyen bir kapalı karaktere dönüştürür.

Örneğin, aşağıdaki lambda ifadesine λx.x/y+2 : λx.x/y+2 , x ilişkilidir ve y serbesttir, dolayısıyla ifade open ve y ne anlama geldiğini söylemediğiniz sürece değerlendirilemez. c + ve 2 , aynı zamanda ücretsizdir). Ancak böyle bir ortamın olduğunu varsayalım:

 { y: 3, +: [built-in addition], 2: [built-in number], q: 42, w: 5 } 

Bu ortam, lambda ifadelerimizdeki ( y , + , 2 ) tüm "tanımsız" (ücretsiz) karakterler ve birkaç ek karakter ( q , w ) için tanım sağlar: Tanımlamamız gereken karakterler, ortamın bir alt kümesidir:

 { y: 3, +: [built-in addition], 2: [built-in number] } 

ve bu sadece lambda ifadelerimizin kapanmasıdır:>

Başka bir deyişle, açık lambda ifadesini kapatır. Burada ismin kapalı olduğu ve bu yüzden bu konudaki cevapların birçoğu tam olarak doğru değildi: P


Peki neden yanılıyorlar? Neden birçoğu, kapatmaların bellekteki bazı veri yapıları veya kullandıkları dillerin bazı özellikleri olduğunu söylüyor veya neden kapanlarla lambdaları karıştırıyorlar?

Eh, kurumsal pazarlar Sun / Oracle, Microsoft, Google, vb. bu yapıları kendi dillerinde (Java, C #, Go vb.) çağırdıkları için suçluyorlar. Bunlara genellikle "kapatma" denir, bunlar sadece lambda olmalı. Veya, sözcük kapsamı uygulamak için kullandıkları belirli bir tekniği “kapatma” olarak adlandırıyorlar, yani. Bir işlevin, tanımı sırasında dış kapsamda tanımlanan değişkenlere erişebilmesi. Genellikle, fonksiyonun bu değişkenleri "çevrelediğini", yani dış fonksiyonun tamamlanmasından sonra tahrip edilmelerini önlemek için onları belirli bir veri yapısında yakaladığını söylerler. Ancak bu, her şeyden önce kendi terminolojisini kullandığı için post-folklorik "folklor etimolojisi" ve sadece işleri daha karmaşık hale getiren pazarlamadır.

Ve bu, söylediklerinde, onu kolayca yanlış olarak reddetmenize izin vermeyen küçük bir gerçeğin olması nedeniyle daha da kötüdür: P Açıklayayım:

Eğer lambda'yı birinci sınıf vatandaşlar olarak kullanan bir dil uygulamak istiyorsanız, onların çevreleyen bağlamlarında tanımlanmış sembolleri kullanmalarına izin vermelisiniz (yani, lambdalarınızdaki serbest değişkenleri kullanmak için). Ve çevreleyen işlev döndüğünde bile bu karakterlerin orada olması gerekir. Sorun, bu karakterlerin, işlev döndüğünde artık bulunmayacak olan bazı yerel işlev depolarına (genellikle çağrı yığında) bağlı olmasıdır. Bu nedenle, lambda'nın beklediğiniz gibi çalışması için, tüm bu serbest değişkenleri bir şekilde dış bağlamınızdan “yakalamanız” ve dış bağlam kaybolduğunda bile daha sonra saklamanız gerekir. Yani, lambda'nızın kapanmasını bulmanız (kullandığı tüm bu harici değişkenler) bulmanız ve başka bir yerde saklamanız (bir kopya oluşturarak veya onlar için yer hazırlayarak, yığında değil) ). Bu hedefe ulaşmak için kullandığınız asıl yöntem, dilinizin "uygulama detayı" dır. Burada önemli olan, lambda ortamınızdan bir yere kaydedilmesi gereken bir dizi serbest değişken olan kapanış.

İnsanların, kapanışı “kapatma” olarak uygulamak için kendi dil uygulamalarında kullandıkları gerçek veri yapısını çağırmaya başlaması çok uzun sürdü. Yapı genellikle şöyle görünür:

 Closure { [pointer to the lambda function machine code], [pointer to the lambda function environment] } 

ve bu veri yapıları, diğer fonksiyonlara parametre olarak iletilir, fonksiyonlardan döndürülür ve lambdaları temsil etmek ve çevrelerine erişmelerine izin vermek için değişkenlerin içinde depolanır ve makine kodunun bu bağlamda çalışmasını sağlar. Ancak bu, kapanmanın kendisini değil, kapanışı uygulamanın bir yoludur (birçoğundan biri).

Yukarıda açıkladığım gibi, bir lambda ifadesinin kapatılması, ortamında, bu lambda ifadesinde yer alan serbest değişkenlere değer veren, ifadesini etkin bir şekilde kapatan (değerlendirilemeyen bir açık lambda ifadesini döndürerek ancak kapalı bir lambda ifadesinde döndüren, bir tanım alt kümesidir) daha sonra değerlendirilebilir, çünkü içerdiği tüm karakterler şimdi tanımlanmıştır).

Geriye kalan her şey, bu kavramların gerçek köklerini bilmeyen programcıların ve dil sağlayıcıların sadece "yük kültü" ve "sihirli büyüsü" dür.

Umarım bu sorularınızı cevaplar. Ancak herhangi bir takip sorunuz varsa, yorumlarda onlara sormakta özgürsünüz ve ben daha iyi açıklamaya çalışacağım.

124
27 апр. Cevap SasQ 27 nisan verildi . 2016-04-27 04:18 '16, 04:18 2016-04-27 04:18

Tüm kapaklar lambda değildir ve tüm lambdalar kapanmaz değildir. Her ikisi de işlevdir, ancak bildiğimiz şekilde olması gerekmez.

Lambda, esas olarak yerleşik işlevler için standart bir yöntem değil, yerleşik olarak tanımlanmış bir işlevdir. Lambdalar sıklıkla nesne olarak geçirilebilir.

Kapanış, etrafındaki durumu çevreleyen, vücudunun dışındaki alanlara gönderme yapan bir fonksiyondur. İç içe geçmiş durum bir kapatma çağrısı şeklinde kalır.

Nesne yönelimli bir dilde, kapatma genellikle nesneler aracılığıyla sağlanır. Bununla birlikte, bazı OO dilleri (örneğin, C #), durumu etkinleştirmek için nesneleri olmayan, tamamen işlevsel dillerin (lisp gibi) sağladığı kapanma tanımlarına daha yakın olan özel fonksiyonlar uygular.

İlginç bir şekilde, C # da Lambdas ve Kapakların tanıtılması, temel kullanıma daha yakın işlevsel programlamaya yol açar.

49
21 окт. Cevap 21 Ekim tarihinde Michael Brown tarafından verildi. 2008-10-21 06:29 '08, 06:29 2008-10-21 06:29

Çok basit: lambda bir dil yapısıdır, yani. anonim işlevler için yalnızca sözdizimi; kapatma işlemi uygulamanın bir yoludur - veya bu konuda herhangi bir birinci sınıf işlevi çağırır veya isimsiz kullanır.

Daha doğrusu, kapatma, birinci sınıf fonksiyonun fonksiyonunun çalışma zamanında, bu kodda kullanılan tüm yerel olmayan değişkenler için bir "kod" ve ortam "kapatma" olarak temsil edilmesidir. Bu nedenle, bu değişkenler, oluştukları dış alanlar zaten tamamlandığında bile mevcuttur.

Ne yazık ki, birinci sınıf değerler olarak işlevleri desteklemeyen veya yalnızca sakat biçimli bir biçimde destekleyen birçok dil vardır. Bu nedenle, insanlar genellikle "gerçek olanı" ayırt etmek için "kapatma" terimini kullanırlar.

13
19 марта '14 в 11:31 2014-03-19 11:31 Cevap Andreas Rossberg tarafından 19 Mart 14:31 tarihinde saat 11: 31'de 2014-03-19 11:31

Programlama dilleri açısından bakıldığında, bunlar tamamen iki farklı şeydir.

Temel olarak, tüm Turing dili için, örneğin sadece çok sınırlı unsurlara ihtiyacımız var. soyutlamalar, uygulamalar ve kısaltmalar. Soyutlama ve uygulama lamdba ifadesini geliştirmenin bir yolunu sağlar ve redüksiyon lambda ifadesinin anlamını boşaltır.

Lambda, hesaplama sürecini soyutlamanın bir yolunu sağlar. örneğin, iki sayının toplamını hesaplamak için, iki parametreyi x, y alan ve x + y döndüren bir işlem soyutlanabilir. Şemada, olarak yazabilirsiniz

 (lambda (xy) (+ xy)) 

Parametreleri yeniden adlandırabilirsiniz, ancak tamamladığı görev değişmez. Neredeyse tüm programlama dillerinde, işlevler adı verilen bir lambda ifadesi verebilirsiniz. Fakat çok fazla fark yok, kavramsal olarak sadece sözdizimsel şeker olarak görülebilirler.

Tamam, şimdi bunun nasıl uygulanabileceğini hayal edin. Ne zaman ne zaman bir lambda ifadesi uyguladığımız bazı ifadelere, örneğin

 ((lambda (xy) (+ xy)) 2 3) 

Parametreleri basitçe değerlendirilmesi gereken bir ifadeyle değiştirebiliriz. Bu model zaten çok güçlü. Ancak bu model, örneğin karakterlerin anlamını değiştirmemize izin vermiyor. Bir durum değişikliğini simüle edemiyoruz. Dolayısıyla daha karmaşık bir modele ihtiyacımız var. Kısaltmak için, lambda ifadesinin değerini ne zaman hesaplamak istersek, ortama (veya tabloya) bir kaç karakter ve karşılık gelen değeri koyarız. Ardından geri kalan (+ xy) tablodaki ilgili karakterleri arayarak değerlendirilir. Şimdi, çevrede doğrudan kullanım için bazı ilkeler sağlarsak, durum değişikliklerini simüle edebiliriz!

Bu arka planla, bu özelliği inceleyin:

 (lambda (xy) (+ xyz)) 

Bir lambda ifadesini değerlendirirken, xy'nin yeni bir tabloya bağlanacağını biliyoruz. Ama nasıl ve nereye bakabiliriz? Aslında, z serbest değişken olarak adlandırılır. Z içeren bir dış ortam olmalı. Aksi takdirde, ifadenin değeri yalnızca x ve y'nin bağlanmasıyla belirlenemez. Bunu açıklığa kavuşturmak için şemada bir şeyler yazabilirsiniz:

 ((lambda (z) (lambda (xy) (+ xyz))) 1) 

Böylece, z dış tabloda 1'e bağlanacaktır. Hala iki parametre alan bir işlev alıyoruz, ancak bunun gerçek anlamı dış ortama da bağlı. Başka bir deyişle, dış ortam serbest değişkenlerle kapatılmıştır. Set! İle durum bilgisi olan bir işlev yapabiliriz, yani. Bu matematik anlamında bir fonksiyon değildir. Döndüğü şey sadece girdiye değil, z'ye de bağlı.

Bu zaten çok iyi bildiğiniz bir şeydir, nesnelerin metodu hemen hemen daima nesnelerin durumuna bağlıdır. Bu yüzden bazı insanlar “kapanmanın kötü insan objeleri” demiştir. Ancak, nesneleri birinci sınıf özellikleri de sevdiğimiz için kapalı insanlar olarak da görebiliriz.