Wiele współczesnych języków programowania obsługuje pewne pojęcia zamknięcia , np. Fragmentu kodu (bloku lub funkcji)
- Może być traktowany jako wartość, a zatem przechowywany w zmiennej, przekazywany do różnych części kodu, definiowany w jednej części programu i wywoływany w zupełnie innej części tego samego programu.
- Może przechwytywać zmienne z kontekstu, w którym są zdefiniowane, i uzyskiwać do nich dostęp, gdy zostaną później wywołane (być może w zupełnie innym kontekście).
Oto przykład zamknięcia napisanego w Scali:
def filterList(xs: List[Int], lowerBound: Int): List[Int] =
xs.filter(x => x >= lowerBound)
Literał funkcji x => x >= lowerBound
zawiera wolną zmienną lowerBound
, która jest zamknięta (powiązana) argumentem funkcji filterList
o tej samej nazwie. Zamknięcie jest przekazywane do metody bibliotecznej filter
, która może wywoływać ją wielokrotnie jako normalną funkcję.
Czytałem wiele pytań i odpowiedzi na tej stronie i, o ile rozumiem, termin zamknięcie jest często automatycznie kojarzony z programowaniem funkcjonalnym i stylem programowania funkcjonalnego.
Definicja programowania funkcji na wikipedii brzmi:
W informatyce programowanie funkcjonalne jest paradygmatem programowania, który traktuje obliczenia jako ocenę funkcji matematycznych i unika stanu i zmiennych danych. Podkreśla zastosowanie funkcji, w przeciwieństwie do imperatywnego stylu programowania, który podkreśla zmiany stanu.
i dalej
[...] w kodzie funkcjonalnym wartość wyjściowa funkcji zależy tylko od argumentów wprowadzonych do funkcji [...]. Wyeliminowanie efektów ubocznych może znacznie ułatwić zrozumienie i przewidywanie zachowania programu, co jest jedną z kluczowych motywacji rozwoju programowania funkcjonalnego.
Z drugiej strony wiele konstrukcji zamknięcia dostarczonych przez języki programowania pozwala zamknięciu na przechwytywanie zmiennych nielokalnych i zmianę ich po wywołaniu zamknięcia, powodując w ten sposób efekt uboczny dla środowiska, w którym zostały zdefiniowane.
W tym przypadku zamknięcia wprowadzają pierwszą ideę programowania funkcjonalnego (funkcje są pierwszorzędnymi jednostkami, które można przenosić jak inne wartości), ale pomijają drugą ideę (unikanie skutków ubocznych).
Czy takie użycie zamknięć ze skutkami ubocznymi jest uważane za funkcjonalny styl, czy też zamknięcia są uważane za bardziej ogólną konstrukcję, którą można stosować zarówno w funkcjonalnym, jak i niefunkcjonalnym stylu programowania? Czy jest jakaś literatura na ten temat?
WAŻNA UWAGA
Nie kwestionuję przydatności efektów ubocznych ani zamykania się z efektami ubocznymi. Nie jestem również zainteresowany dyskusją na temat zalet / wad zamknięć z efektami ubocznymi lub bez nich.
Interesuje mnie tylko to, czy stosowanie takich zamknięć jest nadal uważane za styl funkcjonalny przez zwolennika programowania funkcjonalnego, czy też przeciwnie, ich stosowanie jest zniechęcane podczas używania stylu funkcjonalnego.
źródło
Odpowiedzi:
Nie; definicja paradygmatu funkcjonalnego dotyczy braku stanu i domyślnie braku skutków ubocznych. Nie chodzi o funkcje wyższego rzędu, zamknięcia, manipulowanie listami obsługiwanymi językami ani inne funkcje językowe ...
Nazwa programowania funkcjonalnego pochodzi od matematycznego pojęcia funkcji - powtarzane wywołania na tym samym wejściu zawsze dają to samo wyjście - funkcja nullipotentna. Można to osiągnąć tylko wtedy, gdy dane są niezmienne . W celu ułatwienia rozwoju funkcje stały się zmienne (funkcje się zmieniają, dane są nadal niezmienne), a zatem pojęcie funkcji wyższego rzędu (funkcjonałów w matematyce, na przykład pochodnych) - funkcja, która przyjmuje na wejściu inną funkcję. Aby umożliwić przenoszenie funkcji i przekazywanie ich jako argumentów, przyjęto funkcje pierwszej klasy ; następnie, w celu dalszego zwiększenia wydajności, pojawiły się zamknięcia .
Jest to oczywiście bardzo uproszczony widok.
źródło
map
na przykład, która bierze funkcję, stosuje ją do listy i zwraca listę wyników.map
nie modyfikuje żadnego z argumentów, nie zmienia zachowania funkcji, którą przyjmuje za argument, ale jest to zdecydowanie funkcja wyższego rzędu - jeśli zastosujesz ją częściowo, używając tylko parametru funkcji, zbudowałeś nowa funkcja, która działa na liście, ale nadal nie nastąpiła mutacja.Nie. „Styl funkcjonalny” oznacza programowanie bez skutków ubocznych.
Aby zobaczyć, dlaczego, spójrz na wpis Erica Lipperta na temat
ForEach<T>
metody rozszerzenia i dlaczego Microsoft nie umieścił metody sekwencji takiej jak w Linq :źródło
ParallelQuery<T>.ForAll(...)
. Realizacja takiegoIEnumerable<T>.ForEach(...)
jest niezwykle przydatna do debugowaniaForAll
oświadczenia (wymienićForAll
zForEach
i usunąćAsParallel()
i można znacznie łatwiej prześledzić / debug IT)Programowanie funkcjonalne z pewnością przenosi funkcje pierwszej klasy na kolejny poziom pojęciowy, ale deklarowanie funkcji anonimowych lub przekazywanie funkcji innym funkcjom niekoniecznie jest funkcją programowania funkcjonalnego. W C wszystko było liczbą całkowitą. Liczba, wskaźnik do danych, wskaźnik do funkcji ... wszystko tylko ints. Możesz przekazywać wskaźniki funkcji do innych funkcji, tworzyć listy wskaźników funkcji ... Heck, jeśli pracujesz w języku asemblera, funkcje są tak naprawdę tylko adresami w pamięci, w których przechowywane są bloki instrukcji maszyny. Nadanie funkcji nazwy jest dodatkowym obciążeniem dla osób, które potrzebują kompilatora do pisania kodu. W związku z tym funkcje były „pierwszej klasy” w całkowicie niefunkcjonalnym języku.
Jeśli jedyne, co robisz, to obliczanie wzorów matematycznych w REPL, możesz być funkcjonalnie czysty ze swoim językiem. Ale większość programów biznesowych ma skutki uboczne. Utrata pieniędzy w oczekiwaniu na zakończenie długotrwałego programu to efekt uboczny. Podejmowanie jakichkolwiek działań zewnętrznych: zapisywanie do pliku, aktualizowanie bazy danych, rejestrowanie zdarzeń w kolejności itp. Wymaga zmiany stanu. Możemy dyskutować o tym, czy stan jest naprawdę zmieniony, jeśli umieścisz te działania w niezmiennych opakowaniach, które odpychają skutki uboczne, aby Twój kod nie musiał się o nie martwić. Ale to tak, jakby dyskutować, czy drzewo wydaje hałas, jeśli pada w lesie, a nikt go nie słyszy. Faktem jest, że drzewo zaczęło się wyprostować i skończyło na ziemi. Stan zmienia się, gdy rzeczy się skończą, nawet jeśli tylko po to, aby zgłosić, że coś się skończyło.
Pozostaje nam więc skala funkcjonalnej czystości, nie czerni i bieli, ale odcienie szarości. W tej skali im mniej skutków ubocznych, tym mniejsza zmienność, tym lepiej (bardziej funkcjonalnie).
Jeśli absolutnie potrzebujesz efektu ubocznego lub stanu mutable w swoim skądinąd funkcjonalnym kodzie, dążysz do enkapsulacji lub z reszty twojego programu najlepiej jak potrafisz. Używanie zamknięcia (lub czegokolwiek innego) w celu wstrzyknięcia efektów ubocznych lub stanu zmiennego w poza tym czyste funkcje jest przeciwieństwem programowania funkcjonalnego. Jedynym wyjątkiem może być to, że zamknięcie było najskuteczniejszym sposobem na zamknięcie skutków ubocznych kodu, do którego jest przekazywany. Nadal nie jest to „programowanie funkcjonalne”, ale może być szafą, którą można uzyskać w określonych sytuacjach.
źródło