Birisi açıklayabilir mi? Arkalarındaki temel kavramları anlıyorum ama sık sık birbirlerinin yerine kullanıldığını görüyorum ve kafam karışıyor.
Ve şimdi burada olduğumuza göre, normal bir işlevden nasıl farklılar?
Birisi açıklayabilir mi? Arkalarındaki temel kavramları anlıyorum ama sık sık birbirlerinin yerine kullanıldığını görüyorum ve kafam karışıyor.
Ve şimdi burada olduğumuza göre, normal bir işlevden nasıl farklılar?
Yanıtlar:
Bir lambda sadece anonim bir işlevdir - adı olmayan bir işlev. Şema gibi bazı dillerde, adlandırılmış işlevlere eşdeğerdir. Aslında, fonksiyon tanımı bir lambda'yı dahili olarak bir değişkene bağlama olarak yeniden yazılır. Python gibi diğer dillerde, aralarında bazı (oldukça gereksiz) farklılıklar vardır, ancak aksi takdirde aynı şekilde davranırlar.
Bir kapak herhangi bir fonksiyondur fazla kapatır çevre tanımlandığı edildiği. Bu, parametre listesinde olmayan değişkenlere erişebileceği anlamına gelir. Örnekler:
def func(): return h
def anotherfunc(h):
return func()
Bunun nedeni, hataya neden olur func
değil üzerinde yakın çevre anotherfunc
- h
tanımlanmamış. func
sadece küresel çevre üzerinde kapanır. Bu çalışacak:
def anotherfunc(h):
def func(): return h
return func()
Çünkü burada, func
tanımlanan anotherfunc
piton 2.3 ve daha büyük (veya bu gibi bazı numarası) onlar ne zaman ve neredeyse buna sahip kapanışları düzeltmek (mutasyon hala iş değil), bu araçlar üzerinde kapatır anotherfunc
içinde 'nin çevre ve kutu erişim değişkenleri o. Python 3.1+ sürümünde, mutasyon anahtar kelimeyi kullanırken denonlocal
çalışır .
Başka bir önemli nokta - artık değerlendirilmese bile çevrenin func
kapanmasına devam edecektir . Bu kod da çalışır:anotherfunc
anotherfunc
def anotherfunc(h):
def func(): return h
return func
print anotherfunc(10)()
Bu 10 basacaktır.
Bu, fark ettiğiniz gibi, lambda s ile ilgisi yoktur - iki farklı (ilgili olmasına rağmen) kavramlardır.
Bu StackOverflow sorununun cevaplarında bile lambdas ve kapaklar etrafında çok fazla karışıklık var. Uygulamadan belirli programlama dilleri veya diğer clueless programcılar ile kapanışları öğrenen rastgele programcılara sormak yerine, kaynağa (her şeyin başladığı yer) bir yolculuk yapın . Lamdalar ve kapaklar , ilk elektronik bilgisayarlar bile var olmadan önce 30'larda Alonzo Kilisesi tarafından icat edilen Lambda Calculus'tan geldiğinden, bahsettiğim kaynak bu .
Lambda Matematik dünyadaki en basit programlama dilidir. İçinde yapabileceğiniz tek şey: ►
f x
. f
ve x
tek parametresidir)λ
(lambda), ardından sembolik adı (örneğin x
), ardından .
ifadeden önce bir nokta ekleyerek yapılır . Bu daha sonra ifadeyi bir parametre bekleyen bir işleve dönüştürür . Örneğin: ifadeyi alır ve bu ifadedeki sembolün bağlı bir değişken olduğunu söyler - parametre olarak sağladığınız bir değerle değiştirilebilir.
Bu şekilde tanımlanan fonksiyonun anonim olduğunu unutmayınλx.x+2
x+2
x
(λx.x+2) 7
. Daha sonra ifade (bu durumda değişmez bir değer) 7
, uygulanan x
lambda'nın alt ifadesinde olduğu gibi ikame edilir x+2
, böylece elde edersiniz 7+2
, bu daha sonra 9
yaygın aritmetik kurallarına indirgenir .Biz gizemlerinden biri çözdük Yani:
lambda olan anonim işlev Yukarıdaki örnekten, λx.x+2
.
function(x) { return x+2; }
ve hemen aşağıdaki gibi 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 etkin bir şekilde bir ad verir, ona f
başvurmanıza ve daha sonra birkaç kez çağırmanıza olanak tanır, örneğin:
alert( f(7) + f(10) ); // should print 21 in the message box
Ama ismini vermek zorunda değildin. Hemen arayabilirsin:
alert( function(x) { return x+2; } (7) ); // should print 9 in the message box
LISP'de lambdalar şu şekilde yapılır:
(lambda (x) (+ x 2))
ve böyle bir lambda'yı hemen bir parametreye uygulayarak çağırabilirsiniz:
( (lambda (x) (+ x 2)) 7 )
Söylediğim gibi, lambda soyutlamasının yaptığı, alt ifadesinde bir sembolü bağlamaktır , böylece ikame edilebilir bir parametre haline gelir . Böyle bir sembol bağlı olarak adlandırılır . Fakat ifadede başka semboller varsa ne olur? Örneğin: λx.x/y+2
. Bu ifadede sembol x
, λx.
kendisinden önceki lambda soyutlaması ile bağlıdır . Ancak diğer sembol, y
bağlı değildir - ücretsizdir . Ne olduğunu ve nereden geldiğini bilmiyoruz, bu yüzden ne anlama geldiğini ve hangi değeri temsil ettiğini bilmiyoruz ve bu nedenle ne y
anlama geldiğini anlayana kadar bu ifadeyi değerlendiremeyiz .
Aslında, aynı diğer iki sembollerle gider 2
ve +
. Bu iki simgeye o kadar aşinayız ki, genellikle bilgisayarın onları tanımadığını unuturuz ve bunları bir yerde, örneğin bir kütüphanede veya dilin kendisinde tanımlayarak ne anlama geldiğini söylememiz gerekir.
Aklınıza gelebilecek ücretsiz olarak adlandırılır onun "dair bağlam", içinde, ifade dışında başka bir yerde tanımlandığı gibi semboller ortamı . Ortam, bu ifadenin bir parçası olduğu daha büyük bir ifade olabilir (Qui-Gon Jinn'in dediği gibi: "Her zaman daha büyük bir balık vardır";)) veya bir kütüphanede veya dilin kendisinde ( ilkel olarak ).
Bu, lambda ifadelerini iki kategoriye ayırmamızı sağlar:
Bir kapatabilirsiniz açık sağlayarak lambda ifade ortamını bazı değerlere onları bağlanarak bu bedava sembolleri tanımlar (lambdas aka sayılar, dizeleri, anonim fonksiyonlar ne olursa olsun ...).
Ve buraya gelen kapatma kısmı: kapatma a lambda ifadesi değerleri vermek dış bağlamda (çevre) tanımlanan sembollerin bu özel kümesidir serbest semboller artık onları özgür olmayan hale bu ifadede. Halen bazı "tanımsız" serbest semboller içeren açık bir lambda ifadesini, artık ücretsiz sembol içermeyen kapalı bir ifadeye çevirir .
Örneğin, aşağıdaki lambda ifadesine sahipseniz: λx.x/y+2
sembol x
sınırlıdır, sembol y
ücretsizken, bu nedenle ifade open
ne y
anlama geldiğini (ve aynı ile +
ve 2
de ücretsizdir) söylemediğiniz sürece ifade değerlendirilir ve değerlendirilemez . Ancak, bunun gibi bir ortamınız olduğunu varsayalım :
{ y: 3,
+: [built-in addition],
2: [built-in number],
q: 42,
w: 5 }
Bu ortam bizim lambda ifadeden bütün "tanımsız" (ücretsiz) semboller için malzemeler tanımları ( y
, +
, 2
) ve birkaç ekstra semboller ( q
, w
). Tanımlamamız gereken semboller ortamın bu alt kümesidir:
{ y: 3,
+: [built-in addition],
2: [built-in number] }
ve bu tam olarak lambda ifademizin kapanışı :
Başka bir deyişle, açık bir lambda ifadesini kapatır . İlk etapta isim kapatma buradan geliyor ve bu yüzden bu konudaki pek çok insanın cevapları tam olarak doğru değil: P
Sun / Oracle, Microsoft, Google vb. Kurumsal marketoidler suçlanacak, çünkü bu yapıları kendi dillerinde (Java, C #, Go vb.) Genellikle lambda olması gereken şeyi "kapatmalar" olarak adlandırırlar. Ya da sözcüksel kapsam belirleme uygulamak için kullandıkları belirli bir tekniği "kapanışlar" olarak adlandırırlar, yani bir fonksiyonun tanımlandığı sırada dış kapsamında tanımlanan değişkenlere erişebilir. Genellikle işlevin bu değişkenleri "kapsadığını", yani dış işlevin yürütülmesi bittikten sonra yok edilmelerini önlemek için bunları bir veri yapısına yakaladığını söylerler. Ama bu sadece yapımı bitti sonrası factum , sadece şeyler daha kafa karıştırıcı hale getirir "folklor etimoloji" ve pazarlama,
Ve söyledikleri şeyde her zaman biraz gerçek olduğu gerçeği nedeniyle daha da kötüsü, bu da onu yanlış olarak kolayca reddetmenize izin vermez: P Açıklayayım:
Birinci sınıf vatandaş olarak lambdas kullanan bir dil uygulamak istiyorsanız, onların çevrelerindeki bağlamda tanımlanan sembolleri kullanmasına izin vermeniz gerekir (yani, lambdaslarınızda serbest değişkenler kullanmak için). Ve bu semboller, çevredeki işlev geri döndüğünde bile orada olmalıdır. Sorun, bu sembollerin, işlevin bazı yerel depolamasına (genellikle çağrı yığınında) bağlı olmasıdır; bu, işlev geri döndüğünde artık olmayacaktır. Bu nedenle, bir lambda'nın beklediğiniz şekilde çalışması için, tüm bu serbest değişkenleri bir şekilde dış bağlamından "yakalamanız" ve dış bağlam gitmiş olsa bile bunları daha sonra için saklamanız gerekir. Yani, kapatmayı bulmanız gerekiyorlambda'nızı (kullandığı tüm bu harici değişkenleri) ve başka bir yerde saklayın (bir kopya oluşturarak veya onlar için önceden, yığının dışında bir yerde hazırlayarak). Bu hedefe ulaşmak için kullandığınız gerçek yöntem, dilinizin "uygulama ayrıntısıdır". Burada önemli olan kapatma , kümesidir serbest değişkenler gelen çevre ihtiyaç yerde kaydedilmesini senin lambda.
İnsanların, dilin uygulamalarında kullandıkları gerçek veri yapısını kapatmayı "kapatmanın" kendisi olarak uygulamak için çağırmaları çok uzun sürmedi. Yapı genellikle şöyle görünür:
Closure {
[pointer to the lambda function's machine code],
[pointer to the lambda function's environment]
}
ve bu veri yapıları, diğer işlevlere parametre olarak aktarılır, işlevlerden döndürülür ve değişkenlerde depolanır, lambdaları temsil eder ve bu ortamlarda çalışacak çevreye ve makine koduna erişmelerine izin verir. Ancak bu , kapamanın kendisini değil , bir kapama gerçekleştirmenin bir yoludur .
Yukarıda açıkladığım gibi, lambda ifadesinin kapatılması, çevresindeki lambda ifadesinde bulunan serbest değişkenlere değerler veren, ifadeyi etkili bir şekilde kapatarak ( henüz değerlendirilemeyen açık bir lambda ifadesini çevirerek) tanımların alt kümesidir. bir kapalı içinde yer alan tüm semboller, tanımlı olduğu, daha sonra değerlendirilebilir lambda ifade).
Başka bir şey, programcıların ve dil satıcılarının bu kavramların gerçek köklerinden habersiz bir "kargo kültü" ve "voo-doo büyüsü" dür.
Umarım bu sorularınızı cevaplar. Ancak herhangi bir takip sorunuz varsa, yorumlarda sormaktan çekinmeyin ve daha iyi açıklamaya çalışacağım.
Çoğu insan işlevleri düşündüğünde, adlandırılmış işlevleri düşünür :
function foo() { return "This string is returned from the 'foo' function"; }
Bunlar elbette isimle adlandırılır:
foo(); //returns the string above
Lambda ifadeleri ile anonim işlevlere sahip olabilirsiniz :
@foo = lambda() {return "This is returned from a function without a name";}
Yukarıdaki örnekte, lambda'yı atandığı değişken üzerinden çağırabilirsiniz:
foo();
Bununla birlikte, değişkenlere anonim işlevler atamaktan daha kullanışlı olan, bunları daha üst düzey işlevlere veya diğer işlevlerden, yani diğer işlevleri kabul eden / döndüren işlevlerden geçirmektir. Bu gibi birçok durumda, bir işlevi adlandırmak gereksizdir:
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)});
Bir kapatma adlandırılmış veya anonim bir işlev olabilir, ancak işlevin tanımlandığı kapsamdaki değişkenleri "kapatırsa" olarak bilinir; yani, kapatma, yine de, kapanış kendisi. İşte adlandırılmış bir kapatma:
@x = 0;
function incrementX() { x = x + 1;}
incrementX(); // x now equals 1
Çok fazla görünmüyor ama ya bütün bunlar başka bir işlevdeyse ve incrementX
harici bir işleve geçtiyseniz ?
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 (y.x == 0 + 1)
y(); //returns 2 (y.x == 1 + 1)
Fonksiyonel programlamada durum bilgisi olan nesneleri bu şekilde elde edersiniz. "İncrementX" isimlendirilmesine gerek olmadığından, bu durumda lambda kullanabilirsiniz:
function foo()
{ @x = 0;
return lambda()
{ x = x + 1;
return x;
};
}
Tüm kapaklar lambda değildir ve tüm lambdalar kapak değildir. Her ikisi de işlevlerdir, ancak bilmeye alışık olduğumuz gibi değil.
Bir lambda esasen standart fonksiyon bildirme yönteminden ziyade satır içi olarak tanımlanan bir fonksiyondur. Lambdalar genellikle nesneler olarak geçirilebilir.
Kapatma, vücudunun dışındaki alanlara başvurarak çevresini çevreleyen bir işlevdir. Ekteki durum, kapatmanın çağrıları boyunca kalır.
Nesneye yönelik bir dilde, kapaklar normalde nesneler aracılığıyla sağlanır. Bununla birlikte, bazı OO dilleri (örneğin C #), durumu içine alacak nesneler olmayan tamamen işlevsel diller (lisp gibi) tarafından sağlanan kapakların tanımına daha yakın olan özel işlevler uygular .
İlginç olan, Lambdas ve Closures'ın C # 'a girmesinin fonksiyonel programlamayı genel kullanıma daha yakın hale getirmesidir.
Bu kadar basit: lambda bir dil yapısıdır, yani anonim işlevlerin sözdizimi; kapatma, onu uygulamak için kullanılan bir tekniktir - veya bu konuda adlandırılmış veya anonim herhangi bir birinci sınıf işlevdir.
Daha kesin olarak, bir kapatma, birinci sınıf bir işlevin çalışma zamanında, "kodunun" bir çifti ve bu kodda kullanılan tüm yerel olmayan değişkenler üzerinde "kapanan" bir ortam olarak nasıl temsil edildiğidir. Bu şekilde, bu değişkenlere, kaynaklandıkları dış kapsamlardan çıkılsa bile erişilebilir.
Ne yazık ki, birinci sınıf değerler olarak fonksiyonları desteklemeyen veya sadece sakatlanmış biçimde destekleyen pek çok dil var. İnsanlar genellikle "gerçek olanı" ayırt etmek için "kapatma" terimini kullanırlar.
Programlama dilleri açısından tamamen iki farklı şeydir.
Temel olarak bir Turing tam dili için, yalnızca soyutlama, uygulama ve azaltma gibi çok sınırlı öğelere ihtiyacımız var. Soyutlama ve uygulama, lamdba ifadesini oluşturma yolunuzu sağlar ve azaltma, lambda ifadesinin anlamını dertermine eder.
Lambda, hesaplama sürecini özetleyebileceğiniz bir yol sağlar. örneğin, iki sayının toplamını hesaplamak için, iki parametre x, y alan ve x + y döndüren bir işlem soyutlanabilir. Şemada şöyle yazabilirsiniz:
(lambda (x y) (+ x y))
Parametreleri yeniden adlandırabilirsiniz, ancak tamamladığı görev değişmez. Hemen hemen tüm programlama dillerinde lambda ifadesine işlevler adı verilen bir ad verebilirsiniz. Ancak çok fazla fark yoktur, kavramsal olarak sadece sözdizimi şekeri olarak kabul edilebilirler.
Tamam, şimdi bunun nasıl uygulanabileceğini hayal edin. Lambda ifadesini bazı ifadelere her uyguladığımızda, ör.
((lambda (x y) (+ x y)) 2 3)
Parametreleri değerlendirilecek ifadeyle değiştirebiliriz. Bu model zaten çok güçlü. Ancak bu model, sembollerin değerlerini değiştirmemize izin vermez, örn. Durum değişikliğini taklit edemeyiz. Bu yüzden daha karmaşık bir modele ihtiyacımız var. Kısa yapmak için, lambda ifadesinin anlamını hesaplamak istediğimizde, bir sembol çifti ve karşılık gelen değeri bir ortama (veya tabloya) koyarız. Daha sonra geri kalanı (+ xy) tablodaki karşılık gelen sembollere bakarak değerlendirilir. Şimdi çevre üzerinde doğrudan çalışmak için bazı temel ilkeler sağlarsak, durum değişikliklerini modelleyebiliriz!
Bu arka planla, bu işlevi kontrol edin:
(lambda (x y) (+ x y z))
Lambda ifadesini değerlendirdiğimizde, xy'nin yeni bir tabloya bağlanacağını biliyoruz. Ama nasıl ve nerede z arayabiliriz? Aslında z, serbest değişken olarak adlandırılır. Z içeren bir dış ortam olmalıdır. Aksi takdirde, ifadenin anlamı sadece x ve y bağlanması ile belirlenemez. Bunu netleştirmek için, şemada aşağıdaki gibi bir şeyler yazabilirsiniz:
((lambda (z) (lambda (x y) (+ x y z))) 1)
Yani z bir dış tabloda 1'e bağlı olacaktır. Hala iki parametreyi kabul eden bir fonksiyon elde ediyoruz, ancak bunun gerçek anlamı dış ortama da bağlı. Başka bir deyişle, dış ortam serbest değişkenleri kapatır. Set! Yardımı ile fonksiyonu durumsal hale getirebiliriz, yani matematik anlamında bir fonksiyon değildir. Ne döndürdüğü sadece girdiye değil, z'ye de bağlıdır.
Bu zaten çok iyi bildiğiniz bir şey, bir nesne yöntemi neredeyse her zaman nesnelerin durumuna dayanır. Bu yüzden bazı insanlar "kapanışlar fakir insanın nesneleridir" derler. Fakat birinci sınıf fonksiyonları gerçekten sevdiğimiz için nesneleri de fakir insanın kapanışı olarak düşünebiliriz.
Düzeni, gerçek kapanışları olan en eski dillerden biri olduğu için fikirleri göstermek için kullanıyorum. Buradaki tüm malzemeler SICP bölüm 3'te çok daha iyi sunulmaktadır.
Özetle, lambda ve kapatma gerçekten farklı kavramlardır. Bir lambda bir işlevdir. Kapatma, bir çift lambda ve lambda'yı kapatan ilgili ortamdır.
Kavram yukarıda açıklananla aynıdır, ancak PHP geçmişindeyseniz, bu PHP kodunun kullanımını açıklar.
$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, function ($v) { return $v > 2; });
işlev ($ v) {return $ v> 2; } lambda fonksiyon tanımıdır. Bir değişkende saklayabiliriz, böylece tekrar kullanılabilir:
$max = function ($v) { return $v > 2; };
$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, $max);
Şimdi, filtrelenmiş dizide izin verilen maksimum sayıyı değiştirmek isterseniz ne olur? Başka bir lambda işlevi yazmanız veya bir kapatma oluşturmanız gerekir (PHP 5.3):
$max_comp = function ($max) {
return function ($v) use ($max) { return $v > $max; };
};
$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, $max_comp(2));
Kapatma, kendi ortamında değerlendirilen ve işlev çağrıldığında erişilebilen bir veya daha fazla bağlı değişkene sahip bir işlevdir. Oyunda bir takım kavramların olduğu fonksiyonel programlama dünyasından geliyorlar. Kapaklar lambda işlevleri gibidir, ancak kapağın tanımlandığı dış ortamdaki değişkenlerle etkileşime girme kabiliyetine sahip oldukları için daha akıllıdır.
İşte PHP'nin daha basit bir örneği:
$string = "Hello World!";
$closure = function() use ($string) { echo $string; };
$closure();
Bu soru eski ve birçok cevap aldı.
Şimdi resmi olmayan kapanış projeleri olan Java 8 ve Resmi Lambda ile sorunu yeniden canlandırıyor.
Java bağlamında cevap ( Lambdas ve kapanışlar üzerinden - fark nedir? ):
"Kapatma, serbest değişkenlerinin her birini bir değere bağlayan bir ortamla eşleştirilmiş bir lambda ifadesidir. Java'da lambda ifadeleri, kapaklar aracılığıyla uygulanacaktır, bu nedenle iki terim toplulukta birbirinin yerine kullanılmaya başlanmıştır."
Basitçe söylemek gerekirse, kapanış kapsamla ilgili bir numaradır, lambda anonim bir işlevdir. Lambda ile daha zarif bir şekilde kapanmayı gerçekleştirebiliriz ve lambda genellikle daha yüksek bir işleve geçirilen bir parametre olarak kullanılır
Lambda ifadesi sadece anonim bir işlevdir. örneğin düz java'da şöyle yazabilirsiniz:
Function<Person, Job> mapPersonToJob = new Function<Person, Job>() {
public Job apply(Person person) {
Job job = new Job(person.getPersonId(), person.getJobDescription());
return job;
}
};
burada Sınıf Fonksiyonu sadece java kodunda inşa edilmiştir. Şimdi mapPersonToJob.apply(person)
onu kullanmak için bir yeri arayabilirsiniz . bu sadece bir örnek. Bunun için sözdizimi olmadan önce bir lambda. Lambdas bunun için kısa bir yol.
Kapanış:
bir Lambda bu kapsam dışındaki değişkenlere erişebildiğinde kapanır. sihrini söyleyebilirim, sihirli bir şekilde yaratıldığı ortamın etrafına sarılabilir ve değişkenleri kapsamının dışında kullanabilir (dış kapsam. açık olmak gerekirse, bir kapatma, bir lambda'nın DIŞ KAPSAMINA erişebileceği anlamına gelir.
Kotlin'de bir lambda her zaman kapanışına erişebilir (dış kapsamındaki değişkenler)
Bir işlevin, işlemi gerçekleştirmek için harici değişken kullanıp kullanmadığına bağlıdır.
Dış değişkenler - bir işlevin kapsamı dışında tanımlanan değişkenler.
Lambda ifadeleri vatansızdır çünkü işlemleri gerçekleştirmek için parametrelere, dahili değişkenlere veya sabitlere bağlıdır.
Function<Integer,Integer> lambda = t -> {
int n = 2
return t * n
}
İşlemleri gerçekleştirmek için parametreler ve sabitlerle birlikte harici değişkenler (yani işlev gövdesinin kapsamı dışında tanımlanan değişken) kullandığı için kapanışlar durumu korur .
int n = 2
Function<Integer,Integer> closure = t -> {
return t * n
}
Java kapatma oluşturduğunda, n değişkenini işlevle birlikte tutar, böylece diğer işlevlere aktarıldığında veya herhangi bir yerde kullanıldığında başvurulabilir.