Dane oryginału
Original edition copyright © 2016–2018 Getify Solutions, Inc. Title of English-language original: Functional-Light JavaScript: Balanced, Pragmatic FP in JavaScript by Kyle Simpson, ISBN 9781617296512. Polish-language edition © 2020 by Polish Scientifi c Publishers PWN Wydawnictwo Naukowe PWN Spółka Akcyjna. All Rights reserved.

Przekład

Małgorzata Dąbkowska-Kowalik i Witold Sikorski na zlecenie WITKOM Witold Sikorski

Projekt okładki polskiego wydania na podstawie oryginału

Joanna Andryjowicz

Wydawca

Wioleta Szczygielska-Dybciak

Redaktor prowadzący

Jolanta Kowalczuk

Redaktor

Mariusz Dobkowski

Koordynator produkcji

Anna Bączkowska

Konsultant merytoryczny

Paweł Łukasik

Skład wersji elektronicznej na zlecenie Wydawnictwa Naukowego PWN

Anna Jakubowska / Woblink

Zastrzeżonych nazw firm i produktów użyto w książce wyłącznie w celu identyfikacji.

Copyright © by Wydawnictwo Naukowe PWN SA

Warszawa 2020

ISBN 978-83-01-21225-4

eBook został przygotowany na podstawie wydania papierowego z 2020 r., (wyd. I)

Warszawa 2020

Wydawnictwo Naukowe PWN SA

02-460 Warszawa, ul. Gottlieba Daimlera 2

tel. 22 69 54 321, faks 22 69 54 288

infolinia 801 33 33 88

e-mail: pwn@pwn.com.pl; reklama@pwn.pl

www.pwn.pl

Spis treści

Słowo wstępne

Wprowadzenie

Misja

Rozdział 1. Dlaczego programowanie funkcyjne?

Pierwszy rzut oka

Zaufanie

Komunikacja

Czytelność

Perspektywa

Jak znaleźć równowagę

Zasoby

Podsumowanie

Rozdział 2. Natura funkcji

Czym jest funkcja?

Wejście funkcji

Nazwane argumenty

Wyjście funkcji

Funkcje z funkcji

Składnia

Czym jest this?

Podsumowanie

Rozdział 3. Zarządzanie wejściami funkcji

Wszyscy za jednego

Dostosowywanie argumentów do parametrów

Niektóre teraz, niektóre później

Po jednym

Kolejność ma znaczenie

Bez punktów

Podsumowanie

Rozdział 4. Złożenie funkcji

Wyjście na wejście

Złożenie ogólne

Przekształcone złożenie

Abstrakcja

Punkty raz jeszcze

Podsumowanie

Rozdział 5. Redukowanie efektów ubocznych

Efekty na bok, poproszę

Dziękuję, raz wystarczy

Czysta rozkosz

Jest tam lub nie

Oczyszczanie

Podsumowanie

Rozdział 6. Niemutowalność wartości

Niemutowalność wartości podstawowych

Wartość do wartości

Ponowne przypisanie

Wydajność

Traktowanie wartości

Podsumowanie

Rozdział 7. Domknięcie a obiekt

Zgodność zdań

Podobieństwa

Dwie drogi rozdzieliły się w lesie…

Podsumowanie

Rozdział 8. Rekurencja

Definicja

Deklaratywna rekurencja

Stos

Zmiana kolejności rekurencji

Podsumowanie

Rozdział 9. Działania na listach

Niefunkcyjne przetwarzanie list

Map

Filtr

Reduce

Zaawansowane działania na listach

Metoda kontra samodzielna funkcja

Poszukiwanie list

Łączenie (fuzja)

Poza listami

Podsumowanie

Rozdział 10. Funkcyjność asynchroniczna

Czas jako stan

Zachłanne kontra leniwe

Reaktywne programowanie funkcyjne

Podsumowanie

Rozdział 11. Składamy wszystko razem

Konfiguracja

Zdarzenia giełdowe

Interfejs użytkownika chronometru giełdowego

Podsumowanie

Dodatek A. Transdukcja

Najpierw – dlaczego

Teraz – jak

I wreszcie – co

Podsumowanie

Dodatek B. Skromna monada

Typ

Luźny interfejs

Po prostu monada

Maybe

Skromnie

Podsumowanie

Dodatek C. Biblioteki FP

Materiał do analizy

Ramda (0.23.0)

Lodash/fp (4.17.4)

Mori (0.3.2)

Bonus: FPO

Bonus 2: fasy

Podsumowanie

Przypisy

Słowo wstępne

Nie jest tajemnicą, że należę do maniaków programowania funkcyjnego. Propaguję idee i funkcyjne języki programowania, gdzie tylko mogę, próbuje czytać najnowsze prace naukowe i studiuję abstrakcyjną algebrę w wolnym czasie. Nawet w JavaScripcie odmawiam pisania nieczystych programów, co doprowadziło do powstania książki Professor Frisby’s Mostly Adequate Guide to Functional Programming. Tak, jestem całkowitym dogmatycznym zelotą.

Nie zawsze tak było… Kiedyś fascynowałem się obiektami. Kochałem modelowanie „prawdziwego świata”. Byłem wynalazcą syntetycznych automatów, dłubałem przy nich nocą, aby osiągnąć mistrzowską precyzję. Byłem twórcą samoświadomych kukiełek, przebierającym palcami po klawiaturze, aby tchnąć w nie życie – prawdziwy Geppetto 1337 h4x0r. Jednak po pięciu solidnych latach pisania kodu obiektowego okazało się, że nigdy nie byłem zadowolony z wyniku. Nigdy nie był on dla mnie dość dobry. Straciłem wiarę, że prosty, elastyczny kod w rozsądnej skali jest w ogóle osiągalny.

Doszedłem do wniosku, że spróbuję czegoś innego: programowania funkcyjnego (FP). Zacząłem parać się funkcyjnymi pomysłami w moim dotychczasowym kodzie i ku przerażeniu współpracowników nie miałem pojęcia, co właściwie robię. Kod, jaki pisałem wtedy, był okropny. Potworny. Cyfrowy ściek. Powodem był brak jasnej wizji lub celu w tym, co chciałem osiągnąć. Nie miałem mojego Jiminy’ego Świerszcza, który by mnie poprowadził. Minął długi czas i dużo śmieciowych programów, zanim doszedłem do tego, czym jest FP.

Po tej całej zabałaganionej eksploracji czuję, że programowanie funkcyjne dało mi to, co obiecywało. Istnieją czytelne programy! Istnieje możliwość ponownego ich wykorzystania! Nie muszę wymyślać swego modelu, mogę go odkrywać. Stałem się sprytnym detektywem odkrywającym rozpowszechnione spiski, mając tablicę korkową pełną dowodów matematycznych. Jak Cousteau wieku cyfrowego, odkrywałem cechy tej dziwnej krainy w imię nauki. Nie jestem doskonały i wiele się jeszcze muszę nauczyć, ale nigdy praca nie przynosiła mi tyle satysfakcji i nigdy tak nie cieszyły mnie jej rezultaty.

Gdyby taka książka istniała, gdy zaczynałem, moje przejście do świata programowania funkcyjnego byłoby znacznie łatwiejsze i mniej destrukcyjne. Ta praca przynosi dwojakie korzyści[1]: nie tylko nauczy, jak skutecznie używać w naszym codziennym kodzie FP różnych konstrukcji, lecz także, co ważniejsze, da nam cel – podstawowe zasady, które pozwolą nam utrzymać kierunek.

Nauczycie się lekkiej funkcyjności: paradygmat, który Kyle wyraził jako pierwszy, aby umożliwić programowanie funkcjonalne, dając równowagę i interoperacyjność z resztą świata JavaScriptu. Zrozumiecie podstawy, na których zbudowane jest czyste programowanie funkcyjne, bez konieczności wpisywania się w jego paradygmat w całości. Zyskacie umiejętności praktykowania i analizowania FP bez konieczności przepisywania istniejącego kodu, aby lepiej współpracował z nowym. Możecie zrobić krok do przodu w swoje karierze w dziedzinie oprogramowania, bez cofania się i bezcelowego błądzenia, jak się to mi zdarzyło przed laty. Współpracownicy i koledzy ucieszą się!

Kyle to świetny nauczyciel znany ze swojego niezmordowanego dążenia do pokazania całości, bez pozostawiania niezbadanych zakątków i luk, który jednocześnie ma empatię dla ciężkiego losu uczących się. Jego styl współgra z branżą, podnosząc nas wszystkich na wyższy poziom. Jego praca ma trwałe miejsce w historii języka JavaScript i wielu ma odnośniki do nich w zakładkach. Jesteście w dobrych rękach.

Programowanie funkcyjne ma wiele różnych definicji. Definicja programisty Lispa jest całkiem odmienna od perspektywy Haskella. FP w OCaml w niewielkim stopniu przypomina paradygmaty znane z Erlanga. Znaleźć można nawet kilka konkurujących definicji w JavaScripcie. Jest jednak coś, co to wszystko wiąże ze sobą – dość mglista definicja, którą można zrozumieć, gdy się ją widzi, trochę jak obsceniczność (niektórzy nawet twierdzą, że FP jest obsceniczne!), a w książce tej bez wątpienia udaje się uchwycić ten aspekt. Wynik końcowy nie będzie w niektórych przypadkach uznany za specyficzny, ale wiedza tu nabyta bezpośrednio odnosi się do dowolnego rodzaju FP. Ta książka jest wspaniałym miejscem do rozpoczęcia podróży przez FP. Zaczynaj, Kyle…

Brian Lonsdorf (@drboolean)

Wprowadzenie

Monada jest tylko monoidem w kategorii endofunktorów.

Czy nie straciłem właśnie czytelników? Nie martwcie się, ja też byłbym zagubiony! Wszystkie te terminy, które znaczą coś tylko dla osób wprowadzonych w programowanie funkcyjne (FP), mogą brzmieć jak bełkot dla pozostałych osób.

Z tej książki nie dowiecie się, co te słowa znaczą. Jeśli ktoś tego szuka, niech szuka dalej. W istocie jest wiele książek, które od razu uczą nas FP, od początku do końca. Te słowa mają istotne znaczenie i przy formalnej szczegółowej nauce FP, każdy z pewnością je pozna.

Ale ta książka całkiem inaczej podchodzi do tematu. Zaprezentuję tu najbardziej podstawowe pojęcia FP od początku do końca, z mniejszą liczbą specjalnych lub nieintuicyjnych terminów, które pojawiają się w innych książkach o FP. Próbuję przyjąć praktyczne podejście do każdej zasady, zamiast spojrzenia czysto akademickiego. Bez wątpienia będzie tu terminologia. Ale jej wprowadzanie będzie ostrożne i celowe, z wytłumaczeniem, po co one są, i dlaczego są ważne.

Niestety nie jestem posiadaczem karty członkowskiej FP Cool Kids Club. Nigdy nikt formalnie nie uczył mnie, jak programować w FP. I choć mam za sobą studia wyższe i niezłą wiedzę matematyczną, zapis matematyczny nie jest w zgodzie z rozumieniem programowania przez mój mózg. Nigdy nie napisałem ani jednego wiersza programu w języku Scheme, Clojure czy w Haskelu. I nie jestem ze starej szkoły programistów Lispa.

Brałem udział w dyskusjach na wielu konferencjach na temat FP, przy każdej mając desperacką nadzieję, że tym razem wreszcie zrozumiem, o co chodzi w tym całym mistycyzmie związanym z programowaniem funkcyjnym. I za każdym razem odchodziłem sfrustrowany, wszystkie te nowe terminy mieszały mi się w głowie, a ja nie miałem pojęcia, czy i czego się nauczyłem. A najdłużej nie mogłem dojść, o co w tym wszystkim chodzi.

Mając te wszystkie doświadczenia, powoli zbierałem kawałki ważnych pojęć, które wydawały się zbyt naturalne dla formalisty w zakresie FP.

Uczyłem się powoli, pragmatycznie i doświadczalnie, a nie w sposób akademicki z właściwą terminologia. Pewnie każdemu z was zdarzyło się znać jakiś temat przez długi czas, a potem dowiedzieć się, że ma on jakąś nazwę, której wcale nie znaliście!

Może jesteście podobni do mnie. Słyszałem od lat określenia takie jak „map-reduce” czy „big data” w różnych branżach, nie wiedząc, czy są one naprawdę. W rezultacie nauczyłem się co robi funkcja map(..) – na długo przed tym, zanim dowiedziałem się, że działania na listach są jednym z fundamentów na ścieżce programisty funkcyjnego i dlaczego są takie ważne. Wiedziałem czym jest odwzorowanie (map) na długo przed tym, zanim dowiedziałem się, że nazywa się map(..).

W końcu zacząłem zbierać okruchy zrozumienia w zbiór, który nazywam FLP (Functional-Light Programming, „programowanie lekko funkcyjne”).

Misja

Ale dlaczego tak ważna jest nauka programowania funkcyjnego, nawet w lekkiej postaci?

Zacząłem w coś wierzyć w ostatnich latach tak głęboko, że jest to niemal wiara religijna. Wierzę, że programowanie dotyczy ludzi, a nie kodu. Wierzę, że kod jest pierwszym i podstawowym środkiem komunikacji między ludźmi, a to, że instruuje także komputer, jest tylko efektem ubocznym (można tu usłyszeć mój stłumiony chichot).

Widzę to tak, że programowanie funkcyjne dotyczy w istocie stosowania w kodzie dobrze znanych, zrozumiałych i sprawdzonych wzorców, aby unikać błędów, które sprawiają, że kod jest trudniejszy do zrozumienia. Z tego punktu widzenia FP – lub też FLP! – może być jednym z najważniejszych zestawów narzędzi, jakie może pozyskać deweloper.

Przekleństwem monady jest, że – gdy się ją zrozumie – traci się zdolność do wytłumaczenia jej komukolwiek innemu.

Douglas Crockford 2012 „Monads and Gonads” https://www.youtube.com/watch?v=dkZFtimgAcM)

Mam nadzieję, że ta książka „być może” (ang. Maybe – gra słów z wzorcem FP) przełamie ducha tego przekleństwa, choć nie będę tu wspominał o „monadach”, aż do samych dodatków końcowych.

Formalne podejście programisty FP często zakłada, że prawdziwa wartość FP polega na stosowaniu w zasadzie w 100% podejścia wszystko-albo-nic. Wierzy się, że jeśli używamy FP w części swojego programu, a w innej części nie, to cały program jest zanieczyszczony przez rzeczy spoza FP, więc traci tyle, że nie warto stosować FP.

Mówię wyraźnie: jest to absolutnie fałszywe. Głupotą jest dla mnie sugerowanie, że ta książka może być dobra tylko wtedy, jeśli w całej użyję idealnej gramatyki i strony czynnej. Jeśli popełnię jakieś błędy, to zniszczę jakość całej książki. To nonsens.

Im więcej piszę jasnym, spójnym przekazem, tym lepsze będą efekty dla czytelników. Ale nie jestem idealnym autorem. Niektóre fragmenty są napisane lepiej od innych. Ale części, które mógłbym poprawić, nie deprecjonują innych części książki, które są użyteczne.

I podobnie jest z kodem. Im więcej zastosujemy tych zasad w większej części naszego kodu, tym lepszy będzie kod. Używajmy ich przez 25% czasu i otrzymamy pewne korzyści. Używajmy przez 80% czasu, a zobaczymy więcej korzyści. Zapewne z pewnymi wyjątkami, gdyż nie ma w tym tekście stwierdzeń bezwzględnych. Zamiast tego jest mowa o aspiracjach, celach i zasadach, do których trzeba dążyć. Będzie mowa o pragmatyzmie i kompromisach.

Zapraszam w podróż do najbardziej użytecznych i praktycznych podstaw FP. Wszyscy możemy się wiele nauczyć!

Rozdział 1. Dlaczego programowanie funkcyjne?

Programista funkcyjny (rzecz.): Osoba, która nazywa zmienne „x”, nazywa funkcje „f” oraz nazywa wzorce kodu „zygohistomorficznym prepromorfizmem”.

James Iry @jamesiry 13/5/2015 https://twitter.com/jamesiry/status/598547781515485184

Programowanie funkcyjne (FP) nie jest w żadnym razie nowym pojęciem. Jest obecne w niemal całej historii programowania. Jednak, choć nie jestem pewien, czy można tak uczciwie powiedzieć, nie było mainstreamową koncepcją w świecie deweloperów oprogramowania, aż do chwili, gdy kilka lat temu zaszła zmiana. Wydaje mi się, że FP było raczej dziedziną akademicką.

Jednak wszystko to się zmienia. Narasta fala zainteresowania FP, nie tylko na poziomie języka, ale także bibliotek i platform. Być może czytacie ten tekst, gdyż okazało się, że FP jest czymś, czego nie da się dłużej ignorować. Lub może jak ja próbowaliście nauki FP wiele razy, ale nie udało się pokonać terminologii lub notacji matematycznej.

Ten pierwszy rozdział stanowi odpowiedź na takie pytania, jak „Dlaczego należy używać FP w swoim kodzie” lub „Jak można lekko funkcyjny JavaScript porównać z tym, co inni mówią o FP?”. Po podaniu podstaw w całej książce kawałek po kawałku odkrywam techniki i wzorce pisania w lekko funkcyjnym stylu JS.

Pierwszy rzut oka

Zilustrujmy po krótce pojęcie „Lekko funkcyjny JavaScript” za pomocą kawałka kodu przed i po. Rozważmy:

var numbers = [4,10,0,27,42,17,15,-6,58];

var faves = [];

var magicNumber = 0;

pickFavoriteNumbers();

calculateMagicNumber();

outputMsg(); // The magic number is: 42

// ***************

function calculateMagicNumber() {

for (let fave of faves) {

magicNumber = magicNumber + fave;

}

}

function pickFavoriteNumbers() {

for (let num of numbers) {

if (num >= 10 && num <= 20) {

faves.push( num );

}

}

}

function outputMsg() {

var msg = `The magic number is: ${magicNumber}`;

console.log( msg );

}

A teraz popatrzmy na inny styl kodu, który daje dokładnie taki sam wynik:

var sumOnlyFavorites = FP.compose( [

FP.filterReducer( FP.gte( 10 ) ),

FP.filterReducer( FP.lte( 20 ) )

] )( sum );

var printMagicNumber = FP.pipe( [

FP.reduce( sumOnlyFavorites, 0 ),

constructMsg,

console.log

] );

var numbers = [4,10,0,27,42,17,15,-6,58];

printMagicNumber( numbers ); // Magiczna liczba to: 42

// ***************

function sum(x,y) { return x + y; }

function constructMsg(v) { return `The magic number is: ${v}`; }

Gdy zrozumiemy FP i lekkie FP, drugi fragment kodu przeczytamy i mentalnie przetworzymy w poniższy sposób.

Najpierw tworzymy funkcję o nazwie sumOnlyFavorites(..), która jest kombinacją trzech innych funkcji. Łączymy dwa filtry, z których jeden sprawdza, czy wartość jest większa lub równa 10, a drugi, czy jest mniejsza lub równa 20. Następnie dołączamy reduktor sum(..) do kompozycji transduktora. Wynikowa funkcja sumOnlyFavorites(..) jest reduktorem, który sprawdza, czy wartość przechodzi oba testy filtrów, a jeśli tak, to dodaje wartość do wartości akumulowanej.

Potem tworzymy kolejną funkcję o nazwie printMagicNumber(..), która najpierw redukuje listę liczb za pomocą zdefiniowanego wcześniej reduktora sumOnlyFavorites(..), dając w wyniku sumę tylko tych liczb, które przeszły przez sprawdzenie ulubionych wartości (favorite). Następnie printMagicNumber(..) przekazuje potokowo tę końcową sumę do constructMsg(..), która tworzy wartość łańcuchową przekazywaną ostatecznie do console.log(..).

Wszystkie te ruchome elementy przemawiają do dewelopera FP w sposób, który jak na razie może nam się wydać niezrozumiały. Ta książka ma nam pomóc mówić według tych samych reguł, aby było to dla nas czytelne jak każdy inny kod, jeśli nie lepiej!

Oto kilka innych uwag na temat tego porównania kodów:

- Prawdopodobnie dla wielu czytelników pierwszy fragment kodu wygląda na bardziej przyjazny/czytelny/łatwiejszy do utrzymania niż ten drugi. Jeśli tak jest, nie ma się co przejmować. Jesteście we właściwym miejscu. Jestem przekonany, że jeśli będziecie trzymać się tej książki i przerabiać wszystko, o czym jest mowa, drugi fragment kodu stanie się o wiele bardziej naturalny, a może nawet preferowany!

- Może ktoś to zadanie zrobić całkiem inaczej niż oba prezentowane fragmenty. To też nie szkodzi. Ta książka nie podaje przepisów, dyktując, że coś ma być zrobione w określony sposób. Celem jest ilustracja zalet i wad różnych wzorców i umożliwienie wam podejmowania decyzji. Gdy dojdziemy do końca książki, wasze podejście do zadania znajdzie się zapewne nieco bliżej tego drugiego fragmentu, niż jest to teraz.

- Możliwe też jest, że czytelnik to doświadczony deweloper FP, który przegląda początek tej książki, aby zobaczyć, czy znajdzie tam coś użytecznego. Drugi fragment kodu ma zdecydowanie elementy, które będą dobrze znane. Ale założę się też, że kilka razy taka osoba pomyśli „Hmm, ja bym tego nie zrobił w ten sposób…”. To jest w porządku i jest całkiem rozsądne.

Nie jest to tradycyjna, kanoniczna książka o FP. Pokażemy tu kilka podejść, które wydadzą się całkiem heretyczne. Szukamy pragmatycznej równowagi między jasnymi, niezaprzeczalnymi zaletami FP a potrzebą dostarczenia działającego, możliwego do utrzymania kodu JS, bez konieczności ogarnięcia zniechęcającej góry matematyki/notacji/terminologii.

To nie jest wasze FP, to lekko funkcyjny JavaScript. Jakikolwiek jest powód czytania tej książki, zapraszam!

Zaufanie

Mam zawsze bardzo proste założenie, które leży u podstaw wszystkiego, co robię jako nauczyciel tworzenia oprogramowania (w JavaScripcie): kod, któremu nie możemy ufać, to kod, którego nie rozumiemy. Prawdziwe jest także twierdzenie odwrotne: kod, którego nie rozumiemy, jest kodem, któremu nie możemy ufać. Ponadto, jeśli nie potrafimy zrozumieć naszego kodu ani mu ufać, to nie możemy mieć żadnej wiary w to, że pisany przez nas kod jest odpowiedni do postawionego zadania. Uruchamiamy program i po prostu trzymamy kciuki.

Co rozumiemy przez zaufanie? Dla mnie oznacza to, że można go zweryfikować, czytając go i analizując, a nie tylko wykonując, i że rozumiemy, co dany kawałek kodu naprawdę zrobi. Częściej niż jest to rozważane, mamy tendencję, aby polegać na uruchomieniu zestawu testów do zweryfikowania poprawności programu. Nie mam zamiaru sugerować, że testy to coś złego. Ale wydaje mi się, że powinniśmy aspirować do tego, aby zrozumieć nasz kod na tyle, żeby przed uruchomieniem wiedzieć, że przejdzie testy.

Techniki stanowiące podstawę FP są zaprojektowane z punktu widzenia znacznie większego zaufania do naszych programów po samym ich przeczytaniu. Ktoś, kto rozumie FP i jest dostatecznie zdyscyplinowany, aby skrupulatnie wykorzystywać je w swoich programach, napisze kod, który on i inni będą mogli przeczytać i zweryfikować, czy program będzie robił to, czego od niego oczekują.

Zaufanie jest także zwiększane przez użycie technik, które pozwalają omijać lub minimalizować prawdopodobne źródła błędów. To prawdopodobnie jedna z największych zalet FP: programy napisane funkcyjnie zwykle mają mniej błędów, a istniejące błędy są często w bardziej oczywistych miejscach, więc są łatwiejsze do znalezienia i wyeliminowania. Kod FP jest zazwyczaj bardziej odporny na błędy, ale zdecydowanie nie jest od nich wolny.

W miarę podróży przez tę książkę zaczniemy rozwijać w sobie więcej wiary w pisany kod, gdyż będziemy używać wzorców i praktyk, które będą sprawdzone. I będziemy unikać najpopularniejszych przyczyn błędów w programach.

Komunikacja

Dlaczego programowanie funkcyjne jest ważne? Aby odpowiedzieć na to pytanie, musimy bardziej cofnąć się i porozmawiać o tym, dlaczego programowanie w ogóle jest ważne.

Może to być zaskoczeniem, ale nie uważam, aby przeznaczeniem kodu było przede wszystkim instruowanie komputera. W zasadzie uważam, że fakt instruowania komputera przez kod to tylko szczęśliwy zbieg okoliczności.

Jestem głęboko przekonany, że znacznie ważniejszą rola kodu jest komunikowanie się z innymi ludźmi.

Z doświadczenia wiemy, że duża część czasu spędzona nad „kodowaniem” polega na czytaniu istniejącego kodu. Niewielu z nas jest tak zdolnych, że może spędzać większość czasu, stukając w klawiaturę przy pisaniu nowego kodu bez zajmowania się kodem, który inni (i my sami w przeszłości) napisali.

Szacuje się, że deweloperzy spędzają 70% czasu nad utrzymaniem kodu, czytając go, aby go zrozumieć. To otwiera nam oczy. Siedemdziesiąt procent! Nic dziwnego, że przeciętny programista pisze dziennie około 10 wierszy kodu. Spędzamy 7 godzin dziennie, czytając kod, aby ustalić, gdzie należy wstawić tych 10 wierszy!

Musimy bardziej skupić się na czytelności naszego kodu. A czytelność to nie tylko mniej znaków. Na czytelność największy wpływ ma rozpoznawalność[2].

Jeśli mamy spędzać czas na pisaniu kodu, który będzie bardziej czytelny i zrozumiały, FP ma tu kluczowe znaczenie. Zasady FP są dobrze ustalone, głęboko przeanalizowanie i zweryfikowane, łatwe do zweryfikowania. Poświęcenie czasu na naukę i zastosowanie tych zasad będzie w rezultacie prowadzić do bardziej czytelnego i rozpoznawalnego dla nas i dla innych kodu. Lepsze obycie z kodem i korzyści z jego rozpoznawalności poprawi czytelność kodu.

Na przykład, gdy już dowiemy się co robi map(..), będziemy mogli od razu zauważyć i zrozumieć ją, gdy pojawi się w programie. Ale za każdym razem, gdy zobaczymy pętlę for, musimy przeczytac całą pętlę, aby ją zrozumieć. Składnia pętli for zapewne jest znana, ale istota tego, co robi, nie – trzeba ją za każdym razem przeczytać.

Mając kod rozpoznawalny na pierwszy rzut oka, a tym samym spędzając mniej czasu na ustalaniu, co ten kod robi, możemy skupić naszą uwagę na wyższych poziomach logiki programu. To w końcu ważna sprawa, która wymaga najwięcej naszej uwagi.

FP (przynajmniej bez obciążającej je terminologii) to jedno z najbardziej skutecznych narzędzi do tworzenia czytelnego kodu. Dlatego jest takie ważne.

Czytelność

Czytelność nie jest cechą binarną. Jest to bardzo subiektywny czynnik ludzki opisujący nasz związek z kodem. I będzie się ona zmieniać wraz z naszymi umiejętnościami i zrozumieniem. Ja osobiście odczułem skutki podobne do pokazanego rysunku, a inni, z którymi rozmawiałem, też to potwierdzają.

Możecie doświadczyć podobnych skutków podczas pracy z tą książką. Ale nabierzcie otuchy. Jeśli wytrzymacie, krzywa wróci skierowana do góry!

Imperatywny oznacza kod, który większość z nas pisze w sposób naturalny. Skupia się on na bezpośrednim instruowaniu komputera, jak coś zrobić. Kod deklaratywny – ten, którego uczymy się pisać, a który trzyma się zasad FP – to kod skupiający się bardziej na opisie, jaki ma być wynik.

Wróćmy do dwóch fragmentów kodu pokazanych wcześniej w tym rozdziale.

Pierwszy jest imperatywny, skupiający się na tym, jak wykonać zadania. Jest zawalony instrukcjami if, pętlami for, zmiennymi tymczasowymi, zmianami wartości, wywoływaniem funkcji ze skutkami ubocznymi oraz niejawnym przepływem danych między funkcjami. Z pewnością możemy prześledzić logikę kodu, aby zobaczyć, jak liczby przepływają i ulegają zmianie do stanu końcowego, ale nie jest to jasne i bezpośrednie.

Drugi fragment kodu jest bardziej deklaratywny. Odchodzi od wszystkich wymienionych tu technik imperatywnych. Zauważmy, że nie ma tu jawnych instrukcji warunkowych, pętli, efektów ubocznych, zmiany wartości ani mutacji. Zamiast tego kod stosuje dobrze znane (w każdym razie dla świata FP!) i godne zaufania wzorce jak filtrowanie, redukcja, przenoszenie i złożenie. Uwaga przesuwa się z niskiego poziomu jak na wyższy poziom co będzie wynikiem.

Zamiast babrania się w instrukcjach if w celu sprawdzenia liczby, delegujemy to do dobrze znanego narzędzia FP jak gte(..) (większe lub równe), a następnie skupiamy się na ważniejszym zadaniu powiązania tego filtra z drugim filtrem i funkcją sumowania.

Co więcej, przepływ danych przez drugi program jest jawny:

1. Lista liczb idzie do printMagicNumber(..).

2. Liczby te są przetwarzane po jednej przez sumOnlyFavorites(..), dając w wyniku jedną liczbę (sumę) jedynie z wybranych liczb.

3. Suma zostaje przekształcona na łańcuch komunikatu za pomocą constructMsg(..).

4. Łańcuch komunikatu zostaje wydrukowany na konsoli za pomocą console.log(..).

Może się nadal wydawać, że to podejście jest zawikłane oraz że imperatywny fragment jest łatwiejszy do zrozumienia. Przyzwyczajcie się. Obeznanie ma ogromny wpływ na nasz osąd czytelności. Po przeczytaniu tej książki dzięki praktyce docenicie drugi fragment z podejściem deklaratywnym, a jego znajomość spowoduje poprawę jego czytelności.

Wiem, że prośba, aby to przyjąć, wymaga od was w tym momencie bezwarunkowej wiary.

Poprawienie czytelności i eliminacja wielu pomyłek prowadzących do błędów, jak to sugeruję, wymaga dużo więcej wysiłku oraz czasem więcej kodu. Uczciwie mówiąc, gdy zaczynałem pisać tę książkę, nie potrafiłbym napisać (i w pełni zrozumieć!) drugiego fragmentu kodu. Teraz, gdy jestem dużo dalej w swojej nauce, jest on bardziej naturalny i wygodny.

Jeśli ktoś ma nadzieję, że przejście na FP zadziała jak magiczna różdżka i szybko przekształci kod na przyjemniejszy, elegantszy, mądrzejszy, bardziej odporny i zwięzły – i że to pójdzie łatwo w krótkim terminie – to niestety nie są to realistyczne oczekiwania.

FP różni się bardzo sposobem myślenia o tym, jaka powinna być struktura kodu, aby przepływ danych był bardziej oczywisty i pomagał czytelnikom podążać za naszym myśleniem. To potrwa. Ten wysiłek jest zdecydowanie wart zachodu, ale może to być niełatwa podróż.

Wciąż często potrzebuję kilku prób, aby zrefaktoryzować fragment kodu imperatywnego na bardziej deklaratywny kod FP, zanim uda mi się dojść do czegoś, co jest dostatecznie jasne, abym zrozumiał to później. Doszedłem do wniosku, że przechodzenie na FP to powolny proces iteracyjny, a nie szybkie przełączenie się z jednego paradygmatu na inny.

Stosuję też test „później tego naucz” do każdego kawałka napisanego przez siebie kodu. Po zapisania kawałka kodu zostawiam go na kilka godzin lub dni, a potem próbuję odczytać na świeżo, udając, że chcę kogoś nauczyć lub mu wytłumaczyć. Zwykle na początku jest to pogmatwane i mylące, więc to poprawiam i powtarzam!

Nie próbuję nikogo zdołować. Chciałbym, żeby każdy poczuł bluesa. Cieszę się, że mnie się udało. Mogę wreszcie zobaczyć, jak pokazana krzywa zagina się w górę w kierunku lepszej czytelności. Wysiłek się opłacił. I opłaci się każdemu.

Perspektywa

Większość innych tekstów na temat FP przyjmuje podejście „od ogółu do szczegółu”, ale tu pójdziemy w przeciwnym kierunku: idąc od podstaw w górę, będziemy odkrywać najważniejsze zasady, które zapewne przez formalistów FP zostałyby uznane za rusztowanie dla wszystkich ich działań. Ale większość czasu będziemy trzymać się z dala od najbardziej przerażającej terminologii i notacji matematycznej, które tak łatwo frustrują uczących się.

Uważam, że mniej ważne jest, jak coś nazywamy, a ważniejsze jest zrozumienie, czym to jest i jak to działa. Nie mówię tu, że wspólna terminologia nie ma znaczenia – bez wątpienia ułatwia ona komunikację między dojrzałymi profesjonalistami. Ale dla uczących się może być rozpraszająca.

Dlatego w tej książce spróbuję bardziej skupić się na podstawowych pojęciach, a mniej na wydumanej otoczce. Nie mówię, że nie będę używał terminologii – ona się pojawi. Ale nie będziemy opakowywać tematu w wyrafinowane słowa. Tam, gdzie jest to konieczne, spojrzymy dalej, aby zobaczyć idee.

Nazywam tutaj to mniej formalne podejście „programowaniem lekko funkcyjnym”, gdyż uważam, że formalizm, na który cierpi prawdziwe FP, może sprawić, iż będzie ono przytłaczające dla osób nie przyzwyczajonych do sformalizowanego myślenia. Ja nie zgaduję: to moja własna historia. Nawet po tym, jak uczę FP, i po napisaniu tej książki, mogę nadal stwierdzić, że formalizm terminologii i notacji w FP jest dla mnie bardzo, bardzo trudny do przetworzenia. Próbowałem i próbowałem i nadal nie mogę z tym wiele zrobić.

Znam wielu programistów FP, którzy uważają, że sam formalizm pomaga w nauce. Ja jednak myślę, że jest tam pewna granica, a twierdzenie to staje się prawdą dopiero po osiągnieciu pewnego obycia z formalizmem. Jeśli ktoś ma wykształcenie matematyczne lub choć trochę doświadczenia z dziedziny informatyki, może to być naturalne. Ale do niektórych to nie dociera i choćby bardzo się starali, formalizm będzie ich odstręczał.

Zatem ta książka wprowadza pojęcia, na których, jak uważam, jest zbudowane FP, ale dochodzimy do tego, dając zachętę do wspinania się z dołu na to urwisko, mobilizując do wspinaczki, zamiast protekcjonalnie pokrzykiwać z góry.

Jak znaleźć równowagę

Osoby, które mają długo styczność z FP, zapewne słyszały określenie „YAGNI”: „You Ain’t Gonna Need It” (Nie będziesz tego potrzebować). Zasada ta pochodzi pierwotnie z programowania ekstremalnego (extreme programming) i kładzie nacisk na wysokie ryzyko i koszt tworzenia funkcji, zanim będą one potrzebne.

Czasami wydaje nam się, że będziemy potrzebowali czegoś w przyszłości, więc tworzymy to w przekonaniu, że łatwiej to zrobić przy okazji pracy nad innymi rzeczami, a potem zdajemy sobie sprawę, że nasze przypuszczenie było nieprawdziwe i funkcja nie jest potrzebna lub powinna być całkiem inna. Przy innej okazji przypuszczenie jest prawdziwe, ale funkcja zostaje utworzona zbyt wcześnie i pochłania czas, który powinniśmy poświęcić na funkcje potrzebne teraz. Ponosimy koszty, rozpraszając swoją energię.

YAGNI niesie wyzwanie warte zapamiętania: nawet jeśli wydaje nam się to w danej sytuacji sprzeczne z intuicją, powinniśmy odłożyć tworzenie rzeczy do czasu, kiedy będą naprawdę potrzebne. Mamy tendencję do przesady w naszych ocenach przyszłych kosztów refaktoryzacji związanych z jej późniejszym wykonaniem, gdy będzie naprawdę potrzebna. Prawdopodobne później nie będzie to tak trudne, jak założyliśmy.

W zastosowaniu do programowania funkcyjnego przestrzegam: w tym tekście omawianych jest wiele ciekawych i istotnych wzorców, ale nie warto umieszczać wzorca w swoim kodzie tylko dlatego, że wydaje nam się fascynujący.

Różnię się tu od formalistów FP: to, że do czegoś można zastosować FP nie oznacza, że należy zastosować FP. Co więcej, jest wiele sposobów podzielenia problemu, więc jeśli nawet poznaliśmy bardziej wyrafinowane podejście, które jest w utrzymaniu i możliwości rozszerzania bardziej „odporne na przyszłość”, prostszy wzorzec FP może być w danym punkcie wystarczający.

Ogólnie zalecam szukanie w kodzie równowagi i konserwatywne podejście do stosowania FP, w miarę jak poznajemy jego elementy. Warto przyjąć zasadę YAGNI przy decydowaniu, czy dany wzorzec lub abstrakcja pomogą sprawić, że dany fragment kodu stanie się bardziej czytelny, czy tylko wprowadzą mądre wyrafinowanie, które (jeszcze) nie ma uzasadnienia.

Pamiętajmy, że każde rozszerzenie, które nie zostanie nigdy użyte, to nie tylko marnowanie wysiłku, ale także coś, co może się stać przeszkodą.

Jeremy D. Miller @jeremydmiller 20/2/15
https://twitter.com/jeremydmiller/status/568797862441586688

Pamiętajmy, że każdy wiersz kodu, który piszemy, ma związane z tym koszty czytelnika. Czytelnikiem może być członek innego zespołu lub nawet my sami w przyszłości. Żaden z czytelników nie będzie pod wrażeniem czegoś przemądrzałego, niepotrzebnie wyrafinowanego, co tylko pokazuje naszą biegłość w FP.

Najlepszym kodem jest kod najbardziej czytelny w przyszłości, gdyż celuje dokładnie we właściwe proporcje między tym, co można i co powinno być (idealizm), a tym, co musi być (pragmatyzm).

Zasoby

Przy tworzeniu tego tekstu opierałem się na wielu różnych zasobach. Wierzę, że wy też możecie z nich skorzystać, więc chcę poświęcić na nie trochę miejsca.

Książki

Kilka książek na temat FP/JavaScriptu, które należy z pewnością przeczytać:

- Professor Frisby’s Mostly Adequate Guide to Functional Programming, Brian Lonsdorf (https://drboolean.gitbooks.io/mostly-adequate-guide-old/content/ch1.html)

- JavaScript Allongé, Reg Braithwaite (https://leanpub.com/javascriptallongesix)

- Functional JavaScript, Michael Fogus (http://shop.oreilly.com/product/0636920028857.do)

Blogi i witryny

Oto inni autorzy i treści, które warto sprawdzić:

- Fun Fun Function Videos by Mattias P Johansson (https://www.youtube.com/watch?v=BMUiFMZr7vk)

- Awesome FP JS (https://github.com/stoeffel/awesome-fp-js)

- Kris Jenkins (http://blog.jenkster.com/2015/12/what-is-functional-programming.html)

- Eric Elliott (https://medium.com/@_ericelliott)

- James A Forbes (https://james-forbes.com/#!/)

- James Longster (https://github.com/jlongster)

- André Staltz (https://staltz.com/)

- Functional Programming Jargon (https://github.com/hemanth/functional-programming-jargon#functional-programming-jargon)

- Functional Programming Exercises (https://github.com/InceptionCode/Functional-Programming-Exercises)

Biblioteki

Fragmenty kodu w tej książce w większości nie są oparte na bibliotekach. Sposób implementacji każdego poznawanego działania jest wyprowadzany w starym dobrym JavaScripcie. Jednak w miarę jak zaczynamy tworzyć więcej prawdziwego kodu w FP, będziemy korzystać z bibliotek, aby zapewnić zoptymalizowane i w pełni niezawodne wersje powszechnie akceptowanych narzędzi.

Na marginesie, warto sprawdzić dokumentację dotyczącą funkcji bibliotecznych, aby na pewno wiedzieć, jak one działają. Jest w nich wiele podobieństwa do kodu tworzonego w tej książce, ale bez wątpienia są też różnice, nawet między popularnymi bibliotekami.

Oto kilka popularnych bibliotek FP dla JavaScriptu, które stanowią doskonały punkt wyjścia do naszej analizy:

- Ramda (https://ramdajs.com/)

- lodash/fp (https://github.com/lodash/lodash/wiki/FP-Guide)

- functional.js (https://github.com/functionaljs/functional-js)

- Immutable.js (https://github.com/immutable-js/immutable-js)

Dodatek C zawiera bliższe spojrzenie na te i inne biblioteki.

Podsumowanie

Można mieć wiele powodów, aby zacząć czytać tę książkę i różne oczekiwania wobec niej. W tym rozdziale wyjaśniłem, dlaczego chciałbym, abyście przeczytali tę książkę i co powinniście wynieść z tej podróży. Pomaga też wyjaśnić innym (jak nasi współpracownicy), dlaczego powinni udać się z nami w tę podróż!

Programowanie funkcyjne dotyczy pisania kodu, który opiera się na sprawdzonych zasadach, dzięki czemu możemy zyskać zaufanie i wiarę w kod, który piszemy i czytamy. Nie powinniśmy być usatysfakcjonowani napisaniem kodu, który jak mamy nadzieję, będzie działał, a potem wzdychać z ulgą, gdy okaże się, że przechodzi zestaw testów. Powinniśmy wiedzieć, że będzie działać, zanim go uruchomimy i powinniśmy mieć pełne zaufanie, że przekazaliśmy w naszym kodzie wszystkie idee dla korzyści innych czytelników (w tym dla siebie w przyszłości).

To stanowi sedno lekko funkcyjnego JavaScriptu. Celem jest nauka skutecznego komunikowania się z naszym kodem, ale bez duszenia się na tej drodze pod stosami notacji lub terminologii.

Podróż w nauce programowania funkcyjnego zaczyna się od głębokiego zrozumienia, czym jest funkcja. Tym zajmujemy się w następnym rozdziale.

Rozdział 2. Natura funkcji

Programowanie funkcyjne to nie jest po prostu programowanie ze słowem kluczowym function. Gdyby to było tak proste – mógłbym w tym miejscu zakończyć książkę! Niemniej funkcje w istocie są centralnym elementem FP. A sposób ich używania sprawia, że nasz kod staje się funkcyjny.

Ale czy w ogóle wiecie, co naprawdę znaczy funkcja?

W tym rozdziale położymy fundament pod resztę książki, analizując podstawowe aspekty funkcji. W zasadzie jest to przegląd wszystkiego, co nawet nieprogramista powinien wiedzieć o funkcjach. Ale z pewnością, jeśli chcemy wyciągnąć najwięcej z pojęcia FP, musimy znać funkcje od góry do dołu.

Zbierzmy się w sobie, gdyż w funkcjach jest znacznie więcej, niż nam się wydawało.

Czym jest funkcja?

Pytanie „Czym jest funkcja?” powierzchownie ma dość oczywistą odpowiedź: funkcja to kawałek kodu, który można wykonać jeden lub więcej razy.

Choć definicja ta jest rozsądna, brakuje jej ważnej treści, która jest istotą funkcji w jej zastosowaniu w FP. Zagłębmy się więc w temat, aby w pełni zrozumieć funkcje.

Krótki przegląd matematyczny

Wiem, że obiecałem w miarę możliwości unikać matematyki, ale znieście to przez chwilę, aby zanim przejdziemy dalej, szybko przyjrzeć się kilku podstawowym sprawom związanych z funkcjami i wykresami w algebrze.

Czy pamiętacie ze szkoły naukę o f(x)? A co z równaniem y = f(x)?

Przypuśćmy, że równanie jest zdefiniowane tak: f (x) = 2x^2 + 3. Co to oznacza? Co znaczy narysowanie wykresu tego równania? Oto jego wykres:

f (x) = 2x^2 + 3

Możemy zauważyć, że jeśli dowolną wartość x, powiedzmy 2, wstawimy do równania, to otrzymamy wynik (11). Czym jednak jest 11? Jest to wartość zwrotna funkcji f(x), która zgodnie z wcześniejszym ustaleniem reprezentuje wartość y.

Innymi słowy, możemy wybrać interpretację wartości wejściowych i wyjściowych jako punktu (2,11) na krzywej z wykresu. Dla każdej wartości x, którą wstawimy, możemy otrzymać inną wartość y, która stanowi z nią parę jako współrzędna punktu. Mamy punkty (0,3) lub (-1,5). Gdy złożymy te wszystkie punkty razem, otrzymamy wykres krzywej parabolicznej, pokazanej na rysunku.

A co to wszystko ma wspólnego z FP?

W matematyce funkcja zawsze pobiera dane z wejścia i zawsze daje wyniki na wyjściu. Terminem, który często słyszymy w kręgach FP, jest „morfizm”. Jest to wyszukany sposób na opisanie zbioru wartości, które są odwzorowywane na inny zbiór wartości, jak wejścia funkcji są powiązane z jej wyjściami.

W algebrze te wejścia i wyjścia są często intepretowane jako zestawy współrzędnych do wykreślenia. Jednak w naszych programach możemy zdefiniować funkcje z różnymi rodzajami wejść i wyjść, mimo że będą one rzadko intepretowane wizualnie na wykresie funkcji.

Funkcje a procedury

Dlaczego więc mówimy o matematyce i wykresach? Ponieważ w istocie programowanie funkcyjne dotyczy używania funkcji jak funkcji w tym matematycznym sensie.

Jesteście przyzwyczajeni do myślenia o funkcjach jak o procedurach. Na czym polega różnica? Procedura to arbitralny zbiór funkcjonalności. Może mieć wejścia lub nie. Może mieć wyjście (wartość zwracana przez słowo kluczowe: return) lub nie.

Funkcja pobiera wejścia i zdecydowanie zawsze ma wartość zwrotną return.

Jeśli planujecie wykorzystywać programowanie funkcyjne, powinniście używać jak najwięcej funkcji, unikając, gdzie tylko można procedur. Wszystkie nasze funkcje (function) powinny pobierać wejście i zwracać wyjście (return).

Dlaczego? Odpowiedź na to pytanie ma wiele poziomów znaczenia, które będziemy odkrywać w tej książce.

Wejście funkcji

Do tej pory możemy wyciągnąć wniosek, że funkcje potrzebują wejścia. Ale popatrzmy, jak działa wejście funkcji.

Czasami słyszymy, jak ktoś określa wejście funkcji jako „argumenty”, a czasami jako „parametry”. O co w tym wszystkim chodzi??

Argumenty to wartości przekazywane do funkcji, a parametry to nazwane zmienne wewnątrz funkcji, które otrzymują te przekazane wartości. Na przykład:

function foo(x,y) {

// ..

}

var a = 3;

foo( a, a * 2 );

a i a * 2 (a właściwie wynik działania a * 2, którym jest tu 6) są argumentami wywołania foo(..). Natomiast x i y to parametry, które otrzymują wartości (odpowiednio 3 i 6).

Uwaga

W JavaScripcie nie ma wymagania, aby liczba argumentów była zgodna z liczbą parametrów. Jeśli podamy więcej argumentów, niż zadeklarowaliśmy parametrów, to wartości są przekazywane bez przeszkód. Dostęp do tych wartości może nastąpić na kilka innych sposobów, w tym przez stare dobre obiekty określane jako argumenty (arguments), o których słyszeliśmy wcześniej. Jeśli przekażemy mniej argumentów, niż zadeklarowano parametrów, to każdy dodatkowy parametr jest traktowany jako zmienna „niezdefiniowana”, co oznacza, że jest ona obecna i dostępna w zakresie działania funkcji, ale zaczyna działanie jako pusta, niedefiniowana wartość (undefined).

Wartości domyślne parametrów

Od wersji ES6 parametry mogą przyjmować wartości domyślne. W takim przypadku, gdy argument dla danego parametru nie zostanie przekazany lub zostanie przekazany z wartością undefined, zostanie zastąpiony domyślnym wyrażeniem.

Rozważmy:

function foo(x = 3) {

console.log( x );

}

foo(); // 3

foo( undefined ); // 3

foo( null ); // null

foo( 0 ); // 0

Dobrą praktyką jest zastanowienie się na przypadkami domyślnymi, co może pomóc przy korzystaniu z funkcji. Jednak ustalanie wartości domyślnych dla parametrów prowadzi do większej złożoności w sensie czytelności i zrozumienia różnych przypadków wywoływania funkcji. Należy rozsądnie korzystać tej możliwości.

Zliczanie wejść

Liczba argumentów funkcji oznacza „oczekiwanie”, ile argumentów powinniśmy do niej przekazać. Jest ona określona przez liczbę parametrów, które zostały zadeklarowane:

function foo(x,y,z) {

// ..

}

foo(..) oczekuje trzech argumentów, gdyż ma trzy zadeklarowane parametry. Ten licznik nosi określoną nazwę: argumentowość. Jest to liczba parametrów w deklaracji funkcji. Argumentowość foo(..) wynosi 3.

Ponadto funkcja o argumentowości 1 nazywana jest „jednoargumentową”, funkcja o argumentowości 2 jest „dwuargumentowa”, a funkcja o argumentowości 3 lub więcej – „wieloargumentowa”.

Możemy chcieć sprawdzić odwołanie do funkcji podczas wykonania, aby określić jej argumentowość. Można to zrobić za pomocą własności length w odwołaniu do tej funkcji:

function foo(x,y,z) {

// ..

}

foo.length; // 3

Jednym z powodów określania argumentowości podczas wykonywania jest sprawdzenie, czy kawałek kodu otrzymał odwołanie do funkcji z wielu źródeł i wysłał różne wartości zależnie od argumentowości każdej z nich.

Na przykład wyobraźmy sobie przypadek, gdzie odwołanie do funkcji fn może oczekiwać jednego, dwóch lub trzech argumentów, ale na ostatniej pozycji zawsze chcemy przekazać zmienną x:

// `fn` jest ustawione na odwołanie do jakiejś funkcji

// `x` Ma jakąś wartość

if (fn.length == 1) {

fn( x );

}

else if (fn.length == 2) {

fn( undefined, x );

}

else if (fn.length == 3) {

fn( undefined, undefined, x );

}

Wskazówka

Własność length funkcji jest tylko do odczytu i zostaje określona w momencie definicji funkcji. Należy ją traktować jako istotny element metadanych, opisujący coś związanego z przewidywanym wykorzystaniem funkcji.

Trzeba mieć świadomość, że są konkretne odmiany list parametrów, które sprawiają, że własność length funkcji informuje o czymś innym, niż można by oczekiwać:

function foo(x,y = 2) {

// ..

}

function bar(x,...args) {

// ..

}

function baz( {a,b} ) {

// ..

}

foo.length; // 1

bar.length; // 1

baz.length; // 1

A co ze zliczaniem liczby argumentów otrzymanych w aktualnym wywołaniu funkcji? Tu mamy sytuację, która kiedyś była trywialna, ale teraz będzie nieco bardziej skomplikowana. Każda funkcja ma dostępny obiekt arguments (podobny do tablicy), który przechowuje odwołania do każdego przekazanego argumentu. Możemy więc sprawdzić własność length dla arguments, aby określić, ile zostało w rzeczywistości przekazanych:

function foo(x,y,z) {

console.log( arguments.length );

}

foo( 3, 4 ); // 2

Od wersji ES5 (a konkretnie trybu ścisłego), arguments jest przez niektórych uważane za przestarzałe. Wielu unika stosowania go, o ile jest to możliwe. W JS „nigdy” nie usuwamy kompatybilności wstecznej, niezależnie od tego, w jakim stopniu to może pomóc w przyszłym rozwoju, więc arguments nigdy nie zostanie usunięte. Ale obecnie często zaleca się, aby unikać go, gdy tylko jest to możliwe.

Sugeruję jednak, że arguments.length, i tylko ono, może być stosowane w przypadkach, gdy musimy uważać na liczbę przekazywanych argumentów. W przyszłej wersji JS może ewentualnie zostanie dodana własność, która zaoferuje możliwość określenia liczby przekazanych argumentów, bez konsultacji z arguments.length. Jeśli tak się stanie, to będziemy mogli całkiem porzucić arguments!

Ostrożnie: nigdy nie należy uzyskiwać dostępu do argumentów według pozycji, jak arguments[1]. Trzymajmy się jedynie arguments.length i tylko wtedy, gdy musimy to robić.

Poza tym jak uzyskamy dostęp do argumentu, który został przekazany na pozycji poza liczbą zadeklarowanych parametrów? Odpowiem na to za chwilę: ale najpierw musimy cofnąć się o krok i zapytać się: „Dlaczego mogę chcieć to zrobić?”. Poważnie. Pomyślcie o tym uważnie przez minutę.

To zdarza się dość rzadko. Nie powinno to być coś, czego zwykle oczekujemy lub na czym się opieramy przy pisaniu naszych funkcji. Jeśli okaże się, że mamy taki scenariusz, spędźmy dodatkowych 20 minut, próbując zaprojektować interakcje z funkcją w inny sposób. Nazwijmy ten dodatkowy argument, nawet jeśli jest to sytuacja wyjątkowa.

Sygnaturę funkcji, która akceptuje nieokreśloną liczbę argumentów, określa się jako funkcję ze zmienną liczbą argumentów. Niektórzy wolą ten rodzaj funkcji, ale moim zdaniem większość programistów FP unika tego, jeśli to możliwe.

OK, na razie wystarczy.

Powiedzmy, że chcemy mieć dostęp do argumentów w sposób pozycyjny, podobny do tablicy, zapewne dlatego, że chcemy mieć dostęp do argumentu, który nie ma dla swojej pozycji formalnego parametru. Jak możemy to zrobić?

Na pomoc przychodzi ES6! Zadeklarujmy naszą funkcję za pomocą operatora ... – określanego różnie, jako „rozprowadzający” (spread), „spoczynkowy” (rest) lub (co ja preferuję) „zbierający” (gather):

function foo(x,y,z,...args) {

// ..

}

Czy widzicie na liście parametrów...args? To deklaratywna postać w ES6, która mówi silnikowi, aby odebrał (hmm, „zebrał”) wszystkie pozostałe argumenty (jeśli są), które nie są przypisane do nazwanych parametrów, a następnie umieścił je w jawnej tablicy o nazwie args. args będzie zawsze tablicą, nawet jeśli jest ona pusta. Ale nie będzie zawierać wartości przypisanych do parametrów x, y i z, tylko to, co zostanie przekazane poza tymi trzema pierwszymi wartościami:

function foo(x,y,z,...args) {

console.log( x, y, z, args );

}

foo(); // niezdefiniowane niezdefiniowane niezdefiniowane []

foo( 1, 2, 3 ); // 1 2 3 []

foo( 1, 2, 3, 4 ); // 1 2 3 [ 4 ]

foo( 1, 2, 3, 4, 5 ); // 1 2 3 [ 4, 5 ]

Tak więc, jeśli naprawdę chcemy zaprojektować funkcję, która może przyjmować dowolną liczbę przekazywanych argumentów, używamy na końcu ...args (lub innej wybranej nazwy). Mamy teraz prawdziwą, nieprzestarzałą, przyjemną tablicę, z której mamy dostęp do wartości tych argumentów.

Należy zwrócić uwagę na fakt, że wartość 4 jest na pozycji 0 tablicy args, a nie na pozycji 3. Natomiast wartość length nie będzie zawierać tych trzech wartości 1, 2 i 3. ...args zbiera wszystko inne, nie obejmując x, y i z.

Możemy użyć operatora ... na liście parametrów, nawet gdy nie ma zadeklarowanych innych formalnych parametrów:

function foo(...args) {

// ..

}

Teraz args będzie pełną tablicą argumentów, niezależnie od tego, czym one są, a my możemy użyć args.length do określenia, ile dokładnie argumentów zostało przekazanych. I jeśli mamy na to ochotę, możemy bezpiecznie używać args[1] lub args[317]. Ale proszę, nie przekazujcie 318 argumentów.

Tablice argumentów

Co będzie, jeśli zechcemy przekazać także tablicę wartości argumentów do wywołania funkcji?

function foo(...args) {

console.log( args[3] );

}

var arr = [ 1, 2, 3, 4, 5 ];

foo( ...arr ); // 4

Nasz nowy przyjaciel ... jest w użyciu, ale nie tylko na liście parametrów. Jest też używany na liście argumentów w miejscu wywołania. Ma w tym kontekście odwrotne zachowanie.

Przypisy

1 W oryginale zostało użyte określenie right and left, co stanowi odwołanie do funkcyjnego typu Either (przyp. tłum.)

2 Buse, Raymond P.L., and Westley R. Weimer. “Learning a Metric for Code Readability.” IEEE Transactions on Software Engineering, IEEE Press, July 2010, dl.acm.org/citation.cfm?id=1850615.