Czym jest ta „Lambda”, o której wszyscy mówią?


93

Czym jest ta „Lambda”, o której wszyscy mówią? Wydaje się, że wielu ludzi to uwielbia, ale wszystko, co mogę z tego wyciągnąć, to po prostu sposób na upchanie wielu linii kodu w jednym wyrażeniu.

Czy ktoś może mnie oświecić na temat jego prawdziwej wartości?


16
Czy mogę wskazać respondentom, że pytający nigdzie nie wspomina o domenie .net


Ciekawe pytanie. Dzięki, że pytasz.
Peanut

Lambdy należą do świata programowania funkcyjnego (programowanie deklaratywne).
RBT

Odpowiedzi:


179

Funkcje bez nazwy

Mówiąc najprościej, lambda to funkcja bez nazwy lub funkcja anonimowa. Mały fragment kodu wykonywalnego, który można przekazać tak, jakby był zmienną. W JavaScript:

function () {}; // very simple

Zobaczmy teraz niektóre zastosowania tych lambd.

Abstrakcyjny kod schematyczny

Lambdy mogą być użyte do wyodrębnienia kodu standardowego. Na przykład pętle. Jesteśmy przyzwyczajeni do pisania fori whilepętli przez cały dzień. Ale to jest kod, którego się nie pisze. Moglibyśmy wyodrębnić kod wewnątrz pętli, najważniejszą część pętli i usunąć resztę:

for (var i=0; i<array.length; i++) {
    // do what something useful with array[i]
}

używając forEachobiektów tablicy of, staje się:

array.forEach(function (element, index) {
   // do something useful with element
   // element is the equivalent of array[i] from above
});

Powyższa abstrakcja może nie być przydatna, ale istnieją inne funkcje wyższego rzędu, takie jak forEach, które wykonują znacznie bardziej przydatne zadania. Na przykład filter:

var numbers = [1, 2, 3, 4];
var even    = [];

// keep all even numbers from above array
for (var i=0; i<numbers.length; i++) {
    if (numbers[i] % 2 === 0) {
        even.push(numbers[i]);
    }
}

alert(even);

// Using the filter method
even = [1, 2, 3, 4].filter(function (number) {
    return number % 2 === 0;
});

alert(even);

Opóźnienie wykonania kodu

W niektórych środowiskach, w których pojęcie zdarzenia jest dostępne, moglibyśmy użyć lambd, aby odpowiedzieć na zdarzenia, które mogą się wydarzyć w pewnym momencie.

window.onload = function () {
    alert("Loaded");
};

window.setTimeout(function () {
    alert("Code executed after 2 seconds.");
}, 2000);

Można to zrobić na inne sposoby, ale są one raczej rozwlekłe. Na przykład w Javie jest Runnableinterfejs.

Fabryki funkcji

Do tego momentu używaliśmy tylko lambd głównie ze względu na ich możliwości związane z cukrem syntaktycznym. Ale są sytuacje, w których lambdy mogą być znacznie bardziej przydatne. Na przykład możemy mieć funkcje, które zwracają lambdy. Powiedzmy, że mamy funkcję, dla której chcemy, aby jej zwracane wartości były buforowane.

var users = [];
var getUser = function (name) {
    if (! users[name]) {
        // expensive operations to get a user. Ajax for example
        users[name] = user_from_ajax;
    }

    return users[name];
};

Później możemy zauważyć, że mamy podobną funkcję:

var photos = [];
var getPhoto = function (name) {
    if (! photo[name]) {
        // expensive operations to get a user. Ajax for example
        photos[name] = photo_from_ajax;
    }

    return photos[name];
};

Jest tam wyraźnie jakiś wzór, więc odejmijmy go. Użyjmy zapamiętywania .

/**
 * @param {Array}     store Data structure in which we cache lambda's return values
 * @param {Function}  lambda
 * @return {Function} A function that caches the result of calling the lambda param
 */
var memoize = function (store, lambda) {
    // return a new lambda
    return function (name) {
        if (! store[name]) {
            // Execute the lambda and cache the result
            store[name] = lambda(name);
        }

        return store[name];
    };
};

var getUsers = memoize([], function (name) {
    // expensive operations to get a user. Ajax for example
});

var getPhotos = memoize([], function (name) {
    // expensive operations to get a photo. Ajax for example
});

Jak widać, używając lambd, byliśmy w stanie wyodrębnić logikę buforowania / zapamiętywania. Gdyby w drugim przykładzie istniały jakieś obejścia, uważam, że ten konkretny problem jest trudny do rozwiązania za pomocą innych technik. Udało nam się wyodrębnić ważny kod standardowy w jednym miejscu. Nie wspominając o tym, że pozbyliśmy się zmiennych globalnych usersi photos.

Patrząc na Twój profil, widzę, że jesteś głównie użytkownikiem Pythona. W przypadku powyższego wzorca Python ma koncepcję dekoratorów. W sieci jest wiele przykładów dekoratorów zapamiętywania . Jedyną różnicą jest to, że w Pythonie najprawdopodobniej masz nazwaną zagnieżdżoną funkcję wewnątrz tej funkcji dekoratora. Powodem jest to, że Python obsługuje tylko wyrażenia lambda z jednym wyrażeniem. Ale koncepcja jest taka sama.

Jako przykład użycia lambda w Pythonie. Powyższy kod, w którym odfiltrowaliśmy liczby parzyste, można przedstawić w Pythonie w następujący sposób:

filter(lambda x: x % 2 == 0, [1, 2, 3, 4])

W każdym razie lambdy nie są tak potężne bez zamknięć. Zamknięcia są tym, co sprawia, że ​​koncepcja lambd jest tak potężna. W moim przykładzie zapamiętywania użyłem domknięć, aby utworzyć zamknięcie wokół storeparametru. W ten sposób mam dostęp do tego parametru nawet po tym, jak memoizefunkcja zwróciła swój wynik (lambdę).


3
Wow, włożyłeś w to dużo czasu.
mk12

4
@ Mk12, w samym pisaniu odpowiedzi, nie bardzo. Uczyłem się tych rzeczy, tak, minęło trochę czasu, odkąd zacząłem :)
Ionuț G. Stan

Dobra odpowiedź, ale brakuje informacji o „interfejsach funkcjonalnych” (z punktu widzenia Javy).
djangofan

Co to są operatory „===” w Twoim „Abstrakcyjnym kodzie standardowym”?
Don


19

Termin „lambda” jest używany w odniesieniu do funkcji anonimowej, zwykle zamknięcia . Są przydatne, ponieważ pozwalają pisać funkcje, które używają innych funkcji bez niepotrzebnego nadużywania kodu. Na przykład w Rubim:

(1..100).select {|num| num % 2 == 0}

Spowoduje to utworzenie tablicy zawierającej liczby parzyste z przedziału od 1 do 100. Nie musimy pisać jawnej pętli - metoda select przyjmuje funkcję, której używa do testowania wartości, więc potrzebujemy tylko naszej niestandardowej logiki. To pozwala nam w znacznym stopniu dostosować metodę praktycznie bez wysiłku i kosztów. Zasadniczo możemy składać funkcje z mniejszych funkcji.

To tylko prosty przykład tego, co mogą zrobić. Możliwość przekazywania funkcji jako danych jest naprawdę potężna, a programiści języka funkcjonalnego rutynowo robią z nią naprawdę niesamowite rzeczy.


6
Może powinieneś dodać, że rzecz w rurach to parametr. Jestem jedną z tych osób, które mają problemy z czytaniem rubinu.
Skurmedel,

To miła odpowiedź. Jedynym powodem, dla którego Ionut dostał mój głos, jest to, że powiedział nam, dlaczego powinniśmy dbać (szczegółowo) o lambdy.
Frank Shearar

Nie zgodziłbym się, że lambdy są zwykle zamknięciami.
jwg

6

„Lambda” może po prostu za mało. Spójrz na rachunek Lambda . Jest to przydatne w programowaniu funkcjonalnym.

A programowanie funkcjonalne to kolejny paradygmat programowania (podobny do proceduralnego lub obiektowego).


5
Wtedy ludzie mówią o „Lambdzie”, najprawdopodobniej mówią o funkcji anonimowej, wskazówkach do funkcji, zamknięciu lub czymś podobnym. Prawie nigdy nie odnosimy się do prawdziwego rachunku lambda.
J-16 SDiZ

Cieszę się, że wspomniałeś o rachunku lambda.! +1.
RBT

5

Lambdy w .NET są często określane jako „cukier syntaktyczny”. Nie wpływają bezpośrednio na funkcjonalność, ale ułatwiają korzystanie z języka.

Kiedy zrozumiesz moc ich używania, jestem pewien, że zauważysz, że będziesz pisać mniej kodu w porównaniu do starego stylu przy użyciu metod delegowanych / anonimowych.


1
Nie sądzę, aby ktokolwiek wspomniał o .NET, więc prawdopodobnie lepiej będzie, jeśli OP będzie zawierał bardziej ogólną odpowiedź.
molf

2
dlatego wyjaśniłem, odpowiadając na temat .net. Jeśli inni zaczną ćwierkać przy implementacji języka, wtedy pytania i odpowiedzi pomogą wielu ludziom bez względu na wybór języka.
redsquare

Ta odpowiedź brzmi tak, jakbyś słyszał coś o lambdach, ale sam ich jeszcze nie rozumiesz.
jwg

2

Dr Dobbs Journal ma przydatny artykuł wprowadzający wyrażenia lambda (w kontekście C ++, ale myślę, że możesz zastosować te zasady do dowolnego języka).

Jak czytamy w artykule: „Wyrażenie lambda to bardzo zwarte wyrażenie, które nie wymaga oddzielnej definicji klasy / funkcji”.

A więc na przykładach aukcji 1 i 2 z DDJ zamiast pisać:

std::for_each( vec.begin(), vec.end(), print_to_stream<std::string>(std::cout));

Co wymaga oddzielnej definicji klasy, takiej jak:

template <typename T, typename Stream> class print_to_stream_t {
  Stream& stream_;
public:
  print_to_stream_t(Stream& s):stream_(s) {}
  void operator()(const T& t) const {
    stream_ << t;
  }
};
template <typename T,typename Stream> 
print_to_stream_t<T,Stream>   print_to_stream(Stream& s) {
  return print_to_stream_t<T,Stream>(s);
}

Korzystając z biblioteki Boost lambda, może to wyglądać:

std::for_each(vec.begin(),vec.end(),std::cout << _1);

Co utrzymuje definicję w linii.

Artykuł wyjaśniono również kilka innych zastosowań wyrażeń lambda.

Myślę, że kluczową kwestią w artykule DDJ jest „Zazwyczaj wyrażenia lambda są używane, gdy w miejscu wywołania potrzebne są małe i niezbyt złożone funkcje. Gdyby funkcja była nietrywialna, nie chciałbyś wyrażenia lambda, ale normalną funkcję lub obiekt funkcji. "


2

Jeśli kiedykolwiek pracowałeś z funkcjami / metodami, które używają wskaźników funkcji, delegatów, strategii lub obsługi wzorca / zdarzenia obserwatora i pomyślałeś sobie „Piszę całą tę funkcję tylko po to, aby jej użyć tylko raz - aby przekazać ją do tej metody Chciałbym móc po prostu napisać to w miejscu, zamiast zaśmiecać mój kod ”- tam właśnie możesz użyć funkcji Lambda. Języki obsługujące tę konstrukcję również zwykle w dużym stopniu wykorzystują koncepcję przekazywania funkcji jako parametrów, szczególnie w odniesieniu do pracy z listami (funkcje pierwszej klasy i funkcje wyższego rzędu). Jest to szczególnie prawdziwe w przypadku języków funkcjonalnych, które polegają na składaniu funkcji, a nie modyfikacji pamięci do obliczeń. W niektórych przypadkach (w językach takich jak Python)


2

„lambda” qua słowo to terminologia z czasów, gdy osoby z informatyki z równym prawdopodobieństwem zdobywały wykształcenie matematyczne lub logiczne, co stopień naukowy informatyki. Niektórzy z nich wymyślili paradygmat zwany „programowaniem funkcjonalnym”, zupełnie inny niż imperatyw i również dość potężny. AFAIK czyli środowisko, w którym termin ten wszedł w życie.

Matematycy i logicy mają skłonność do używania dziwnych słów.

„lambda” brzmi naprawdę ezoterycznie - jakby było bardzo dziwną i wyjątkową rzeczą. Naprawdę, jeśli piszesz JavaScript dla aplikacji przeglądarkowej i używasz idiomu "var foo = function () {...}", to od dawna używasz funkcji lambda.


1

Wyrażenie lambda to prosta forma funkcji. Chodzi o to, że coś z formularza po lewej stronie (odpowiednik parametrów) staje się czymś z formy po prawej stronie (odpowiednik ciała).

na przykład w cis:

x => x * x

jest lambdą do kwadratu wartości. Coś w formie

x

staje się czymś w rodzaju formy

x * x

0

„Wyrażenie lambda to anonimowa funkcja, która może zawierać wyrażenia i instrukcje i może być używana do tworzenia delegatów lub typów drzew wyrażeń.

Wszystkie wyrażenia lambda używają operatora lambda =>, który jest odczytywany jako „idzie do”. Lewa strona operatora lambda określa parametry wejściowe (jeśli istnieją), a prawa strona zawiera wyrażenie lub blok instrukcji. Wyrażenie lambda x => x * x jest odczytywane „x idzie do x razy x”.

z MSDN


4
Tak, Microsoft sprawia, że ​​wszyscy myślą, że to wymyślili. Wyrażenia lambda są jednak starsze niż Microsoft. To termin matematyczny, który został zastosowany w kilku językach programowania. (Co jest możliwe, ponieważ matematyka sama w sobie może być uważana za język komputerowy).
Wim ten Brink,

4
Zauważ, że jest to specyficzne dla implementacji .Net firmy Microsoft. Nie jest to duże odstępstwo od ogólnej idei lambdy, ale myślę, że funkcjonalność zaimplementowana w Lispie jest bardziej „standardowa”.
Chuck,

2
Ta odpowiedź w ogóle nie wyjaśnia, czym jest Lambda (wymaga definicji funkcji anonimowej, delegata, typu drzewa wyrażeń) i na pewno nie wyjaśnia jej wartości.
danio

0

Pełne wyjaśnienie wyrażeń Lambda można znaleźć również w Wikipedii . (Przewiń w dół do rachunku Lambda i języków programowania ). Wyrażenia lambda nie są takie nowe i nie są tylko częścią C #, ale czymś, co zostało wprowadzone do przetwarzania danych prawie 80 lat temu! Wyrażenia lambda są podstawą programowania funkcjonalnego.

Czy to wartość? Biorąc pod uwagę, że jest dość stary, powiedziałbym: bardzo cenny dla każdego, kto wykonuje obliczenia.


0

Jeśli interesujesz się Javą, wiele słyszałeś o lambdach lub domknięciach w ciągu ostatnich kilku miesięcy, ponieważ były różne propozycje dodania tej funkcji do Java 7. Jednak wydaje mi się, że komisja porzuciła tę funkcję. Jedna z propozycji pochodzi od Neala Gaftera i została szczegółowo wyjaśniona tutaj: javac.info . Pomogło mi to zrozumieć przypadki użycia i zalety (zwłaszcza w porównaniu z klasami wewnętrznymi)




-1

Tak, jest to po prostu sposób na upchanie wielu wierszy kodu w jednym wyrażeniu. Ale będąc tak efektywnym wkuwaniem, umożliwia nowe sposoby strukturyzacji programu.

Często można by uniknąć pisania delegatów lub wywołań zwrotnych i powrócić do stylu proceduralnego po prostu dlatego, że deklarowanie nowych funkcji lub klas dla pojedynczego wyrażenia jest zbyt pracochłonne.

Wyrażenia lambda sprawiają, że warto używać wywołań zwrotnych nawet w przypadku najmniejszych zadań, co może uczynić kod bardziej przejrzystym. Może nie.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.