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?
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?
Odpowiedzi:
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:
Spowoduje to błąd, ponieważ
func
nie zamyka środowiska wanotherfunc
-h
jest niezdefiniowany.func
zamyka się tylko w globalnym środowisku. To zadziała:Ponieważ tutaj
func
jest zdefiniowany wanotherfunc
Pythonie 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ę wanotherfunc
środowisku i może uzyskiwać dostęp do zmiennych wewnątrz to. W Pythonie 3.1 i nowsze, mutacja działa też przy korzystaniu znonlocal
słowa kluczowego .Kolejna ważna kwestia -
func
będzie nadal zamykaćanotherfunc
środowisko, nawet jeśli nie jest już ocenianeanotherfunc
. Ten kod będzie również działać: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.
źródło
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ć: ►
f x
.(Pomyśl o tym jak o wywołaniu funkcji , gdzie
f
jest funkcja ix
jest jej jedynym parametrem)λ
(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+2
bierze wyrażeniex+2
i mówi, że symbolx
w 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)7
jest podstawiane tak, jakx
w podwyrażeniux+2
zastosowanej lambdy, więc otrzymujesz7+2
, który następnie zmniejsza się do9
wspó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:
i możesz natychmiast zastosować go do niektórych parametrów takich jak ten:
lub możesz zapisać tę anonimową funkcję (lambda) w jakiejś zmiennej:
co skutecznie nadaje mu nazwę
f
, dzięki czemu możesz się do niej odwoływać i wywoływać wiele razy później, np .:Ale nie musiałeś tego nazywać. Możesz to nazwać natychmiast:
W LISP lambdas są tworzone w następujący sposób:
i możesz wywołać taką lambda, stosując ją natychmiast do parametru:
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 symbolx
jest związanyλx.
poprzedzającą go abstrakcją lambda . Ale drugi symboly
nie 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, coy
to 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:
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+2
symbolx
jest związany, podczas gdy symboly
jest wolny, dlatego wyrażenie jestopen
i nie może być ocenione, chyba że powiesz coy
oznacza (i to samo z+
i2
, które są również wolne). Ale załóżmy, że masz również takie środowisko :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: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:
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ć.
źródło
Kiedy większość ludzi myśli o funkcjach , myśli o nazwanych funkcjach :
Są one oczywiście nazywane po imieniu:
Dzięki wyrażeniom lambda możesz mieć anonimowe funkcje :
W powyższym przykładzie możesz wywołać lambda poprzez zmienną, do której została przypisana:
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:
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:
Nie wydaje się to wiele, ale co, jeśli to wszystko było w innej funkcji i przeszedłeś
incrementX
na funkcję zewnętrzną?W ten sposób uzyskujesz obiekty stanowe w programowaniu funkcjonalnym. Ponieważ nazwanie „incrementX” nie jest potrzebne, w tym przypadku możesz użyć lambda:
źródło
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.
źródło
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ść”.
źródło
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
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
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ę:
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:
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.
źródło
Koncepcja jest taka sama, jak opisano powyżej, ale jeśli pochodzisz z PHP, wyjaśnij to dalej za pomocą kodu PHP.
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ć:
Co teraz, jeśli chcesz zmienić maksymalną liczbę dozwoloną w filtrowanej tablicy? Musisz napisać inną funkcję lambda lub utworzyć zamknięcie (PHP 5.3):
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:
Ładnie wyjaśnione w tym artykule.
źródło
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? ):
źródło
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
źródło
Wyrażenie Lambda jest tylko funkcją anonimową. na przykład w zwykłej Javie możesz napisać to w następujący sposób:
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)
źródło
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.
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.
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.
źródło