Programowanie funkcyjne jest drugim, obok obiektowego, z najważniejszych paradygmatów języka JavaScript. W podejściu obiektowym zrozumienie kodu uzyskuje się m.in. dzięki hermetyzacji zmieniających elementów. Programowanie funkcyjne ułatwia zrozumienie kodu dzięki minimalizowaniu liczby takich elementów. Dzięki podziałowi zadań na proste funkcje oraz mniejszej złożoności, wpływa na poprawę jakości kodu, pozwala uzyskać rozszerzalną i przejrzystą strukturę aplikacji oraz umożliwia wielokrotne wykorzystywanie kodu.

Programowanie Funkcyjne JavaScript

Zacznij myśleć funkcyjnie

Przede wszystkim celem programowania funkcyjnego nie jest podział programu na funkcje. Celem jest abstrakcyjne ujęcie operacji na danych za pomocą funkcji, tak aby uniknąć efektów ubocznych i ograniczyć modyfikacje stanu w aplikacji. Programowanie funkcyjne nie jest nowym terminem. Jako paradygmat istnieje już od dawna, jest powszechnie wykorzystywane, a na jego popularność w ostatnim czasie w znacznym stopniu wpłynęła biblioteka React.

Programowanie funkcyjne na początku wymaga przyswojenia kilku terminów i zrozumienia pewnych koncepcji, jednak niewątpliwie warto poświęcić czas na naukę tego paradygmatu. Według wielu najlepsze rezultaty można uzyskać dzięki połączeniu paradygmatów – obiektowego i funkcyjnego. Moim zdaniem to prawda. Szalenie ważne jest jednak zachowanie równowagi – nadużycie programowania funkcyjnego bardzo szybko może sprawić, że nasz kod będzie mniej czytelny.

Funkcje Pierwszego Rzędu

Jedną z cech języka JavaScript jest to, że prawie wszystko w tym skryptowym języku programowania jest traktowane jako obiekt, nawet funkcje. Dzięki temu możemy używać ich jak zwykłych zmiennych i np. przekazywać jako parametry innych funkcji. W ten sposób możemy tworzyć funkcje wyższego rzędu (które jako parametr przyjmują inne funkcje). W programowaniu funkcyjnym, funkcje wyższego rzędu pomagają eliminować imperatywne struktury (jak np. pętle) i powtarzalne fragmenty kodu. Dobrym przykładem takich funkcji są .map(), .reduce() czy .forEach().

Programowanie funkcyjne jest deklaratywne

W przypadku programowania deklaratywnego opisujemy co chcemy osiągnąć (jakie warunki musi spełniać rozwiązanie końcowe). Uzyskuje się to m.in. poprzez unikanie imperatywnych struktur sterujących (pętli), które są trudne do ponownego wykorzystania i scalania z innymi operacjami. Co ważne, podejście deklaratywne wpływa na minimalizację skutków ubocznych, co z kolei przekłada się na mniejszą ilość błędów.

Przykład kodu imperatywnego:

var array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

for (let i = 0; i < array.length; i++) {
  array[i] = Math.pow(array[i], 2);
}

Dla porównania, ten sam kod w wersji deklaratywnej:

const numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

const squareNumber = num => Math.pow(num, 2);

numbers.map(squareNumber);

To oczywiście bardzo prosty przykład, ale na pierwszy rzut oka widać, że kod się uprościł.

Funkcje powinny być czyste

Czyste funkcje to funkcje, które nie wykonują zmian poza swoim zasięgiem. Ich wynik zależy od danych wejściowych, a nie zewnętrznego stanu. Funkcja taka zwraca ten sam wynik dla tych samych danych wejściowych. O takich funkcjach mówimy, że są przejrzyste referencyjnie.

Przykład funkcji, która nie jest przejrzysta referencyjnie:

let counter = 0;

function increment() {
  ++counter;
}

Funkcja przejrzysta referencyjnie:

function increment(counter) {
  return counter++;
}

Ponownie, jest bardzo prosty przykład, jednak chodzi o zobrazowanie idei i przyswojenie pewnych terminów. Czyste funkcje zwiększają czytelność kodu i zmniejszają ryzyko pojawienia się błędów.

Niezmienność danych

Niemodyfikowalne dane to takie, które nie mogą być zmieniane po ich utworzeniu. Dzięki takiemu podejściu zmniejszamy ryzyko pojawiania się błędów oraz niepożądanych skutków ubocznych.

Przykład mutacji danych:

const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];

const sortDesc = arr => {
  return arr.sort((a, b) => b - a);
};

sortDesc(arr); //-> [9,8,7,6,5,4,3,2,1]
arr; //-> [9,8,7,6,5,4,3,2,1]

Sortowanie bez mutacji danych:

const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];

const sortDesc = arr => {
  return [...arr].sort((a, b) => b - a);
};

sortDesc(arr); //-> [9, 8, 7, 6, 5, 4, 3, 2, 1]
arr; //-> [1, 2, 3, 4, 5, 6, 7, 8, 9]

Powyższy kod realizuje operację sortowania tablicy. Zauważ, że jego pierwsza wersja modyfikuje tablicę arr. W drugim przypadku użyłem operatora rozproszenia do utworzenia nowej tablicy, na której wykonuję operację – dzięki temu bazowa tablica pozostaje niezmieniona.

Podsumowanie – Programowanie Funkcyjne w JavaScript

Początkowo nauka programowania funkcyjnego może wydawać się trudna. Jednak kiedy nowe podejście wejdzie w krew, zaczyna się doceniać korzyści płynące z tego paradygmatu. Naturalnym staje się korzystanie z metod, takich jak .map() czy .filter(). Programowanie funkcyjnie nie tylko sprawia, że nasz kod staje się lepszy, ale przede wszystkim daje dużo satysfakcji.

Zobacz także

Przyłącz się do konwersacji

2 komentarze

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Wymagane pola są oznaczone *