08/02/2020
W świecie programowania, zwłaszcza w dynamicznym środowisku JavaScript, poszukiwanie czystego, modułowego i łatwego do utrzymania kodu jest priorytetem. Jedną z technik, która znacząco przyczynia się do osiągnięcia tych celów, jest tak zwane currying. Pochodząca z programowania funkcyjnego koncepcja curryingu może na początku wydawać się nieco abstrakcyjna, ale jej zrozumienie otwiera drzwi do pisania bardziej eleganckiego i wydajnego kodu. W tym artykule zagłębimy się w to, czym jest currying, dlaczego warto go używać i jak zastosować go w swoich projektach JavaScript.

Co to jest Currying w JavaScript?
Currying to technika transformacji funkcji, która przyjmuje wiele argumentów, w sekwencję funkcji, z których każda przyjmuje tylko jeden argument. Wyobraź sobie funkcję, która normalnie wymaga podania trzech wartości naraz, np. dodaj(a, b, c). Dzięki curryingowi, zamiast wywoływać ją w ten sposób, tworzysz serię zagnieżdżonych funkcji: dodaj(a)(b)(c). Każde wywołanie funkcji z jednym argumentem zwraca nową funkcję, która czeka na kolejny argument, aż do momentu, gdy wszystkie argumenty zostaną podane i funkcja wykona swoje finalne działanie.
Głównym celem curryingu jest zwiększenie elastyczności i możliwości ponownego użycia funkcji. Pozwala on na częściowe zastosowanie funkcji (partial application), czyli tworzenie nowych, bardziej wyspecjalizowanych funkcji z istniejących, poprzez „ustawianie” niektórych argumentów z góry. Jest to szczególnie przydatne w obsłudze zdarzeń, gdzie często musimy przekazać dodatkowe dane do funkcji obsługującej zdarzenie, lub w sytuacjach, gdy chcemy uniknąć wielokrotnego przekazywania tej samej zmiennej jako argumentu funkcji.
Currying jest ściśle związany z koncepcją funkcyjna kompozycja, ponieważ umożliwia tworzenie łańcuchów funkcji, gdzie wynik jednej funkcji staje się wejściem dla następnej. To sprawia, że kod jest bardziej deklaratywny i łatwiejszy do odczytania, a także sprzyja tworzeniu małych, wyspecjalizowanych funkcji, które można łączyć w bardziej złożone operacje.
Jak osiągnąć Currying w JavaScript?
W JavaScript istnieją dwie główne metody, aby zaimplementować currying. Obie wykorzystują podstawowe mechanizmy języka do osiągnięcia tego potężnego wzorca.
1. Currying za pomocą domknięć (Closures)
Domknięcia (closures) to fundamentalna koncepcja w JavaScript, która polega na tym, że wewnętrzna funkcja ma dostęp do zmiennych funkcji zewnętrznej, nawet po tym, jak zewnętrzna funkcja zakończyła swoje wykonanie. To właśnie ta właściwość sprawia, że domknięcia są idealnym narzędziem do implementacji curryingu.
Aby osiągnąć currying za pomocą domknięć, tworzymy sekwencję zagnieżdżonych funkcji, z których każda przyjmuje jeden argument i zwraca kolejną funkcję, aż do momentu, gdy wszystkie argumenty zostaną zebrane i nastąpi ostateczne obliczenie.
Składnia i przykład:
Poniżej przedstawiono ogólną strukturę funkcji curryngowej za pomocą domknięć:
function funkcjaZewnetrzna(a) { return function (b) { // dodaj więcej funkcji // LUB return a * b; }; } // Wywołanie: const wynik = funkcjaZewnetrzna(a)(b);W powyższym przykładzie funkcjaZewnetrzna() przyjmuje pojedynczy parametr i zwraca funkcję wewnętrzną. Ta wewnętrzna funkcja również przyjmuje jeden parametr i w swoim ciele może wykorzystywać parametry zarówno funkcji zewnętrznej, jak i wewnętrznej.
Poniższy przykład ilustruje to na funkcji mnożącej trzy liczby:
<html> <head> <title>JavaScript currying za pomocą domknięć</title> </head> <body> <div id = "output"></div> <script> // Osiągnięcie curryingu function mul(a) { return function (b) { return function (c) { return a * b * c; }; }; } // Wywołanie funkcji curryingowej let result = mul(2)(3)(4); document.getElementById("output").innerHTML = "Wynik to: " + result; </script> </body> </html>Po uruchomieniu tego kodu, element o id „output” wyświetli komunikat „Wynik to: 24”. Zwróć uwagę na to, jak funkcja mul(a) zwraca kolejną funkcję, która z kolei zwraca jeszcze jedną funkcję. Dopiero ta ostatnia funkcja, po otrzymaniu wszystkich trzech argumentów (2, 3, 4), wykonuje ostateczne mnożenie. To podejście sprawia, że kod jest bardziej modułowy i sprzyja ponowne użycie kodu poprzez tworzenie wyspecjalizowanych wersji funkcji.
2. Currying za pomocą metody bind()
Metoda bind() w JavaScript jest używana do tworzenia nowej funkcji, która po wywołaniu ma ustawioną wartość this na podaną wartość, a także predefiniowane argumenty. bind() jest często wykorzystywany do częściowego stosowania argumentów do istniejących funkcji, co efektywnie pozwala na implementację curryingu.
Składnia i przykład:
Poniżej ogólna składnia użycia bind() do curryingu:
let pomnozPrzezDwa = pomnoz.bind(null, 2); let pomnozPrzezDwaITrzy = pomnozPrzezDwa.bind(null, 3); pomnozPrzezDwaITrzy(4); // Wynik: 24W powyższym przykładzie pomnoz to funkcja przyjmująca wiele argumentów. Metodą bind() predefiniujemy argumenty jeden po drugim, osiągając efekt curryingu. Pierwszy argument null w bind() określa wartość this dla nowej funkcji; w kontekście curryingu często nie jest to kluczowe i można użyć null lub undefined.
Rozważmy ponownie przykład mnożenia trzech liczb, tym razem z wykorzystaniem bind():
<html> <head> <title>JavaScript currying za pomocą metody bind()</title> </head> <body> <div id = "output"></div> <script> // Oryginalna funkcja mnożąca trzy argumenty function multiply(x, y, z) { return x * y * z; } // Użycie metody bind() do osiągnięcia curryingu poprzez częściowe zastosowanie pierwszego argumentu (2) let multiplyByTwo = multiply.bind(null, 2); // Dalsze currying poprzez częściowe zastosowanie drugiego argumentu (3). let multiplyByTwoAndThree = multiplyByTwo.bind(null, 3); // Ostateczne wywołanie funkcji curryingowej z trzecim argumentem (10) i wyświetlenie wyniku document.getElementById("output").innerHTML = "Wynik to: " + multiplyByTwoAndThree(10); </script> </body> </html>Ten kod wyświetli „Wynik to: 60”. W tym przypadku, multiplyByTwo jest nową funkcją, która już ma zdefiniowany pierwszy argument (2). Następnie, multiplyByTwoAndThree jest funkcją, która ma zdefiniowane dwa pierwsze argumenty (2 i 3). Kiedy wywołujemy ją z ostatnim argumentem (10), funkcja multiply jest ostatecznie wywoływana z wszystkimi trzema argumentami (2, 3, 10).
Currying vs. Partial Application: Czy to to samo?
Choć terminy currying i partial application (częściowe zastosowanie) są często używane zamiennie, istnieje między nimi subtelna, ale ważna różnica. Currying to proces transformacji funkcji, tak aby przyjmowała ona argumenty jeden po drugim, zwracając nową funkcję, aż wszystkie argumenty zostaną podane. Oznacza to, że funkcja curryingowa zawsze zwraca funkcję, która przyjmuje tylko jeden argument.
Z drugiej strony, częściowe zastosowanie to proces tworzenia nowej funkcji poprzez predefiniowanie niektórych argumentów istniejącej funkcji, ale niekoniecznie przekształcając ją w serię funkcji przyjmujących pojedyncze argumenty. Nowa funkcja wynikowa może nadal przyjmować wiele argumentów. Metoda bind() jest doskonałym przykładem częściowego zastosowania.
Można powiedzieć, że currying jest specyficzną formą częściowego zastosowania, gdzie każdy etap częściowego zastosowania redukuje liczbę wymaganych argumentów o dokładnie jeden. Częściowe zastosowanie jest bardziej ogólnym pojęciem.
Przypadki użycia Currying w praktyce
Technika curryingu znajduje szerokie zastosowanie w rzeczywistych projektach oprogramowania, ponieważ pozwala na pisanie bardziej modułowego i elastycznego kodu. Oto kilka scenariuszy, w których currying jest szczególnie użyteczny:
Obsługa asynchronicznych operacji i obietnic (Promises)
W aplikacjach, które intensywnie korzystają z asynchroniczności, np. w komunikacji z API, currying może pomóc w tworzeniu bardziej czytelnych i łatwiejszych do zarządzania łańcuchów obietnic. Możesz predefiniować część parametrów żądania API, a następnie użyć tak przygotowanej funkcji w różnych miejscach, przekazując tylko zmienne dane.
Obsługa zdarzeń i kontekst
W aplikacjach front-endowych, gdzie często mamy do czynienia z obsługą zdarzeń (np. kliknięć, zmian w formularzach), currying pozwala na łatwe przekazywanie dodatkowych danych do funkcji obsługującej zdarzenie, bez konieczności tworzenia anonimowych funkcji. Na przykład, jeśli masz wiele przycisków, które wykonują podobną akcję, ale z różnymi identyfikatorami, możesz użyć funkcji curryingowej, aby przypisać jej unikalny ID, zanim zostanie wywołana przez zdarzenie.
function handleClick(id) { return function(event) { console.log(`Przycisk o ID: ${id} został kliknięty. Zdarzenie:`, event); }; } // Użycie document.getElementById('button1').addEventListener('click', handleClick('przycisk_glowny')); document.getElementById('button2').addEventListener('click', handleClick('przycisk_pomocniczy'));Tworzenie konfigurowalnych funkcji middleware
W architekturach opartych na middleware (np. w Express.js czy Redux), currying jest niezwykle przydatny do tworzenia funkcji, które mogą być konfigurowane z różnymi opcjami. Funkcja middleware może przyjmować obiekt konfiguracyjny jako pierwszy argument, a następnie zwracać właściwą funkcję middleware, która będzie używana w łańcuchu. Pozwala to na dużą elastyczność i ponowne wykorzystanie logiki.
Walidacja danych
Możesz tworzyć ogólne funkcje walidacyjne, a następnie curryingować je, aby tworzyć bardziej specyficzne walidatory. Na przykład, ogólna funkcja
jestWiekszeNiz(limit)może być curryingowana dojestWiekszeNiz10 = jestWiekszeNiz(10), co jest bardzo czytelne i elastyczne.
Zalety i wady Curryingu
Jak każda technika programistyczna, currying ma swoje mocne i słabe strony, które warto rozważyć przed jego zastosowaniem w projekcie.
Zalety:
- Modułowość i ponowne użycie kodu: Pozwala na tworzenie małych, wyspecjalizowanych funkcji, które można łatwo łączyć w celu budowania bardziej złożonych operacji. To sprzyja zasadzie DRY (Don't Repeat Yourself).
- Łatwiejsze testowanie: Mniejsze, jednoargumentowe funkcje są znacznie łatwiejsze do testowania w izolacji, co poprawia jakość kodu i skraca czas debugowania.
- Lepsza czytelność i deklaratywność: Kod napisany z użyciem curryingu często jest bardziej deklaratywny, co oznacza, że opisuje, co ma być zrobione, a nie jak. Może to prowadzić do bardziej intuicyjnych i czytelnych łańcuchów wywołań.
- Ułatwiona kompozycja funkcji: Currying jest naturalnym partnerem dla kompozycji funkcji, ponieważ każda curryingowana funkcja zwraca inną funkcję, co idealnie pasuje do koncepcji łączenia funkcji ze sobą.
- Częściowe zastosowanie: Umożliwia tworzenie nowych, wyspecjalizowanych funkcji z istniejących, poprzez predefiniowanie niektórych argumentów.
Wady:
- Zwiększona złożoność dla początkujących: Koncepcja zagnieżdżonych funkcji i domknięć może być początkowo trudna do zrozumienia dla osób niezaznajomionych z programowaniem funkcyjnym.
- Niewielkie narzuty wydajnościowe: Tworzenie wielu zagnieżdżonych funkcji może wiązać się z minimalnym narzutem wydajnościowym, choć w większości nowoczesnych zastosowań JavaScript jest to pomijalne i nie stanowi problemu.
- Może prowadzić do nadmiernej abstrakcji: Nie zawsze każda funkcja musi być curryingowana. Nadmierne stosowanie tej techniki może czasem prowadzić do kodu trudniejszego do śledzenia, jeśli nie jest używane z umiarem i w odpowiednich kontekstach.
Często Zadawane Pytania (FAQ)
Poniżej przedstawiamy odpowiedzi na niektóre z najczęściej zadawanych pytań dotyczących curryingu w JavaScript.
P: Czy currying jest zawsze lepszy niż tradycyjne funkcje?
O: Nie zawsze. Currying jest potężnym narzędziem w programowaniu funkcyjnym, ale jego zastosowanie zależy od konkretnego przypadku użycia. Jest szczególnie korzystny, gdy chcesz tworzyć funkcje o wysokiej możliwości ponownego użycia, łatwo konfigurowalne lub gdy często wykonujesz częściowe zastosowania argumentów. Dla prostych, jednorazowych operacji, tradycyjne funkcje mogą być bardziej czytelne i mniej skomplikowane.
P: Kiedy powinienem używać curryingu?
O: Rozważ użycie curryingu, gdy:
- Masz funkcję, która przyjmuje wiele argumentów i chcesz tworzyć jej wyspecjalizowane wersje, predefiniując niektóre z nich.
- Potrzebujesz tworzyć funkcje, które są łatwe do kompozycji z innymi funkcjami.
- Pracujesz w środowisku, gdzie programowanie funkcyjne jest preferowanym paradygmatem (np. React z Redux, biblioteki funkcyjne).
- Chcesz uniknąć wielokrotnego przekazywania tych samych argumentów do wielu funkcji.
P: Czy currying spowalnia kod w JavaScript?
O: Teoretycznie, tworzenie dodatkowych zagnieżdżonych funkcji wiąże się z niewielkim narzutem wydajnościowym. Jednak w praktyce, dla większości aplikacji JavaScript, ten narzut jest znikomy i nie ma zauważalnego wpływu na wydajność. Nowoczesne silniki JavaScript (takie jak V8 w Chrome) są bardzo zoptymalizowane pod kątem obsługi domknięć i funkcji wyższego rzędu.
P: Jaka jest główna różnica między curryingiem a częściowym zastosowaniem?
O: Główna różnica polega na liczbie argumentów przyjmowanych przez funkcje pośrednie. Currying zawsze transformuje funkcję tak, aby każda z pośrednich funkcji przyjmowała dokładnie jeden argument. Częściowe zastosowanie (jak to, które osiąga bind()) tworzy nową funkcję, która ma już predefiniowane niektóre argumenty, ale może nadal przyjmować wiele argumentów w pozostałych pozycjach. Currying jest więc specyficzną formą częściowego zastosowania.
Podsumowanie
Currying to potężna technika programowania funkcyjnego w JavaScript, która pozwala na transformację funkcji przyjmujących wiele argumentów w serię funkcji, z których każda przyjmuje pojedynczy argument. Dzięki temu kod staje się bardziej modularność, elastyczny i łatwiejszy w utrzymaniu. Niezależnie od tego, czy używasz domknięć, czy metody bind(), zrozumienie i zastosowanie curryingu może znacząco poprawić jakość i czytelność Twojego kodu JavaScript. Pamiętaj jednak, aby używać go z umiarem i w sytuacjach, gdzie jego zalety przeważają nad potencjalną początkową złożonością. Opanowanie tej koncepcji to kolejny krok w kierunku pisania bardziej zaawansowanego i eleganckiego kodu w JavaScript.
Zainteresował Cię artykuł Currying w JavaScript: Klucz do Czystego Kodu? Zajrzyj też do kategorii Kulinaria, znajdziesz tam więcej podobnych treści!
