How to return infinite sum with currying?

Nieskończone Currying w JavaScript: Pełny Przewodnik

15/08/2020

Rating: 4.69 (16935 votes)

W świecie programowania JavaScript, gdzie elastyczność i dynamiczność są na porządku dziennym, techniki takie jak currying zyskują na znaczeniu. Currying, będące filarem programowania funkcyjnego, pozwala na częściowe aplikowanie argumentów do funkcji, co prowadzi do tworzenia bardziej modularnego i czytelnego kodu. Jednak co, jeśli chcemy zastosować currying do funkcji, której arność (liczby oczekiwanych argumentów) nie znamy z góry? Tutaj wkracza fascynujące pojęcie nieskończonego currying – techniki, która pozwala na nieograniczone przekazywanie argumentów, aż uznamy, że nadszedł czas na ostateczną ewaluację. Ten artykuł zagłębi się w mechanizmy stojące za nieskończonym curryingiem, jego implementację w JavaScript oraz praktyczne zastosowania, które mogą odmienić Twój sposób myślenia o funkcjach.

How to build an infinite currying function without knowing its arity?
Now we will build an infinite currying function i.e currying a function without knowing its arity. You can make the above curry technique by infinitely returning a function which takes one argument and accumulates the result till you say it's enough. Something like,

Czym Jest Currying i Dlaczego Jest Ważne?

Zanim przejdziemy do nieskończonej wersji, przypomnijmy sobie podstawy. Currying to technika transformacji funkcji, która przyjmuje wiele argumentów jednocześnie, w serię funkcji, z których każda przyjmuje tylko jeden argument. Na przykład, funkcja sum(a, b, c) może zostać skurzona do sum(a)(b)(c). Kluczową zaletą curryingu jest możliwość tworzenia funkcji częściowo zaaplikowanych. Możemy zablokować niektóre argumenty i przekazać nową funkcję, która czeka na pozostałe. To sprawia, że kod jest bardziej elastyczny, łatwiejszy do testowania i sprzyja reużywalności.

Tradycyjne currying wymaga jednak znajomości arności funkcji. Tworzymy łańcuch funkcji, który dokładnie odpowiada liczbie oczekiwanych argumentów. Co jednak, gdy nasza funkcja ma przyjmować dowolną liczbę argumentów, a my chcemy ją ewaluować dopiero wtedy, gdy uznamy, że wszystkie niezbędne dane zostały przekazane? To właśnie problem, który rozwiązuje nieskończone currying.

Wyzwanie: Currying Bez Znajomości Arności

Wyobraź sobie funkcję sumującą, która ma sumować dowolną liczbę liczb. Chcielibyśmy móc ją wywoływać w taki sposób:

const sumator = sum(1)(2)(3); const wynik = +sumator; // Oczekiwany wynik: 6 

Albo nawet:

const czesciowySumator = sum(1, 2, 3)(4); const ostatecznyWynik = +czesciowySumator(5, 6); // Oczekiwany wynik: 21 

Jak osiągnąć taki efekt, skoro nie wiemy, kiedy użytkownik przestanie przekazywać argumenty? Odpowiedź leży w unikalnych właściwościach JavaScriptu, a konkretnie w tym, że funkcje są obiektami i w mechanizmie konwersji typów.

Jak Działa Nieskończone Currying w JavaScript?

Kluczem do implementacji nieskończonego curryingu w JavaScript są dwie główne koncepcje:

  1. Funkcje jako obiekty: W JavaScript funkcje są obiektami pierwszej klasy. Oznacza to, że możemy przypisywać im właściwości, tak jak zwykłym obiektom.
  2. Metoda valueOf(): Każdy obiekt w JavaScript może implementować metodę valueOf(). Kiedy obiekt jest ewaluowany w kontekście, który wymaga wartości prymitywnej (np. liczby, ciągu znaków), JavaScript próbuje wywołać właśnie tę metodę. Jeśli valueOf() zwróci wartość prymitywną, zostanie ona użyta. Jeśli nie, zostanie wywołana metoda toString().

Połączenie tych dwóch cech pozwala nam na stworzenie funkcji, która wciąż zwraca inną funkcję do momentu, aż zostanie „spytana” o swoją wartość numeryczną. Oto jak to działa krok po kroku:

1. Akumulacja Argumentów

Nasza funkcja currying musi w jakiś sposób przechowywać wszystkie argumenty, które zostały do niej przekazane na przestrzeni kolejnych wywołań. Możemy to zrobić, tworząc nową funkcję, która „pamięta” poprzednie argumenty w swoim zamknięciu (closure).

How to build an infinite currying function without knowing its arity?
Now we will build an infinite currying function i.e currying a function without knowing its arity. You can make the above curry technique by infinitely returning a function which takes one argument and accumulates the result till you say it's enough. Something like,

2. Zwracanie Nowej Funkcji z Dodatkowymi Argumentami

Każde wywołanie funkcji currying (dopóki nie nastąpi finalna ewaluacja) powinno zwrócić nową funkcję. Ta nowa funkcja będzie przyjmować kolejne argumenty i dodawać je do puli już zebranych. W tym celu doskonale sprawdza się metoda Function.prototype.bind(). Pozwala ona na utworzenie nowej funkcji, która po wywołaniu ma ustalony kontekst this oraz wstępnie zdefiniowane argumenty. Możemy użyć null jako kontekstu, jeśli nie jest nam potrzebny, a następnie rozprzestrzenić wszystkie zebrane do tej pory argumenty wraz z nowo przekazanymi.

3. Finalna Ewaluacja za Pomocą valueOf()

To jest magiczna część. Do każdej zwracanej funkcji (która jest przecież obiektem) możemy przypisać specjalną właściwość – metodę valueOf(). Ta metoda będzie odpowiedzialna za wykonanie ostatecznej operacji (np. sumowania wszystkich zebranych liczb) i zwrócenie wartości prymitywnej. Kiedy spróbujemy użyć naszej skurzonej funkcji w kontekście numerycznym (np. poprzez operator unarny +, konstruktor Number(), czy w wyrażeniu arytmetycznym), JavaScript automatycznie wywoła metodę valueOf(), aby uzyskać wartość liczbową. W ten sposób możemy „powiedzieć”, kiedy „skończyliśmy” przekazywać argumenty i chcemy otrzymać wynik.

Przykład logiczny (bez pełnego kodu, który ma być implementacją, ale ilustrujący ideę):

function nieskonczonaFunkcjaSumujaca(...args) { // Zbieraj wszystkie argumenty const allArgs = [...args]; // Zdefiniuj funkcję, która będzie zwracana const nextFn = function(...newArgs) { // Twórz nową instancję funkcji, która 'pamięta' wszystkie argumenty // i dodaje nowe, a następnie przypisz jej valueOf return Object.assign( nieskonczonaFunkcjaSumujaca.bind(null, ...allArgs, ...newArgs), { valueOf: () => allArgs.reduce((acc, val) => acc + val, 0) } ); }; // Na pierwszej instancji funkcji też musi być valueOf return Object.assign(nextFn, { valueOf: () => allArgs.reduce((acc, val) => acc + val, 0) }); } 

W powyższym schemacie, Object.assign() jest kluczowy. Pozwala on na skopiowanie właściwości z jednego obiektu do drugiego. W naszym przypadku, kopiujemy metodę valueOf do nowo utworzonej funkcji zwróconej przez bind. Dzięki temu, każda kolejna funkcja w łańcuchu ma dostęp do wszystkich zebranych argumentów i wie, jak obliczyć sumę, gdy zostanie o to poproszona.

Praktyczne Zastosowania i Zalety

Nieskończone currying, choć może wydawać się skomplikowane na pierwszy rzut oka, oferuje szereg zalet i otwiera drzwi do tworzenia bardzo elastycznych i potężnych abstrakcji:

  • Elastyczne API: Pozwala na tworzenie funkcji, które mogą być wywoływane z dowolną liczbą argumentów, w dowolnym momencie. Jest to szczególnie przydatne w bibliotekach narzędziowych, gdzie chcemy zapewnić użytkownikom maksymalną swobodę.
  • Budowanie Konfigurowalnych Funkcji: Można go używać do stopniowego budowania złożonych konfiguracji lub zapytań, gdzie każdy krok dodaje kolejny parametr.
  • Programowanie Funkcyjne: Wpasowuje się w paradygmaty programowania funkcyjnego, promując czyste funkcje i unikanie efektów ubocznych.
  • Czytelność i Ekspresyjność: Dla deweloperów zaznajomionych z tą techniką, kod staje się niezwykle ekspresyjny i elegancki, pozwalając na intuicyjne tworzenie łańcuchów operacji.

Porównanie: Currying Zwykłe vs. Nieskończone

Aby lepiej zrozumieć różnice, spójrzmy na tabelę porównawczą:

CechaCurrying ZwykłeNieskończone Currying
Znajomość ArnościWymaga znajomości liczby argumentówNie wymaga znajomości liczby argumentów
Sposób EwaluacjiJawne wywołanie ostatniej funkcji w łańcuchuAutomatyczna konwersja typu (np. +, Number())
Złożoność ImplementacjiRelatywnie prostaWymaga zrozumienia valueOf(), Object.assign(), bind()
ZastosowanieCzęściowe aplikacje, tworzenie specjalizowanych funkcjiBardzo elastyczne API, stopniowe budowanie operacji, DSL

Potencjalne Pułapki i Najlepsze Praktyki

Mimo swojej elegancji, nieskończone currying nie jest rozwiązaniem uniwersalnym i może prowadzić do pewnych pułapek, jeśli nie jest używane świadomie:

  • Niejasność dla Niedoświadczonych: Dla programistów niezaznajomionych z tą techniką, funkcja, która zwraca inną funkcję, a następnie „magicznie” staje się liczbą, może być myląca. Zawsze rozważaj czytelność kodu dla całego zespołu.
  • Separacja Zadań: Jak wspomniano w źródłach, funkcja sumująca powinna jedynie sumować. Funkcja currying powinna zajmować się tylko curryingiem. Mieszanie tych ról może prowadzić do niejasności. Lepiej jest mieć ogólną funkcję curry, która może opakować dowolną inną funkcję.
  • Wydajność: Ciągłe tworzenie nowych instancji funkcji może mieć marginalny wpływ na wydajność w bardzo intensywnych obliczeniowo scenariuszach, choć w większości przypadków jest to pomijalne.
  • Alternatywy: W wielu przypadkach prostsze podejścia, takie jak funkcja przyjmująca tablicę argumentów (...args) lub tradycyjne currying, mogą być bardziej odpowiednie i łatwiejsze do zrozumienia.

Często Zadawane Pytania (FAQ)

Czy nieskończone currying jest zawsze najlepszym rozwiązaniem?

Nie, nie jest. Jest to potężna, ale specyficzna technika. Najlepiej sprawdza się w scenariuszach, gdzie potrzebujesz niezwykle elastycznego API, które pozwala użytkownikowi na swobodne grupowanie argumentów i ewaluację w dogodnym dla niego momencie. W prostszych przypadkach tradycyjne currying lub funkcje variadic (z argumentami rest ...args) są często bardziej czytelne i wystarczające.

What is infinite currying in JavaScript?
Cool right! Now I’ll introduce the concept of infinite currying. In Javascript, every object can implement this method valueOf that when the object is evaluated, the result would be the result of the method call. Also in Javascript, everything is an object (besides primitives) — including functions!

Jakie są główne różnice w implementacji w porównaniu do zwykłego curryingu?

Główną różnicą jest to, że w nieskończonym curryingu nie ma z góry określonej liczby argumentów, po której funkcja „zakończy” swoje działanie. Zamiast tego, polega na mechanizmie konwersji typów JavaScript (metoda valueOf()) do wyzwolenia końcowej ewaluacji. Zwykłe currying zazwyczaj sprawdza, czy wszystkie wymagane argumenty zostały dostarczone, i dopiero wtedy zwraca wynik.

Czy mogę używać tej techniki w innych językach programowania?

Koncepcja curryingu jest uniwersalna i występuje w wielu językach wspierających programowanie funkcyjne. Jednak konkretna implementacja nieskończonego curryingu, opierająca się na dynamicznym typowaniu i metodzie valueOf(), jest specyficzna dla JavaScriptu i jego unikalnych cech obiektowych. W innych językach mogłyby istnieć podobne wzorce, ale wymagałyby innej implementacji.

Czy nieskończone currying wpływa na wydajność aplikacji?

W większości typowych zastosowań wpływ na wydajność jest pomijalny. Tworzenie wielu małych funkcji i użycie bind() oraz Object.assign() wiąże się z pewnym narzutem, ale nowoczesne silniki JavaScript są w tym bardzo zoptymalizowane. Jeśli jednak tworzysz system o ekstremalnie wysokich wymaganiach wydajnościowych, gdzie liczy się każda nanosekunda, warto przeprowadzić testy porównawcze i rozważyć bardziej bezpośrednie podejścia.

Podsumowanie

Nieskończone currying to potężna i elegancka technika w JavaScript, która wykorzystuje jego elastyczność i mechanizmy konwersji typów. Pozwala na tworzenie niezwykle elastycznych funkcji, które adaptują się do liczby i sposobu przekazywania argumentów. Chociaż wymaga głębszego zrozumienia mechanizmów języka, takich jak funkcje jako obiekty, valueOf i Object.assign, jego opanowanie otwiera nowe możliwości w projektowaniu API i pisaniu bardziej wyrafinowanego kodu funkcyjnego. Pamiętaj jednak, aby używać go rozważnie, zawsze stawiając czytelność i zrozumiałość kodu na pierwszym miejscu. Właściwie zastosowane, nieskończone currying może być cennym narzędziem w arsenale każdego zaawansowanego dewelopera JavaScript.

Zainteresował Cię artykuł Nieskończone Currying w JavaScript: Pełny Przewodnik? Zajrzyj też do kategorii Kulinaria, znajdziesz tam więcej podobnych treści!

Go up