Programowanie funkcyjne - ebook
Programowanie funkcyjne - ebook
Programowanie funkcyjne jest jednym z głównych paradygmatów programowania. W rozmowach o programowaniu funkcyjnym zawsze pojawiają się określone pojęcia i zagadnienia. Rekurencja. Leniwe obliczenia. Przezroczystość odwołań. Eliminowanie efektów ubocznych. Funkcje jako obiekty pierwszej klasy. Funkcje wyższego poziomu. Rozwijanie (currying). Dopasowywanie wzorców. W książce Programowanie funkcyjne. Poznaj Clojure Elixir Haskell Scala Swift autorzy poruszają wszystkie te zagadnienia, spoglądając na nie z perspektywy pięciu różnych języków programowania.
Ale programowanie funkcyjne to nie jest podejście typu „wszystko albo nic”. Całkiem sensowne jest napisanie kodu imperatywnego, który wykorzystuje niektóre techniki funkcyjne, praktyki i struktury danych. Do przyjęcia jest mieszanie i dopasowywanie stylów, a niektóre języki programowania są wręcz zaprojektowane do budowy hybrydowej, pozwalając na korzystanie ze stylu, który najlepiej pasuje do bieżących potrzeb użytkownkia. W książce Programowanie funkcyjne. Poznaj Clojure Elixir Haskell Scala Swift autorzy analizują te różne podejścia, a czytelnicy sami mogą zdecydować, co im najbardziej pasuje.
Kategoria: | Programowanie |
Zabezpieczenie: |
Watermark
|
ISBN: | 978-83-01-21046-5 |
Rozmiar pliku: | 2,0 MB |
FRAGMENT KSIĄŻKI
Michael Swaine
W tej książce pokazano podejście do paradygmatu programowania funkcyjnego w pięciu różnych językach. Kolejne rozdziały zostały napisane przez ekspertów w zakresie tych języków pojawiły się pierwotnie jako artykuły w czasopiśmie PragPub. Po opublikowaniu niemal stu wydań czasopisma stało się jasne, że dysponujemy bogactwem informacji o programowaniu funkcyjnym i zdecydowaliśmy, że najlepsze artykuły złożą się na ciekawą książkę.
Programowanie funkcyjne jest jednym z głównych paradygmatów programowania. Podchodzimy w nim do obliczeń jako do wyznaczania wartości funkcji matematycznych, w miarę możliwości unikając zmiany stanu i mutowalnych danych.
W rozmowach o programowaniu funkcyjnym zawsze pojawiają się określone pojęcia i zagadnienia: rekurencja, leniwe obliczenia, przezroczystość odwołań, eliminowanie efektów ubocznych, funkcje jako obiekty pierwszej klasy, funkcje wyższego rzędu, rozwijanie (currying), dopasowywanie do wzorców. Autorzy poruszają wszystkie te tematy, spoglądając na nie z perspektywy różnych języków.
Ale programowanie funkcyjne to nie jest podejście typu „wszystko albo nic”. Całkiem sensowne jest napisanie kodu imperatywnego, który wykorzystuje niektóre techniki funkcyjne, praktyki i struktury danych. Do przyjęcia jest łączenie i dopasowywanie styli, a niektóre języki programowania są zaprojektowane jako hybrydy, pozwalając na używanie stylu, który najlepiej pasuje do naszych bieżących potrzeb – na przykład Scala, Mathematica lub Swift. Autorzy analizują w książce te różne podejścia, a my sami możemy zdecydować, co nam najbardziej odpowiada.
Książka zawiera analizy programowania funkcyjnego w pięciu językach, opisane przez ekspertów w zakresie danego języka. Jeśli zachęci to Czytelników do przeczytania pełnego przewodnika po niektórych językach, to polecamy książki: Venkat Subramaniam, Pragmatic Scala , Stuart Halloway i Aaron Bedra, Programming Clojure (wyd. 2) , Dave Thomas, Programming Elixir 1.3 , Chris Eidhof, Florian Kugler i Wouter Swiersta, Functional Swift (https://www.objc.io/books/functional-swift/), a także Miran Lipovaca, Learn You a Haskell for Great Good!: A Beginner’s Guide .
W tej książce znajdziecie analizę ważnego paradygmatu programowania w pięciu językach napisaną przez zespół ekspertów. To chcieliśmy uzyskać.
Mam nadzieję, że zgodzicie się, że wyszła z tego ciekawa książka.Podziękowania
Nie po raz pierwszy wydawca może z przyjemnością podziękować kolegom z grupy Pragmatic Programmers – profesjonalistom, z którymi przyjemnie się pracuje, i cudownym ludziom. Książka ta wiele zyskała dzięki umiejętności oceny wydawcy Andy Hunt, która rozumiała wizję książki; wiceprezes ds. działań Janet Furlow, która doprowadziła do jej zrealizowania; głównej redaktorki Susannah Davidson Pfalze, która pomogła nadać jej kształt, oraz redaktorce Nicole Abramowitz, która poprawiła niedociągnięcia.
Większość rozdziałów tej książki pojawiła się najpierw jako artykuły w czasopiśmie PragPub i jestem wdzięczny autorom nie tylko za oryginalne artykuły, lecz także za redakcję – w niektórych przypadkach znaczną – która pomogła przekształcić artykuły w rozdziały książki. Przeczytajcie tekst O autorach. Myślę, że będziecie pod wrażeniem.
Muszę też podziękować firmie Gilson Graphics za pracę przy projekcie i produkcji. Nancy Groth dziękuję za redakcję oryginalnych artykułów, a redaktorom technicznym – Paulowi Butcherowi, Ianowi Dees, Jeffowi Heonowi, Ronowi Jeffriesowi i Kim Shrier – za poświęcenie czasu i wiedzy.
Mam nadzieję, że spodoba wam się ta książka, która ma zapoczątkować serię antologii PragPub.ROZDZIAŁ 1
Programowanie funkcyjne wraca do łask
Michael Swaine
Lato, gdy przeniosłem się do Doliny Krzemowej, było tym latem, w którym zlikwidowano ILLIAC IV w Moffett Field.
Widzieliśmy już ten film
W 1981 roku centrum zainteresowania informatyki przeniosło się z ogromnych maszyn obsługiwanych przez kapłanów w białych fartuchach na tanie komputery osobiste tworzone i programowane przez niechlujnych hakerów. Ja przeniosłem się ze środkowego zachodu do Palo Alto i zgłosiłem się do pomocy w utworzeniu nowego tygodnika obsługującego tych niechlujów. W międzyczasie, w niewielkiej odległości – w ośrodku NASA Moffett Field oficjalnie zamknięto i rozłożono na części komputer, który zainspirował Stanleya Kubricka i Arthura C. Clarke’a do wymyślenia komputera HAL 9000.
ILLIAC IV to legendarny punkt zwrotny w projektowaniu komputerów, mający podobną pozycję w długiej i skomplikowanej historii programowania funkcyjnego.
Koncepcją leżącą u podstaw ILLIAC IV było oderwanie się od modelu sekwencyjnego, który od początku dominował w informatyce. Pewne obszary problemów, takie jak mechanika płynów, lepiej nadawały się do przetwarzania równoległego, a ILLIAC IV został specjalnie zaprojektowany dla tego rodzaju równoległych problemów – takich, w których jedna instrukcja mogła zostać zastosowana równolegle do wielu zbiorów danych. Jest to znane pod skrótem SIMD (single instruction, multiple data – jedna instrukcja, wiele danych). Bardziej ogólny przypadek – MIMD (multiple instructions operating on multiple data sets – wiele instrukcji działających na wielu zbiorach danych) jest trudniejszy. Ale każdy rodzaj równoległego programowania wymaga nowych algorytmów i nowego podejścia. I stanowi zaproszenie do programowania funkcyjnego.
Programowanie funkcyjne, określane skrótem FP, zgodnie z Wikipedią „traktuje obliczenia jako wyznaczanie matematycznych wartości funkcyjnych i unika zmiany stanu i zmiany danych”. Może to posłużyć za podstawową definicję, ale my musimy się zagłębić dużo bardziej.
FP funkcjonuje od lat pięćdziesiątych XX wieku, gdy John McCarthy wymyślił język Lisp, ale dążenie do przetwarzania równoległego dało programowaniu funkcyjnemu nowy impet. Unikanie zmiany stanu było wtedy trudną do przełknięcia pigułką, ale pasowało do modelu SIMD. To doprowadziło do rozwoju nowych języków i nowych cech FP w istniejących językach. Fortran był wtedy językiem, w którym większość wykonywała obliczenia naukowe, więc naturalne było jego rozszerzanie. Programiści ILLIAC IV mogli pisać programy w IVTRAN, TRANQUIL lub CFD, zrównoleglonych wersjach FORTRAN-u. Powstała też zrównoleglona wersja języka ALGOL.
Dla odpowiednich typów problemów, które można rozwiązać za pomocą SIMD, ILLIAC IV był najszybszym komputerem na świecie i utrzymał ten tytuł do czasu jego wycofania z eksploatacji w 1981 roku. Był o rząd wielkości szybszy od każdego komputera w tamtych czasach i był doskonale dopasowany do swoich docelowych zastosowań i programowania funkcyjnego.
Ale era ta gwałtownie się skończyła. 7 września 1981 roku ILLIAC IV został na dobre zatrzymany. Można dziś zobaczyć jego fragment w muzeum historii komputerów w Mountain View, niedaleko miejscowości Moffett Field.
Dlaczego ta era dobiegła końca? Według Wikipedii: „Illiac IV należał do klasy komputerów równoległych, określanych jako SIMD. Prawo Moore’a prześcignęło specjalizowane podejście stosowane w SIMD ILLIAC, sprawiając, że podejście MIMD stało się preferowane dla niemal wszystkich obliczeń naukowych”.
Po drodze zyskał jeszcze jedno miejsce w historii – inspirację dla komputera HAL 9000 w filmie 2001: Odyseja kosmiczna. Arthur C. Clarke nie był neofitą w kwestii komputerów – rozmawiał z Turingiem w Bletchley Park i pilnie śledził postępy w dziedzinie mikrokomputerów. Clarke był zaintrygowany, gdy dowiedział się o działaniu ILLIAC IV na Uniwersytecie Stanu Illinois w Urbana-Champaign i uczcił ten kampus w swoim filmie jako miejsce narodzin HAL-a 9000.
Przejdźmy do roku 2000. Zastosowanie, które sprawiło, że FP stało się warte nauki, okazało się znowu przetwarzaniem równoległym, ale napędzanym pojawieniem się procesorów wielordzeniowych. „Twórcy chipów”, jak wtedy twierdziłem, „mówili w istocie, że wdrażanie prawa Moore’a to teraz domena oprogramowania. Oni skoncentrują się na wstawianiu coraz więcej rdzeni do kości, a programiści muszą tak przerobić swoje programy, aby wykorzystać możliwości przetwarzania równoległego w ich układach”.
Impet, jaki zyskało FP na początku XXI wieku, wynikał z chęci oderwania się od modelu sekwencyjnego, przy czym zaoferowane zostały różne podejścia. Osoby, które zainwestowały wiele w umiejętności i narzędzia w Javie, nie chciały odkładać na bok bibliotek kodów, umiejętności i narzędzi, na których się opierały, mogły używać języka Scala Martina Odersky’ego, ostatnio wprowadzonego na platformie Javy. Zaoferowano go też na platformie .NET, choć (niestety) zaniechano jego wsparcia w 2012 roku. Użytkownicy .NET powinni raczej przymierzać się do F#, utworzonego przez Dona Syme’a z Microsoft Research. Kto chce mieć czysto funkcyjny język, może skorzystać z Erlang, opracowanego dla wysoce równoległego programowania przez Joe Armstronga z Ericssona, z języka Haskell lub wielu innych możliwości.
Tym, co oferują wszystkie te języki, jest możliwość pracy w paradygmacie funkcyjnym. Dwie podstawowe cechy paradygmatu funkcyjnego to potraktowanie wszystkich obliczeń jako wyznaczania wartości funkcyjnych oraz unikanie zmiany stanu i mutowalnych danych. Ale są też inne wspólne cechy programowania funkcyjnego:
• funkcje pierwszoklasowe – funkcje mogą służyć jako argumenty i wyniki funkcji;
• rekurencja jako podstawowe narzędzie iteracji;
• szerokie stosowanie dopasowywania do wzorców;
• leniwe wartościowanie, co pozwala na tworzenie nieskończonych ciągów.
Programiści iteracyjni, którzy po raz pierwszy stykają się z FP, mogą jeszcze dodać – jest ono wolne i niejasne. W istocie żadna z tych opinii nie występuje zawsze, ale wymaga to zmiany sposobu myślenia o problemach i różnych algorytmach. Aby programy działały odpowiednio, muszą mieć lepsze wsparcie ze strony języka niż to, które było powszechne w pierwszej dekadzie XXI wieku. To się zmieni w kolejnym dziesięcioleciu, a wraz z tym zmienią się przyczyny, dla których FP przyciąga ludzi do siebie.
Nowe argumenty za programowaniem funkcyjnym
Po upływie dekady ponownie wróciła sprawa programowania funkcyjnego. Pojawiło się nowe wsparcie językowe i sprawa FP stała się szersza.
Chociaż równoległość była tradycyjnie siłą napędową programowania funkcyjnego, teraz gdy mówi się o FP, częściej odwołuje się do zdolności spojrzenia na problem na wyższym poziomie oraz o zaletach niemutowalności. Zamiast patrzeć na FP jak na dziwny sposób, który trzeba zastosować, aby poradzić sobie z równoległością, nowym argumentem jest bardziej naturalny sposób pisania, bliższy pojęciom i terminologii dla naszej dziedziny. Ludzie używają FP do aplikacji, które nie wymagają równoległości, aby programować bardziej efektywnie i jasno, działając bliżej sposobu myślenia o swoich problemach.
Mówi się, że zamiast konieczności przełożenia problemu na język programowania, adaptujemy język do problemu.
A jeśli mamy dostępnych wiele rdzeni i równoległość może być korzystna dla naszego kodu, to mamy równoległość za darmo. Neal Ford powiedział: „Ostatnie mądre innowacje w bibliotekach pozwoliły przepisać funkcję odwzorowania tak, aby automatycznie stawała się równoległa, co oznacza, że wszystkie działania związane z odwzorowaniami skorzystają z przyspieszenia wydajności bez interwencji dewelopera”.
Dobre dopasowanie FP do współbieżności przemawia do ludzi, którzy piszą aplikacje wieloprocesorowe, aplikacje o dużej dostępności, serwery WWW dla sieci społecznościowych i do wielu innych zastosowań. Wyższy poziom abstrakcji FP przemawia do osób, które szukają krótszego czasu pracy dewelopera lub lepiej zrozumiałego kodu. FP kładzie nacisk na niemutowalność, co silnie przemawia do każdego, kto jest zainteresowany niezawodnością.
Wzrost sprzedaży stanowi prawdziwą zmianę z argumentacji na rzecz programowania funkcyjnego z czasów ILLIAC IV. Dziś są nowe powody, aby spojrzeć na FP, a także lepsze wsparcie językowe FP i większy wybór podejść. Zaczyna się to od wyboru języka. Można wygodnie trzymać się znanych sobie języków i ich narzędzi, wprowadzając funkcyjność tam, gdzie jest dla nas użyteczna. Lub też można przejść do języka zbudowanego od podstaw do programowania funkcyjnego. W tej książce zobaczymy oba te podejścia.
To ekscytujący czas na poznawanie programowania funkcyjnego, a korzyści z niego wynikające są znaczne. Ale wymaga to jednak innego sposobu myślenia.
W następnym rozdziale Michael Bevilacqua-Linn zaprasza nas do myślenia o programowaniu w sposób funkcyjny.ROZDZIAŁ 2
Myślenie funkcyjne dla imperatywnego umysłu
Michael Bevilacqua-Linn
Dokładne określenie, czym w istocie jest programowanie funkcyjne, może być trudne.
Nie jest to łatwe, gdyż termin ten, choć dobrze zdefiniowany teoretycznie, w praktyce wskazuje kilka różnych, choć powiązanych ze sobą idei. Jeśli porozmawiamy z hakerem Closure, zapewne dużo będzie mówił o makrach. Programista w Haskellu może mówić o monadach, a programista w Erlangu o aktorach.
Są to różne pojęcia. Makro dają programistom niezwykle silne możliwości metaprogramowania, monady pozwalają na bezpieczne modelowanie zmian stanu, aktory zaś zapewniają rozbudowany sposób realizacji programowania rozproszonego i współbieżnego.
Mimo to wszystkie te różne idee uważa się za cechy języków funkcyjnych. Mnogość pojęć może nieco utrudnić określenie, o co w tym wszystkim chodzi. A jednak podstawowa idea jest prosta.
Wszystko sprowadza się do funkcji
W swojej istocie programowanie funkcyjne dotyczy programowania z czystymi funkcjami, bez efektów ubocznych.
Oto czysta funkcja:
f(x) =
A więc ta jest czysta:
public int incrementCounter(int counter) {
return counter++;
}
a ta nie jest:
private int counter = 0;
public void incrementMutableCounter() {
counter++;
}
Pierwsze dwa przykłady zwiększają licznik, zwracając nową wartość całkowitą, która jest o jeden większa od przekazanej liczby całkowitej. Trzeci przykład robi to samo, ale zmienia stan, który może być wspólny dla wielu fragmentów programu.
Definicja – funkcja taka jak incrementCounter, która nie jest oparta na zmianie stanu, jest nazywana funkcją czystą. A ta jej czystość daje wiele korzyści. Jeśli na przykład mamy funkcję czystą, która wykonuje jakieś kosztowne obliczenia, możemy zoptymalizować nasz program, wywołując tę funkcję tylko raz i przechwytując wynik – czyli stosując technikę zwaną memoizacją.
Czyste funkcje ułatwiają też analizę programów. Program obiektowy to graf obiektów, gdzie każdy ma wiele mutowalnych stanów. Modyfikacja stanu jednego z obiektów może prowadzić do modyfikacji stanu innego, który może się znajdować w grafie w odległości wielu węzłów. W programie, który ma tylko czyste funkcje, takie działanie na odległość jest niemożliwe.
To jest uproszczony opis programowania funkcyjnego.
Chodzi o niemutowalność
Niestety, pełna czystość nie ma się najlepiej w świecie rzeczywistym. Czyste funkcje mogą dobrze modelować pewne dziedziny, ale do innych nie pasują. Kompilatory to czyste funkcje. Wyszukiwanie w Google nie jest czyste.
Praktyczne języki programowania funkcyjnego kładą nacisk na niemutowalność i czystość funkcyjną, ale muszą mieć też możliwość modelowania zmieniającego się świata, który nie zapewnia pełnej czystości funkcji. W języku Haskell, który jest zapewne najbardziej ścisłym językiem funkcyjnym, można modelować zmiany, wykorzystując monady, zachowując przy tym ścisłą czystość.
Inne języki funkcyjne mają inne techniki minimalizowania i kontroli zmiany stanu, które nie są tak ścisłe jak te z Haskela. Clojure na przykład używa systemu programowej pamięci transakcyjnej w połączeniu ze zbiorem typów referencyjnych oraz bardzo sprytnych niemutowalnych struktur danych, aby utrzymać wysoki poziom czystości, a jedocześnie pozwolić programistom na radzenie sobie w zmieniającym się świecie.
Chodzi o sposób myślenia
Tak więc w pierwszym przybliżeniu programowanie funkcyjne to programowanie z czystymi funkcjami i niemutowalnym stanem, w przeciwieństwie do programowania imperatywnego, które silnie zależy od mutowalności. Wokół tej niemutowalnej podstawy mamy zbiór technik językowych i cech, które zastępują mutowalne techniki imperatywne. Ich analiza da nam lepsze poczucie, czym jest myślenie i programowanie funkcyjne.
Popatrzmy na prosty przykład – filtrowanie listy tak, aby zawierała tylko liczby nieparzyste.
public List
List
for(Integer current : list) {
if(1 == current % 2) {
filteredList.add(current);
}
}
return filteredList;
}
To jest kod imperatywny. Iterujemy po liście i sprawdzamy dla każdego elementu, czy liczba jest nieparzysta, obliczając jej modulo. Moglibyśmy napisać jej zawartość nieco jaśniej, gdybyśmy wyciągnęli sprawdzenie do funkcji pomocniczej i nadali jej nazwę.
public List
List
for (Integer current : list) {
if (isOdd(current)) {
filteredList.add(current);
}
}
return filteredList;
}
private boolean isOdd(Integer integer) {
return 1 == integer % 2;
}
Co należy zrobić, jeśli chcemy teraz utworzyć funkcję, która pozwoli nam odfiltrować liczby parzyste zamiast nieparzystych? Jedyny fragment kodu, który trzeba zmienić, to wywołanie isEven zamiast isOdd.
public List
List
for (Integer current : list) {
if (isEven(current)) {
filteredList.add(current);
}
}
return filteredList;
}
private boolean isEven(Integer integer) {
return 0 == integer % 2;
}
To działa, ale popełniliśmy jeden z kardynalnych grzechów kodowania. Większość filterOutEvens została wycięta i wklejona z filterOutOdds. W istocie chcemy mieć filtr (filter), który może korzystać z dowolnego fragmentu kodu, aby wykonać swoje filtrowanie.
Zastanówmy się, jak możemy dokonać tego w języku Java. Zarówno isOdd, jak i isEven pobierają jeden argument i zwracają wartość logiczną (boolean). Zdefiniujmy interfejs, który obejmuje istotę tych obliczeń. Nazwiemy go Predicate, co stanowi matematyczną nazwę funkcji, która zwraca wartość typu boolean.
public interface Predicate {
public boolean evaluate(Integer argument);
}
Możemy teraz przepisać filterEvens i filterOdds tak, aby były bardziej ogólne.
public List
List
for (Integer current : list) {
if (predicate.evaluate(current)) {
filteredList.add(current);
}
}
return filteredList;
}
Następnie definiujemy dwa nasze predykaty.
class isEven implements Predicate {
public boolean evaluate(Integer argument) {
return 0 == argument % 2;
}
}
class isOdd implements Predicate {
public boolean evaluate(Integer argument) {
return 1 == argument % 2;
}
}
Możemy teraz po prostu utworzyć instancję jednego z predykatów i przekazać ją do metody filter. Jeśli pojawi się nowy sposób filtrowania naszej listy – powiedzmy, że zechcemy zachować tylko liczby całkowite, które są idealnymi kwadratami – możemy zdefiniować PerfectSquare zamiast wycinania i wklejania całej funkcji filtrującej.
To, co właśnie zrobiliśmy z metodą filter i interfejsem Predicate, symuluje koncepcje ze świata funkcyjnego – funkcje wyższego rzędu. Są to takie funkcje, które można przekazywać do innych funkcji lub takie, które mogą wracać z innych funkcji. Zastanówmy się, jak zrobilibyśmy podobne filtrowanie w Clojure – nowoczesnym i funkcyjnym wariancie języka Lisp.
(filter odd? )
(filter even? )
To jest to! Pierwsze, co można zauważyć, to znacznie krótszy zapis niż w wersji Javy. Druga rzecz to zapewne fakt, że nawiasy nie znajdują się w swoim zwykłym położeniu. Clojure i inne wersje języka Lisp do wywoływania funkcji wykorzystują notację z prefiksem. Oznacza to, że funkcja, która jest wywoływana, znajduje się na pierwszym miejscu w nawiasach, a argumenty są umieszczane po niej.
Pomijając różnice składniowe, zauważmy, jak wersja z Clojure używa wszystkich wbudowanych funkcji i cech języka. Nie musimy definiować interfejsu Predicate. Funkcja odd? to funkcja, która pobiera liczbę i zwraca true, jeśli jest ona nieparzysta, natomiast even? robi to samo w odniesieniu do liczb parzystych. Możemy przekazać te funkcje bezpośrednio do funkcji filtrującej dzięki mocy funkcji wyższego rzędu.
To zmienia nasze rozwiązanie imperatywne, w którym pisaliśmy kod związany ze szczegółami związanymi z iteracją po liście, w postać bardzo deklaratywną. Pracujemy na wyższym poziomie abstrakcji, który prowadzi nas często do opisu, jakich wyników potrzebujemy, zamiast szczegółów ich otrzymywania.
Gdy więc mowa jest o programowaniu funkcyjnym, to zwykle chodzi o co najmniej dwie oddzielne, choć powiązane ze sobą sprawy. Po pierwsze, mówimy o programowaniu za pomocą czystych funkcji. Ponieważ jest to nieosiągalne marzenie przy większości rzeczywistych problemów, w praktyce funkcyjne języki programowania koncentrują się na łatwiejszym stosowaniu niemutowalności oraz na możliwości kontrolowania zmienności, gdy jest ona rzeczywiście niezbędna.
Po drugie, mówi się o stylu programowania, który wyrósł wokół podstawy funkcyjnej. Jak widzieliśmy, ten styl opiera się w znacznym stopniu na funkcjach wyższego rzędu i związanych z tym technikami. Techniki te często tworzą kod, który opiera się na wyższym poziomie abstrakcji, jaki widzieliśmy przy korzystaniu z funkcji filtrowania w powyższym przykładzie, a nie na jawnych iteracjach.
Te dwa aspekty programowania funkcyjnego dają znaczące korzyści. Wyjątkowy nacisk na niemutowalność sprawia, że programy są łatwiejsze do zrozumienia. Zachowanie funkcji można zrozumieć, czytając kod samej funkcji i nie martwiąc się o to, że jakiś fragment zmiennego stanu może zależeć od czegoś, co jest oddalone o setki lub tysiące wierszy kodu. Korzystanie z funkcji wyższego rzędu prowadzi często do kodu deklaratywnego, który jest prostszy i bardziej bezpośredni od odpowiednika imperatywnego.
Nie myli się więc ktoś, kto myśląc o programowaniu funkcyjnym, rozważa dwie rzeczy: preferowanie programowania z czystymi funkcjami i styl programowania obejmujący funkcje wyższego rzędu w kodzie deklaratywnym.
Kolejnych kilkanaście rozdziałów jest uporządkowanych według języków, a kilka z nich poświęcono programowaniu funkcyjnemu w tych językach. Można przeskoczyć bezpośrednio do języka, który nas najbardziej interesuje, lub czytać rozdziały po kolei. Ale najlepiej zacząć od następnego rozdziału, w którym Venkat Subramaniam pokaże nam jak Scala, hybrydowy język funkcyjny, wdraża omawiane wyżej problemy.