Jaka jest różnica między „zamknięciem” a „lambda”?

811

Czy ktoś mógłby wyjaśnić? Rozumiem podstawowe pojęcia, które się za nimi kryją, ale często widzę, że są używane zamiennie i się mylę.

A skoro już tu jesteśmy, czym różnią się od zwykłej funkcji?

sker
źródło
85
Lambdy są konstrukcją językową (funkcje anonimowe), zamknięcia są techniką implementacji do implementacji funkcji pierwszej klasy (anonimowych lub nie). Niestety, wielu ludzi często myli to.
Andreas Rossberg,
Informacje na temat zamknięć PHP można znaleźć na stronie php.net/manual/en/class.closure.php . Nie jest to oczekiwanie od programisty JavaScript.
PaulH
Odpowiedź SasQ jest doskonała. IMHO to pytanie byłoby bardziej przydatne dla użytkowników SO, gdyby kierowało widzów do tej odpowiedzi.
AmigoNico,

Odpowiedzi:

703

Lambda jest tylko anonimowa funkcja - funkcja określona bez nazwy. W niektórych językach, takich jak Schemat, są one równoważne nazwanym funkcjom. W rzeczywistości definicja funkcji jest przepisywana jako wewnętrzne powiązanie lambda ze zmienną. W innych językach, takich jak Python, istnieją pewne (raczej niepotrzebne) różnice między nimi, ale zachowują się w ten sam sposób inaczej.

Zamknięcie jest dowolną funkcją, która zamyka się w środowisku , w którym została ona zdefiniowana. Oznacza to, że może uzyskać dostęp do zmiennych, których nie ma na liście parametrów. Przykłady:

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

Spowoduje to błąd, ponieważ funcnie zamyka środowiska w anotherfunc- hjest niezdefiniowany. funczamyka się tylko w globalnym środowisku. To zadziała:

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

Ponieważ tutaj funcjest zdefiniowany w anotherfuncPythonie 2.3 i nowszych (lub jakaś liczba takich jak ta), kiedy prawie poprawne są zamknięcia (mutacja nadal nie działa), oznacza to, że zamyka się w anotherfunc środowisku i może uzyskiwać dostęp do zmiennych wewnątrz to. W Pythonie 3.1 i nowsze, mutacja działa też przy korzystaniu z nonlocalsłowa kluczowego .

Kolejna ważna kwestia - funcbędzie nadal zamykać anotherfuncśrodowisko, nawet jeśli nie jest już oceniane anotherfunc. Ten kod będzie również działać:

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

print anotherfunc(10)()

Spowoduje to wydrukowanie 10.

Jak zauważyłeś, nie ma to nic wspólnego z lambda - są to dwa różne (choć powiązane) pojęcia.

Claudiu
źródło
1
Claudiu, według mojej niepewnej wiedzy, python nigdy nie miał poprawnych zamknięć. Czy naprawili problem zmienności, gdy nie patrzyłem? Całkiem możliwe ...
simon
simon - masz rację, wciąż nie są w pełni poprawne. myślę, że Python 3.0 doda hack, aby obejść ten problem. (coś w rodzaju „nielokalnego” identyfikatora). zmienię mój ansewr, aby to odzwierciedlić.
Claudiu
2
@AlexanderOrlov: Są to zarówno lambdas, jak i zamknięcia. Java miała wcześniej zamknięcia za pośrednictwem anonimowych klas wewnętrznych. Teraz ta funkcjonalność została ułatwiona składniowo dzięki wyrażeniom lambda. Prawdopodobnie najbardziej istotnym aspektem nowej funkcji jest to, że są teraz lambdy. Nazywanie ich lambdami nie jest błędne, w rzeczywistości są one lambdami. Dlaczego autorzy Java 8 nie chcą podkreślać faktu, że są zamknięciami, nie wiem o tym.
Claudiu
2
@AlexanderOrlov, ponieważ lambda Java 8 nie są prawdziwymi zamknięciami, są symulacjami zamknięć. Są one bardziej podobne do zamknięć w języku Python 2.3 (brak możliwości ich modyfikacji, stąd wymóg, aby zmienne, o których mowa, były „efektywnie ostateczne”), i wewnętrznie kompilują się z funkcjami nie zamykającymi, które przyjmują wszystkie zmienne wymienione w zakresie obejmującym jako parametry ukryte.
Logan Pickup
7
@Claudiu Myślę, że odniesienie do konkretnej implementacji języka (Python) może nadmiernie skomplikować odpowiedź. Pytanie jest całkowicie niezależne od języka (jak również nie ma znaczników specyficznych dla języka).
Matthew
318

Istnieje wiele zamieszania wokół lambd i zamknięć, nawet w odpowiedziach na pytanie StackOverflow tutaj. Zamiast pytać przypadkowych programistów, którzy nauczyli się o zamknięciach z praktyki z niektórymi językami programowania lub innymi programistami bez pojęcia, wybierz się do źródła (gdzie wszystko się zaczęło). A ponieważ lambda i zamknięcia pochodzą z Lambda Calculus wynalezionego przez Alonzo Church w latach 30., zanim jeszcze istniały pierwsze komputery elektroniczne, to jest to źródło , o którym mówię.

Lambda Calculus to najprostszy język programowania na świecie. Jedyne, co możesz w nim zrobić: ►

  • ZASTOSOWANIE: Oznaczanie jednego wyrażenia do drugiego f x.
    (Pomyśl o tym jak o wywołaniu funkcji , gdzie fjest funkcja i xjest jej jedynym parametrem)
  • ABSTRAKCJA: Wiąże symbol występujący w wyrażeniu, aby zaznaczyć, że ten symbol jest tylko „szczeliną”, pustym polem oczekującym na wypełnienie wartością, „zmienną” jakby. Odbywa się to poprzez wstawienie greckiej litery λ(lambda), następnie nazwy symbolicznej (np. x), A następnie kropki .przed wyrażeniem. To następnie przekształca wyrażenie w funkcję oczekującą jednego parametru .
    Na przykład: λx.x+2bierze wyrażenie x+2i mówi, że symbol xw tym wyrażeniu jest zmienną powiązaną - można go zastąpić wartością podaną jako parametr.
    Zauważ, że funkcja zdefiniowana w ten sposób jest anonimowa- nie ma nazwy, więc nie może odnosić się do niej jeszcze, ale można natychmiast wezwać go (pamiętaj wniosek?) Poprzez dostarczanie mu parametr jest oczekiwanie na coś takiego: (λx.x+2) 7. Następnie wyrażenie (w tym przypadku wartość dosłowna) 7jest podstawiane tak, jak xw podwyrażeniu x+2zastosowanej lambdy, więc otrzymujesz 7+2, który następnie zmniejsza się do 9wspólnych reguł arytmetycznych.

Więc mamy rozwiązać jedną z zagadek:
lambda jest anonimowa funkcja z powyższym przykładzie λx.x+2.


W różnych językach programowania składnia abstrakcji funkcjonalnej (lambda) może się różnić. Na przykład w JavaScript wygląda to tak:

function(x) { return x+2; }

i możesz natychmiast zastosować go do niektórych parametrów takich jak ten:

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

lub możesz zapisać tę anonimową funkcję (lambda) w jakiejś zmiennej:

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

co skutecznie nadaje mu nazwę f, dzięki czemu możesz się do niej odwoływać i wywoływać wiele razy później, np .:

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

Ale nie musiałeś tego nazywać. Możesz to nazwać natychmiast:

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

W LISP lambdas są tworzone w następujący sposób:

(lambda (x) (+ x 2))

i możesz wywołać taką lambda, stosując ją natychmiast do parametru:

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


OK, teraz czas rozwiązać drugą tajemnicę: czym jest zamknięcie . Aby to zrobić, porozmawiajmy o symbolach ( zmiennych ) w wyrażeniach lambda.

Jak powiedziałem, abstrakcja lambda wiąże symbol w podwyrażeniu, tak że staje się parametrem zastępowalnym . Taki symbol nazywa się związany . Ale co, jeśli w wyrażeniu są inne symbole? Na przykład: λx.x/y+2. W tym wyrażeniu symbol xjest związany λx.poprzedzającą go abstrakcją lambda . Ale drugi symbol ynie jest związany - jest bezpłatny . Nie wiemy, co to jest i skąd pochodzi, więc nie wiemy, co to znaczy i co wartość reprezentuje, i dlatego nie możemy ocenić tego wyrażenia, dopóki nie ustalimy, co yto znaczy.

W rzeczywistości to samo dotyczy pozostałych dwóch symboli, 2 i +. Po prostu tak dobrze znamy te dwa symbole, że zwykle zapominamy, że komputer ich nie zna i musimy powiedzieć im, co mają na myśli, definiując je gdzieś, np. W bibliotece lub w samym języku.

Możesz myśleć o wolnych symbolach zdefiniowanych gdzie indziej, poza wyrażeniem, w jego „otaczającym kontekście”, który nazywa się jego środowiskiem . Środowisko może być większym wyrażeniem, którego częścią jest to wyrażenie (jak powiedział Qui-Gon Jinn: „Zawsze jest większa ryba”;)) lub w bibliotece lub w samym języku (jako prymityw ).

To pozwala nam podzielić wyrażenia lambda na dwie kategorie:

  • Wyrażenia ZAMKNIĘTE: każdy symbol występujący w tych wyrażeniach jest związany pewną abstrakcją lambda. Innymi słowy, są samodzielne ; nie wymagają oceny żadnego otaczającego kontekstu. Są również nazywane kombinatorami .
  • Wyrażenia OPEN: niektóre symbole w tych wyrażeniach nie są powiązane - to znaczy niektóre z występujących w nich symboli są bezpłatne i wymagają pewnych informacji zewnętrznych, a zatem nie można ich ocenić, dopóki nie podasz definicji tych symboli.

Możesz ZAMKNIJ otwarte wyrażenie lambda, podając środowisko , które definiuje wszystkie te wolne symbole, wiążąc je z pewnymi wartościami (które mogą być liczbami, łańcuchami, anonimowymi funkcjami aka lambdas, cokolwiek…).

I tutaj jest zamknięcie części: zamknięcie z ekspresją lambda jest ten konkretny zbiór symboli zdefiniowane w kontekście zewnętrznej (otoczenia), które dają wartości do wolnych symboli w tym wyrażeniu, przez co więcej ich wolna. Zamienia otwarte wyrażenie lambda, które wciąż zawiera pewne „niezdefiniowane” wolne symbole, w
zamknięte , które nie ma już żadnych wolnych symboli.

Na przykład, jeśli masz następujące wyrażenie lambda:, λx.x/y+2symbol xjest związany, podczas gdy symbol yjest wolny, dlatego wyrażenie jest openi nie może być ocenione, chyba że powiesz co yoznacza (i to samo z +i 2, które są również wolne). Ale załóżmy, że masz również takie środowisko :

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

To środowisko zaopatrzenie definicje dla wszystkich „niezdefiniowana” (wolnych) symboli z naszego wyrażenia lambda ( y, +, 2), a także kilka dodatkowych symboli ( q, w). Symbole, które musimy zdefiniować to następujący podzbiór środowiska:

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

i to jest właśnie zamknięcie naszego wyrażenia lambda:>

Innymi słowy, zamyka otwarte wyrażenie lambda. Stąd właśnie wzięło się zamknięcie nazwy i dlatego odpowiedzi tak wielu osób w tym wątku nie są całkiem poprawne: P


Dlaczego więc się mylą? Dlaczego tak wielu z nich mówi, że zamknięcia są pewnymi strukturami danych w pamięci lub niektórymi cechami używanych przez nich języków, lub dlaczego mylą zamknięcia z lambdami? : P

Cóż, korporacyjne marketoidy Sun / Oracle, Microsoft, Google itp. Są winne, ponieważ tak nazywają te konstrukcje w swoich językach (Java, C #, Go itp.). Często nazywają „zamknięciami” czymś, co ma być po prostu lambdas. Lub nazywają „zamknięcia” szczególną techniką stosowaną w celu zaimplementowania zakresu leksykalnego, to znaczy fakt, że funkcja może uzyskać dostęp do zmiennych, które zostały zdefiniowane w jej zewnętrznym zakresie w momencie jej definicji. Często mówią, że funkcja „zamyka” te zmienne, tzn. Przechwytuje je w jakiejś strukturze danych, aby uchronić je przed zniszczeniem po zakończeniu wykonywania funkcji zewnętrznej. Ale to tylko wymyślone post factum „etymologia folklorystyczna” i marketing, co tylko bardziej komplikuje sytuację,

I jest jeszcze gorzej, ponieważ w tym, co mówią, zawsze jest trochę prawdy, co nie pozwala łatwo odrzucić to jako fałszywe: P Pozwól mi wyjaśnić:

Jeśli chcesz zaimplementować język, w którym lambdas są pierwszorzędnymi obywatelami, musisz pozwolić im używać symboli zdefiniowanych w ich otaczającym kontekście (to znaczy, aby używać wolnych zmiennych w twoich lambdach). Symbole te muszą istnieć nawet po powrocie otaczającej funkcji. Problem polega na tym, że te symbole są powiązane z pewnym lokalnym przechowywaniem funkcji (zwykle na stosie wywołań), którego już nie będzie, gdy funkcja powróci. Dlatego, aby lambda działała w oczekiwany sposób, musisz jakoś „uchwycić” wszystkie te zmienne wolne z zewnętrznego kontekstu i zachować je na później, nawet gdy zewnętrzny kontekst zniknie. Oznacza to, że musisz znaleźć zamknięcietwojej lambda (wszystkie te zmienne zewnętrzne, których używa) i przechowuj ją gdzie indziej (albo przez wykonanie kopii, albo przez przygotowanie dla nich miejsca z góry, gdzie indziej niż na stosie). Rzeczywistą metodą stosowaną do osiągnięcia tego celu jest „szczegół implementacji” Twojego języka. Ważne jest tutaj zamknięcie , czyli zbiór wolnych zmiennych z środowiska twojej lambda, które trzeba gdzieś zapisać.

Nie trzeba było długo czekać, aby ludzie zaczęli nazywać rzeczywistą strukturę danych, której używają w implementacjach swojego języka, aby zaimplementować zamknięcie jako „zamknięcie”. Struktura zwykle wygląda mniej więcej tak:

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

a te struktury danych są przekazywane jako parametry do innych funkcji, zwracane z funkcji i przechowywane w zmiennych, w celu reprezentowania lambdas i umożliwiając im dostęp do otaczającego środowiska, a także kodu maszynowego do działania w tym kontekście. Ale to tylko sposób (jeden z wielu) na wdrożenie zamknięcia, a nie samego zamknięcia.

Jak wyjaśniłem powyżej, zamknięcie wyrażenia lambda jest podzbiorem definicji w jego środowisku, które nadają wartości wolnym zmiennym zawartym w tym wyrażeniu lambda, skutecznie zamykając wyrażenie (przekształcając otwarte wyrażenie lambda, którego nie można jeszcze ocenić, w za zamknięty ekspresji lambda, które następnie mogą być ocenione, ponieważ wszystkie symbole zawarte w nim obecnie zdefiniowane).

Wszystko inne to tylko „kult ładunku” i „magia voo-doo” programistów i sprzedawców językowych nieświadomych prawdziwych korzeni tych pojęć.

Mam nadzieję, że to odpowiada na twoje pytania. Ale jeśli masz dodatkowe pytania, zadaj je w komentarzach, a ja postaram się to lepiej wyjaśnić.

SasQ
źródło
50
Najlepsza odpowiedź wyjaśniająca rzeczy ogólnie, a nie specyficzne dla języka
Shishir Arora,
39
uwielbiam takie podejście podczas wyjaśniania rzeczy. Zaczynając od samego początku, wyjaśniając, jak działają rzeczy, a następnie jak powstały obecne nieporozumienia. Ta odpowiedź musi przejść na górę.
Sharky
1
@SasQ> zamknięcie wyrażenia lambda jest podzbiorem definicji w jego środowisku, które nadają wartości wolnym zmiennym zawartym w tym wyrażeniu lambda <Czy w twojej przykładowej strukturze danych oznacza to, że właściwsze jest wypowiedzenie „wskaźnika do środowisko funkcji lambda ”jest zamknięciem?
Jeff M
3
Chociaż rachunek lambda wydaje mi się językiem maszynowym, muszę się zgodzić, że jest to język „znaleziony” w przeciwieństwie do języka „stworzonego”. A zatem o wiele mniej podlega arbitralnym konwencjom, a bardziej nadaje się do uchwycenia podstawowej struktury rzeczywistości. Możemy znaleźć szczegóły w Linq, JavaScript, F # bardziej przystępne / dostępne, ale rachunek Lambda dociera do sedna sprawy bez rozproszenia.
StevePoling,
1
Doceniam to, że powtórzyłeś swój punkt kilkakrotnie, za każdym razem z nieco innym brzmieniem. Pomaga wzmocnić koncepcję. Chciałbym, żeby więcej ludzi to zrobiło.
johnklawlor
175

Kiedy większość ludzi myśli o funkcjach , myśli o nazwanych funkcjach :

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

Są one oczywiście nazywane po imieniu:

foo(); //returns the string above

Dzięki wyrażeniom lambda możesz mieć anonimowe funkcje :

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

W powyższym przykładzie możesz wywołać lambda poprzez zmienną, do której została przypisana:

foo();

Bardziej przydatne niż przypisywanie funkcji anonimowych do zmiennych, jednak przekazują je do lub z funkcji wyższego rzędu, tj. Funkcji, które akceptują / zwracają inne funkcje. W wielu przypadkach nazywanie funkcji jest niepotrzebne:

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

Zamknięcie może być nazwane lub funkcja anonimowa, ale są znane jako aktywny przy „zamykane na” zmienne w zakresie, w którym funkcja jest określona, to znaczy zamknięcie nadal znajduje się w środowisku z żadnym z parametrów zewnętrznych, które są stosowane w samo zamknięcie. Oto nazwane zamknięcie:

@x = 0;

function incrementX() { x = x + 1;}

incrementX(); // x now equals 1

Nie wydaje się to wiele, ale co, jeśli to wszystko było w innej funkcji i przeszedłeś incrementXna funkcję zewnętrzną?

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)

W ten sposób uzyskujesz obiekty stanowe w programowaniu funkcjonalnym. Ponieważ nazwanie „incrementX” nie jest potrzebne, w tym przypadku możesz użyć lambda:

function foo()
 { @x = 0;

   return lambda() 
           { x = x + 1;
             return x;
           };
 }
Mark Cidade
źródło
14
w jakim języku tu jesteś?
Claudiu
7
Jest to w zasadzie pseudokod. Jest w nim trochę seplenienia i JavaScript, a także projektowany przeze mnie język o nazwie „@” („at”), nazwany na cześć operatora deklaracji zmiennej.
Mark Cidade
3
@MarkCidade, więc gdzie jest ten język @? Czy jest dokumentacja i donwload?
Pacerier
5
Dlaczego nie wziąć Javascript i dodać ograniczenie deklarowania zmiennych z wiodącym znakiem @? Zaoszczędziłoby to trochę czasu :)
Nemoden,
5
@Pacerier: Zacząłem implementować język: github.com/marxidad/At2015
Mark Cidade,
54

Nie wszystkie zamknięcia są lambdas i nie wszystkie lambdas są zamknięciami. Obie są funkcjami, ale niekoniecznie w sposób, w jaki jesteśmy przyzwyczajeni.

Lambda jest zasadniczo funkcją zdefiniowaną wewnętrznie, a nie standardową metodą deklarowania funkcji. Baranki można często przekazywać jako przedmioty.

Zamknięcie jest funkcją, która otacza stan otaczający, odwołując się do pól zewnętrznych względem jego ciała. Stan zamknięty pozostaje w obrębie inwokacji zamknięcia.

W języku zorientowanym obiektowo zamknięcia są zwykle dostarczane przez obiekty. Jednak niektóre języki OO (np. C #) implementują specjalną funkcjonalność, która jest bliższa definicji zamknięć zapewnianych przez języki wyłącznie funkcjonalne (takie jak lisp), które nie mają obiektów do zamknięcia stanu.

Co ciekawe, wprowadzenie Lambdas i Closures w C # zbliża funkcjonalne programowanie do głównego nurtu.

Michael Brown
źródło
Czy możemy więc powiedzieć, że zamknięcia są podzbiorem lambdas, a lambdas są podzbiorem funkcji?
sker
Zamknięcia są podzbiorem lambdas ... ale lambdas są bardziej specjalne niż normalne funkcje. Jak powiedziałem, lambdas są zdefiniowane w linii. Zasadniczo nie ma możliwości odwołania się do nich, chyba że zostaną one przekazane do innej funkcji lub zwrócone jako wartość zwracana.
Michael Brown
19
Lambdy i zamknięcia są podzbiorem wszystkich funkcji, ale istnieje tylko przecięcie między lambdami i zamknięciami, gdzie nie przecinająca się część zamknięć byłaby nazwana funkcjami, które są zamknięciami, a nie przecinające się lamdy są niezależnymi funkcjami o całkowicie- zmienne powiązane.
Mark Cidade
Moim zdaniem lambdy są bardziej podstawowymi pojęciami niż funkcje. To naprawdę zależy od języka programowania.
Wei Qiu
15

To takie proste: lambda jest konstrukcją językową, tj. Po prostu składnią anonimowych funkcji; zamknięcie jest techniką jego wdrożenia - lub dowolnymi funkcjami najwyższej klasy, w tym przypadku nazwanymi lub anonimowymi.

Dokładniej, zamknięcie polega na tym, jak reprezentowana jest funkcja pierwszej klasy w czasie wykonywania, jako para jej „kodu” i środowisko „zamykające się” na wszystkie zmienne nielokalne używane w tym kodzie. W ten sposób zmienne te są nadal dostępne, nawet jeśli zewnętrzne zakresy, z których pochodzą, zostały już opuszczone.

Niestety istnieje wiele języków, które nie obsługują funkcji jako wartości najwyższej klasy lub obsługują je tylko w okaleczonej formie. Dlatego ludzie często używają terminu „zamknięcie”, aby rozróżnić „rzeczywistość”.

Andreas Rossberg
źródło
12

Z punktu widzenia języków programowania są to dwie różne rzeczy.

Zasadniczo dla pełnego języka Turinga potrzebujemy tylko bardzo ograniczonych elementów, np. Abstrakcji, aplikacji i redukcji. Abstrakcja i aplikacja zapewniają sposób budowania wyrażenia lamdba, a redukcja określa znaczenie wyrażenia lambda.

Lambda zapewnia sposób na streszczenie procesu obliczeniowego. na przykład, aby obliczyć sumę dwóch liczb, można wyodrębnić proces, który przyjmuje dwa parametry x, y i zwraca x + y. W schemacie możesz to zapisać jako

(lambda (x y) (+ x y))

Możesz zmienić nazwę parametrów, ale zadanie, które wykonuje, nie ulega zmianie. W prawie wszystkich językach programowania można nadać wyrażeniu lambda nazwę, która nazywa się funkcją. Ale nie ma dużej różnicy, można je koncepcyjnie traktować jako cukier składniowy.

OK, teraz wyobraź sobie, jak można to zaimplementować. Ilekroć stosujemy wyrażenie lambda do niektórych wyrażeń, np

((lambda (x y) (+ x y)) 2 3)

Możemy po prostu zastąpić parametry wyrażeniem do oceny. Ten model jest już bardzo wydajny. Ale ten model nie pozwala nam zmieniać wartości symboli, np. Nie możemy naśladować zmiany statusu. Potrzebujemy zatem bardziej złożonego modelu. Krótko mówiąc, ilekroć chcemy obliczyć znaczenie wyrażenia lambda, umieszczamy parę symboli i odpowiednią wartość w środowisku (lub tabeli). Następnie reszta (+ xy) jest oceniana poprzez wyszukiwanie odpowiednich symboli w tabeli. Teraz, jeśli dostarczymy prymitywów do bezpośredniego działania w środowisku, możemy modelować zmiany statusu!

Na tym tle sprawdź tę funkcję:

(lambda (x y) (+ x y z))

Wiemy, że kiedy ocenimy wyrażenie lambda, xy zostanie powiązane w nowej tabeli. Ale jak i gdzie możemy szukać? W rzeczywistości z jest nazywany zmienną swobodną. Musi istnieć środowisko zewnętrzne, które zawiera z. W przeciwnym razie znaczenia wyrażenia nie można ustalić, wiążąc tylko xiy. Aby to wyjaśnić, możesz napisać w schemacie coś w następujący sposób:

((lambda (z) (lambda (x y) (+ x y z))) 1)

Więc z będzie związany z 1 w tabeli zewnętrznej. Nadal otrzymujemy funkcję, która akceptuje dwa parametry, ale jej prawdziwe znaczenie zależy również od środowiska zewnętrznego. Innymi słowy, środowisko zewnętrzne zamyka wolne zmienne. Za pomocą set! Możemy sprawić, by funkcja była stanowa, tzn. Nie jest to funkcja w sensie matematycznym. To, co zwraca, zależy nie tylko od danych wejściowych, ale również od Z.

Jest to coś, co już znasz bardzo dobrze, metoda obiektów prawie zawsze opiera się na stanie obiektów. Dlatego niektórzy twierdzą, że „zamknięcia są obiektami biednego człowieka”. Możemy jednak uznać przedmioty za zamknięcia biednego człowieka, ponieważ naprawdę lubimy funkcje pierwszej klasy.

Używam schematu do zilustrowania pomysłów, ponieważ ten schemat jest jednym z najwcześniejszych języków, który ma prawdziwe zamknięcia. Wszystkie materiały tutaj przedstawione są znacznie lepiej przedstawione w rozdziale 3 SICP.

Podsumowując, lambda i zamknięcie to naprawdę różne pojęcia. Lambda jest funkcją. Zamknięcie to para lambda i odpowiednie środowisko, które zamyka lambda.

Wei Qiu
źródło
Więc można zastąpić wszystkie zamknięcia zagnieżdżonymi lambdami, dopóki nie będą już dostępne żadne zmienne wolne? W tym przypadku powiedziałbym, że zamknięcia można uznać za specjalny rodzaj lambdas.
Trilarion
8

Koncepcja jest taka sama, jak opisano powyżej, ale jeśli pochodzisz z PHP, wyjaśnij to dalej za pomocą kodu PHP.

$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, function ($v) { return $v > 2; });

funkcja ($ v) {return $ v> 2; } to definicja funkcji lambda. Możemy nawet przechowywać go w zmiennej, aby można go było ponownie użyć:

$max = function ($v) { return $v > 2; };

$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, $max);

Co teraz, jeśli chcesz zmienić maksymalną liczbę dozwoloną w filtrowanej tablicy? Musisz napisać inną funkcję lambda lub utworzyć zamknięcie (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));

Zamknięcie jest funkcją ocenianą we własnym środowisku, które ma jedną lub więcej zmiennych powiązanych, do których można uzyskać dostęp po wywołaniu funkcji. Pochodzą ze świata programowania funkcjonalnego, w którym występuje wiele koncepcji. Zamknięcia są jak funkcje lambda, ale mądrzejsze w tym sensie, że mają możliwość interakcji ze zmiennymi ze środowiska zewnętrznego, w którym zdefiniowano zamknięcie.

Oto prostszy przykład zamknięcia PHP:

$string = "Hello World!";
$closure = function() use ($string) { echo $string; };

$closure();

Ładnie wyjaśnione w tym artykule.

Deweloper
źródło
7

To pytanie jest stare i ma wiele odpowiedzi.
Teraz, gdy Java 8 i Official Lambda są nieoficjalnymi projektami zamykającymi, ożywia to pytanie.

Odpowiedź w kontekście Java (za pomocą Lambdas i zamknięć - jaka jest różnica? ):

„Zamknięcie jest wyrażeniem lambda sparowanym ze środowiskiem, które wiąże każdą z wolnych zmiennych z wartością. W Javie wyrażenia lambda będą implementowane za pomocą zamknięć, więc te dwa terminy zaczęły być używane zamiennie w społeczności”.

philippe lhardy
źródło
Jak Lamdy są implementowane przez zamknięcie w Javie? Czy to oznacza, że ​​wyrażenie Lamdasa zostaje przekształcone w anonimową klasę w starym stylu?
hackjutsu
5

Mówiąc wprost, zamknięcie jest sztuczką dotyczącą zakresu, lambda jest funkcją anonimową. Możemy bardziej elegancko zrealizować zamknięcie za pomocą lambda, a lambda jest często używana jako parametr przekazywany do wyższej funkcji

feilengcui008
źródło
4

Wyrażenie Lambda jest tylko funkcją anonimową. na przykład w zwykłej Javie możesz napisać to w następujący sposób:

Function<Person, Job> mapPersonToJob = new Function<Person, Job>() {
    public Job apply(Person person) {
        Job job = new Job(person.getPersonId(), person.getJobDescription());
        return job;
    }
};

gdzie funkcja klasy jest właśnie wbudowana w kod Java. Teraz możesz zadzwonić mapPersonToJob.apply(person)gdzieś, aby z niego skorzystać. to tylko jeden przykład. To lambda, zanim do tego doszło. Lambdas to skrót.

Zamknięcie:

Lambda staje się zamknięciem, gdy może uzyskać dostęp do zmiennych poza tym zakresem. Myślę, że można powiedzieć, że jest magiczny, może magicznie owijać się wokół środowiska, w którym został stworzony, i używać zmiennych poza swoim zakresem (zakres zewnętrzny. tak więc, dla jasności, zamknięcie oznacza, że ​​lambda może uzyskać dostęp do ZAKRESU ZEWNĘTRZNEGO.

w Kotlinie lambda zawsze może uzyskać dostęp do swojego zamknięcia (zmienne, które są w zewnętrznym zakresie)

j2emanue
źródło
0

Zależy to od tego, czy funkcja używa zmiennej zewnętrznej, czy nie wykonuje operacji.

Zmienne zewnętrzne - zmienne zdefiniowane poza zakresem funkcji.

  • Wyrażenia lambda są bezstanowe, ponieważ wykonywanie operacji zależy od parametrów, zmiennych wewnętrznych lub stałych.

    Function<Integer,Integer> lambda = t -> {
        int n = 2
        return t * n 
    }
    
  • Zamknięcia utrzymują stan, ponieważ używa zmiennych zewnętrznych (tj. Zmiennych zdefiniowanych poza zakresem treści funkcji) wraz z parametrami i stałymi do wykonywania operacji.

    int n = 2
    
    Function<Integer,Integer> closure = t -> {
        return t * n 
    }
    

Gdy Java tworzy zamknięcie, zachowuje zmienną n z funkcją, dzięki czemu można się do niej odwoływać, gdy jest przekazywana do innych funkcji lub używana w dowolnym miejscu.

DIPANSHU GOYAL
źródło