09/05/2021
W świecie programowania, zwłaszcza w JavaScript, nieustannie poszukujemy sposobów na pisanie bardziej elastycznego, czytelnego i łatwego w utrzymaniu kodu. Jedną z technik, która doskonale wpisuje się w te założenia, jest currying. Pochodząca z paradygmatu programowania funkcyjnego, ta koncepcja pozwala na transformację funkcji przyjmującej wiele argumentów w sekwencję funkcji, z których każda przyjmuje tylko jeden argument. Brzmi skomplikowanie? W rzeczywistości jest to potężne narzędzie, które może znacznie ułatwić zarządzanie logiką w Twoich aplikacjach.

W tym artykule zagłębimy się w świat curryingu, wyjaśnimy jego podstawowe zasady i pokażemy, jak można go zastosować w praktyce. Na konkretnych przykładach – od prostego dodawania po zaawansowany generator spersonalizowanych e-maili – zobaczysz, jak currying może uczynić Twój kod bardziej modułowym i przystosowanym do zmian. Omówimy również, kiedy warto używać tej techniki, a kiedy lepiej jej unikać.
Co to jest Currying?
Currying to technika transformacji funkcji, która oryginalnie przyjmuje wiele argumentów (np. f(a, b, c)) w serię zagnieżdżonych funkcji, z których każda przyjmuje jeden argument i zwraca kolejną funkcję, aż wszystkie argumenty zostaną dostarczone. Ostatecznie, po dostarczeniu wszystkich argumentów, ostatnia funkcja zwraca wynik. Wyobraź sobie, że masz maszynę, która potrzebuje trzech składników, aby coś wyprodukować. Zamiast wrzucać wszystkie trzy naraz, currying pozwala na dodawanie ich po kolei, a po każdym dodaniu maszyna jest gotowa na kolejny składnik.
Dla przykładu, tradycyjna funkcja dodająca dwie liczby wyglądałaby tak:
function add(a, b) {
return a + b;
}
console.log(add(2, 3)); // Output: 5W wersji curried, ta sama funkcja mogłaby wyglądać następująco:
const curriedAdd = (a) => (b) => a + b;
console.log(curriedAdd(2)(3)); // Output: 5Zauważ, że w przypadku funkcji curried, wywołujemy ją wielokrotnie, przekazując po jednym argumencie w każdym wywołaniu, aż do momentu, gdy wszystkie argumenty zostaną dostarczone. Każde pośrednie wywołanie zwraca nową funkcję, która „zapamiętuje” wcześniej przekazane argumenty (dzięki domknięciom – closures).
Dlaczego warto używać Curryingu?
Currying nie jest tylko ciekawostką programistyczną; oferuje szereg praktycznych korzyści, które mogą znacząco poprawić jakość Twojego kodu:
- Wielokrotne użycie funkcji: Currying umożliwia tworzenie wyspecjalizowanych funkcji z bardziej ogólnych. Możesz „częściowo aplikować” funkcję, czyli dostarczyć część jej argumentów, a resztę pozostawić do późniejszego wypełnienia. To prowadzi do tworzenia elastycznych szablonów i komponentów.
- Zwiększona czytelność i utrzymywalność: Rozbijając funkcje na mniejsze, jednorazowe kroki, kod staje się bardziej modularny i łatwiejszy do zrozumienia. Każdy krok w sekwencji ma jasno określoną odpowiedzialność, co ułatwia debugowanie i wprowadzanie zmian.
- Częściowe aplikowanie funkcji: Jest to jedna z kluczowych zalet. Możesz tworzyć nowe funkcje, które są pochodnymi istniejących, ale z pewnymi parametrami już zdefiniowanymi. To idealne rozwiązanie, gdy masz funkcję, której często używasz z tymi samymi początkowymi argumentami.
- Modułowość kodu: Currying promuje modułowe podejście, gdzie każda funkcja skupia się na jednym konkretnym zadaniu. Dzięki temu kod jest bardziej skalowalny i łatwiejszy do testowania.
Praktyczny przykład: Generator spersonalizowanych e-maili
Aby zilustrować potęgę curryingu, stwórzmy generator spersonalizowanych e-maili dla kampanii marketingowej. Chcemy móc dostosować temat, powitanie i treść wiadomości dla każdego użytkownika. Używając curryingu, możemy budować ten proces krok po kroku, tworząc elastyczne szablony e-maili.
Krok 1: Zdefiniowanie funkcji curried
Zaczynamy od ogólnej funkcji createEmail, która będzie przyjmować temat, powitanie i wiadomość jako oddzielne argumenty, ale w sekwencji curried.
const createEmail = (subject) => (greeting) => (message) => {
return ` ${greeting}, ${message} Pozdrawiam serdecznie, [Twoja Firma] Temat: ${subject}`;
};Jak widać, createEmail najpierw przyjmuje subject, następnie zwraca funkcję, która przyjmuje greeting, która z kolei zwraca funkcję przyjmującą message. Dopiero po dostarczeniu wszystkich trzech argumentów generowany jest pełny tekst e-maila.
Krok 2: Tworzenie funkcji wielokrotnego użytku
Teraz, wykorzystując naszą funkcję createEmail, możemy zdefiniować częściowe funkcje dla konkretnych przypadków użycia. To jest moment, w którym currying naprawdę błyszczy, umożliwiając tworzenie szablonów.
const promotionalEmail = createEmail("Wyjątkowa Oferta Specjalnie dla Ciebie!");
const feedbackRequestEmail = createEmail("Cenimy Twoją Opinię!");W tym kroku stworzyliśmy dwie nowe funkcje: promotionalEmail i feedbackRequestEmail. Obie te funkcje są już „ustawione” na konkretny temat e-maila, ale nadal czekają na powitanie i treść wiadomości.
Krok 3: Personalizacja dla użytkowników
Teraz możemy dostosować powitanie i treść wiadomości dla każdego użytkownika indywidualnie, korzystając z wcześniej zdefiniowanych szablonów.
// E-maile dla konkretnych osób
const johnsEmail = promotionalEmail("Cześć Janie")("Mamy dla Ciebie 20% zniżki na następny zakup. Nie przegap!");
const marysEmail = feedbackRequestEmail("Witaj Mary")("Chcielibyśmy poznać Twoje zdanie na temat naszych usług. Twoja opinia jest dla nas bezcenna!");W tym miejscu w pełni aplikujemy nasze curried funkcje, dostarczając ostatnie argumenty (powitanie i treść). Rezultatem są gotowe, spersonalizowane wiadomości e-mail.
Krok 4: Wysyłanie e-maili (symulacja)
Ostatni krok to integracja z logiką wysyłki e-maili. W naszym przypadku po prostu wyświetlimy wygenerowane wiadomości w konsoli.
console.log(johnsEmail);
/* Output:
Cześć Janie,
Mamy dla Ciebie 20% zniżki na następny zakup. Nie przegap!
Pozdrawiam serdecznie,
[Twoja Firma]
Temat: Wyjątkowa Oferta Specjalnie dla Ciebie! */
console.log(marysEmail);
/* Output:
Witaj Mary,
Chcielibyśmy poznać Twoje zdanie na temat naszych usług. Twoja opinia jest dla nas bezcenna!
Pozdrawiam serdecznie,
[Twoja Firma]
Temat: Cenimy Twoją Opinię! */Jak widać, dzięki curryingowi, nasza funkcja createEmail stała się niezwykle elastyczna. Możemy tworzyć ogólne szablony, a następnie personalizować je w miarę potrzeb, bez powielania kodu.

Currying w nowoczesnym JavaScript
Chociaż currying jest bardziej powszechny w językach programowania funkcyjnego, JavaScript deweloperzy mogą czerpać korzyści z bibliotek takich jak Lodash, które zapewniają wbudowane wsparcie dla curryingu. Użycie biblioteki może uprościć implementację curryingu, zwłaszcza dla funkcji przyjmujących wiele argumentów jednocześnie.
Oto jak można użyć funkcji _.curry z Lodash:
const l = require('lodash');
const createEmailLodash = l.curry((subject, greeting, message) => {
return ` ${greeting}, ${message} Pozdrawiam serdecznie, [Twoja Firma] Temat: ${subject}`;
});
const promotionalEmailLodash = createEmailLodash("Ekskluzywna Oferta Lodash!");
const johnsEmailLodash = promotionalEmailLodash("Witaj John")("Mamy dla Ciebie specjalną zniżkę od Lodash!");
console.log(johnsEmailLodash);Lodash _.curry automatycznie transformuje funkcję przyjmującą wiele argumentów w jej curried wersję, pozwalając na wywoływanie jej z dowolną liczbą argumentów naraz, aż wszystkie zostaną dostarczone. Jest to bardzo wygodne i skraca kod, który musielibyśmy napisać ręcznie.
Currying a dynamiczne wartości i reguły biznesowe
Currying nie tylko ułatwia tworzenie szablonów, ale także pozwala na elastyczne wprowadzanie reguł biznesowych i dynamicznych zmian w zależności od kontekstu. Rozważmy funkcję obliczającą całkowity koszt przedmiotu z uwzględnieniem rabatu i podatku.
Wersja nie-curried:
function totalCost(price, discount, tax) {
return price - (price * discount) + (price * tax);
}
const itemCost = totalCost(400, 0.2, 0.1);
console.log(itemCost); // Output: 360Teraz, jeśli wymagana jest zmiana reguły biznesowej – na przykład, dla cen powyżej 350, powinien być dodany dodatkowy rabat 5% – w funkcji nie-curried musielibyśmy zmodyfikować jej wnętrze, co może być problematyczne, jeśli ta sama funkcja jest używana w wielu miejscach z różnymi regułami.
Wersja curried pozwala na bardziej eleganckie zarządzanie takimi regułami:
function totalCostCurried(price) {
return function(discount) {
// Dynamiczna reguła biznesowa w tym "zakresie"
if (price > 350) {
discount += 0.05; // Dodaj dodatkowe 5% rabatu
}
return function(tax) {
return price - (price * discount) + (price * tax);
}
}
}
const itemCost1 = totalCostCurried(400)(0.2)(0.1);
console.log(itemCost1); // Output: 340 (400 - (400 * 0.25) + (400 * 0.1) = 400 - 100 + 40 = 340)W tym refaktoryzowanym kodzie, dzięki curryingowi, rozbiliśmy funkcję totalCost na serię mniejszych, bardziej wyspecjalizowanych funkcji. Regułę biznesową dotyczącą dodatkowego rabatu umieściliśmy w zakresie funkcji przyjmującej discount. Oznacza to, że logika rabatu jest ściśle związana z momentem jego aplikacji, co czyni kod bardziej spójnym i łatwiejszym do modyfikacji w przyszłości, jeśli zmieni się tylko reguła rabatowa.
Pamiętaj jednak, że currying tak prostych funkcji może czasami prowadzić do nadmiernego inżynierowania. Zawsze oceniaj, czy korzyści z curryingu (modułowość, reusability) przewyższają dodaną złożoność w danym kontekście.
Kiedy stosować Currying? Typowe zastosowania
Currying, choć nie jest rozwiązaniem na każdą bolączkę, doskonale sprawdza się w konkretnych scenariuszach:
- Transformacja danych: Currying może być używany do tworzenia elastycznych potoków przetwarzania danych. Poprzez currying każdego kroku potoku, łatwiej jest komponować nowe funkcje, które mogą transformować dane na różne sposoby.
- Obsługa zdarzeń: Currying może być wykorzystany w obsłudze zdarzeń do tworzenia funkcji, które obsługują konkretne zdarzenia. Poprzez currying funkcji nasłuchującej zdarzenia z typem zdarzenia, łatwiej jest dynamicznie dodawać lub usuwać nasłuchiwacze zdarzeń.
- Funkcje konfigurowalne: Currying może być używany do tworzenia funkcji konfigurowalnych, które mogą być ponownie używane w całej aplikacji. Poprzez currying funkcji z wartościami domyślnymi lub początkowymi, łatwiej jest ponownie używać tej funkcji w wielu przypadkach użycia, jednocześnie pozwalając na dostosowanie z różnymi parametrami.
Oto tabela podsumowująca typowe zastosowania curryingu:
| Zastosowanie | Opis | Przykład (koncepcyjny) |
|---|---|---|
| Transformacja danych | Tworzenie elastycznych potoków przetwarzania danych poprzez komponowanie funkcji. | compose(trim, toLowerCase, removePunctuation) |
| Obsługa zdarzeń | Dynamiczne dodawanie/usuwanie listenerów z predefiniowanymi parametrami. | attachEventHandler(eventType)(handlerFunction) |
| Funkcje konfigurowalne | Tworzenie wyspecjalizowanych wersji ogólnej funkcji, często z częściowo zdefiniowanymi parametrami. | createLogger(logLevel)(message) |
Często Zadawane Pytania (FAQ)
Czy currying jest tym samym co częściowe aplikowanie funkcji?
Nie do końca. Currying jest specyficzną formą częściowego aplikowania funkcji. Częściowe aplikowanie (partial application) to ogólna technika tworzenia nowej funkcji poprzez ustalenie niektórych argumentów istniejącej funkcji. Currying jest przypadkiem częściowego aplikowania, gdzie każda funkcja w sekwencji przyjmuje dokładnie jeden argument. Możesz częściowo aplikować funkcję tak, aby nadal przyjmowała wiele pozostałych argumentów, podczas gdy currying zawsze rozbija to na pojedyncze argumenty.
Kiedy nie używać curryingu?
Currying może być nadmiernym inżynierowaniem dla prostych funkcji, które zawsze przyjmują wszystkie swoje argumenty jednocześnie i nie czerpią korzyści z częściowego aplikowania. Może również wprowadzić minimalny narzut wydajnościowy ze względu na tworzenie wielu zagnieżdżonych funkcji. Jeśli priorytetem jest czysta wydajność, a nie elastyczność czy modułowość, rozważ tradycyjne podejście.
Czy currying poprawia wydajność kodu?
Bezpośrednio currying zazwyczaj nie poprawia wydajności. W rzeczywistości, ze względu na dodatkowe wywołania funkcji i tworzenie domknięć, może wprowadzić niewielki narzut. Jego głównym celem jest poprawa czytelności, elastyczności, modułowości i reusability kodu, a nie optymalizacja szybkości wykonania.
Czy mogę używać curryingu bez bibliotek takich jak Lodash?
Absolutnie! Jak pokazano w przykładach, możesz ręcznie zaimplementować currying za pomocą zagnieżdżonych funkcji strzałkowych lub tradycyjnych funkcji i domknięć. Biblioteki po prostu automatyzują ten proces, co jest wygodne dla bardziej złożonych scenariuszy.
Podsumowanie
Currying to wszechstronna koncepcja programowania funkcyjnego, która może znacznie zwiększyć elastyczność i możliwość ponownego użycia Twojego kodu JavaScript. Poprzez rozbijanie funkcji na mniejsze, zarządzalne fragmenty, możesz tworzyć dynamiczne, łatwe do utrzymania i eleganckie rozwiązania dla złożonych problemów. Przykład generatora e-maili to tylko jeden z wielu sposobów wykorzystania curryingu w rzeczywistych scenariuszach. Niezależnie od tego, czy jesteś doświadczonym programistą, czy dopiero zaczynasz swoją przygodę, nie bój się eksperymentować z curryingiem i kompozycją funkcji w swoim kodzie JavaScript. Włącz go do swoich projektów i odkryj, jak może poprawić Twój przepływ pracy i jakość tworzonego oprogramowania.
Zainteresował Cię artykuł Currying w JavaScript: Przewodnik praktyczny? Zajrzyj też do kategorii Kulinaria, znajdziesz tam więcej podobnych treści!
