Nie tak dawno temu świętowaliśmy wydanie EcmaScript 2022. Nie da się jednak ukryć, że konsorcjum TC39 ma w rękawie jeszcze kilka proposali, które w niedługim czasie mogą rozbić bank. Jednym z nich ma być Pattern Matching, który, obecnie w fazie drugiej, będzie omawiany na zbliżającej się konferencji w siedzibie Google. Trzymamy kciuki za pomyślne rozmowy, a tymczasem przyjrzyjmy się bliżej tej konkretnej propozycji.
Nazwa: ECMAScript Pattern Matching Link do propsala: doc Etap: 2
A poniżej wersja dla zabieganych. 😉
Pattern matching to w bardzo luźnej interpretacji “rozbudowany switch”. Jednak w dobrze nam znanej konstrukcji switcha, rozważamy typy prymitywne jako wyrózniki dla poszczególnych przypadków. Tymczasem, pattern matching, jak sama nazwa wskazuje, pozwala na porównywanie bardziej złożonych wzorców i to na ich podstawie wykonujemy konkretne akcje. Dodatkowo możemy też tymi akcjami manipulować w bardziej zaawansowany sposób, za pomocą słów kluczowych.
Niby fajnie, ale to chyba to samo co if-else?! Jako przykłady użycia, autorzy wyszczególnili np. obsługę akcji reduksowych wraz z payloadami (obecnie zwykle operujemy na typie akcji reduksowej, zakładając, że payload powinien tam być) - dzięki pattern matchingowi, jeśli payloadu by zabrakło, reducer od razu zwróciłby nam default, czyli niezmieniony state. Świetnie też sprawdzi się do obsługi np. biblioteki react-query z poziomu JSX.
Wspomnieliśmy już przypadek reduksa, gdzie switchem rozróżnialiśmy typ i wykonywaliśmy akcję. Najbliższą pattern matchingowi metodę imitowały jednak dotychczas tylko biblioteki. Jedną z nich jest np. ts-pattern, który pozwala na budowanie chainowanych wariantów wzorców i wykonywanie poszczególnych akcji w zależności od pasującego patternu:
match(result).with({ type: "error" }, () => `<p>Oups! An error occured</p>`).with({ type: "ok", data: { type: "text" } },(res) => `<p>${res.data.content}</p>`).with({ type: "ok", data: { type: "img", src: P.select() } },(src) => `<img src=${src} />`).exhaustive();
Jak jednak zaraz zobaczymy, opcje te są stosunkowo ubogie i niewygodne, w stosunku do tego, co oferuje nam omawiany proposal.
Czas do brzegu. Propozycja zakłada zupełnie nową konstrukcję językową, która nieco przypomina switcha, jednak dostarcza kilka dodatkowych, ciekawych rozwiązań. Przykładowa obsługa odpowiedzi z biblioteki fetch będzie wyglądała następująco:
match (res) {when ({ status: 200, headers: { 'Content-Length': s } }):console.log(`size is ${s}`);when ({ status: 404 }):console.log('JSON not found');when ({ status }) if (status >= 400): do {throw new RequestError(res);}};
Jak widać, z tym całym podobieństwem do switcha nie przesadzałem. Wydaje się, że jedyną różnicą tutaj jest zamiana case na when. Im dalej w las, tym jednak ciekawiej. Każdy taki warunek otrzymuje obiekt zamiast wartości prostej. A może to być równie dobrze array, prymityw, regex, etc.! Lista jest długa i wciąż widnieje jako TODO: Rest pattern. Dzięki temu możemy pozwolić sobie na rozważenie różnych statusów w ramach jednego matchowania. Co również rzuca się w oczy, to słowo kluczowe when-if, którym dodatkowo możemy zwarunkować dany pattern.
Ważną właściwością pattern matchingu opisanego w proposalu jest też to, że zachowuje się on jak first-class citizen, a to oznacza, że możemy wynik matchowania zwrócić jako wynik funkcji, przypisać do zmiennej czy po prostu przekazać jako atrybut funkcji.
Oczywiście, każdy matching będzie mógł być zabezpieczony warunkiem default, który zostanie wywołany, gdy wszyskie inne wzorce (sprawdzane z góry na dół) zawiodą.
Gdyby tego było mało, budując nasze warunki będziemy mogli się posiłkować operatorem with, którym sprawdzimy czy wartość, oprócz np. zgodności z regexpem, zawiera jakąś konkretną wartość w pasującym wyrażeniu 🤯.
match ("foobar") {when (/foo(.*)/ with [, suffix]):console.log(suffix);// logs "bar", since the match result// is an array-like containing the whole match// followed by the groups.// note the hole at the start of the array matcher// ignoring the first item,// which is the entire match "foobar".}
W ramach proposala jest również rozważana obsługa asynchronicznych akcji z wykorzystaniem async.
Pattern Matching to chyba obok pipe’a jedna z najbardziej wyczekiwanych nowości w JS z punktu widzenia programowania funkcyjnego. A jak widać powyżej - jego potencjał jest ogromny, także w kontekście wykorzystania na froncie.
Niniejszym chciałbym rozpocząć przegląd ciekawych proposali, które krążą w ekosystemie JS, a które chętnie widziałby każdy entuzjasta FP w głównej specyfikacji języka. Mam nadzieję, że przypadnie Wam do gustu i razem przejdziemy przez różne nowości, które wspomogą nasz funkcyjny kod w JS w (oby) niedalekiej przyszłości. 🚀
