Alternatywy dla Prelude w Haskellu
Haskell jest pięknym językiem programowania, ale nie jest doskonały. Haskell posiada wiele błędów projektowych. Biblioteka standardowa Haskella też nie jest idealna. Prelude jest zbiorem domyślnie importowanych modułów w Haskellu z biblioteki standardowej. Niestety prelude importuje wiele niebezpiecznych oraz powolnych funkcji. Jednocześnie nie importuje wielu użytecznych funkcji.
Dlatego alternatywy dla prelude próbują to poprawić. Są to między innymi biblioteki:
Jednak którą bibliotekę wybrać?
- Tą, która ma najwięcej gwiazdek na githubie?
- Tą, która ma najlepszą dokumentację?
- Tą, która ma najlepszą stronę?
Nie jest to ważne. I tak we wszystkich trzech konkurencjach wygrywa relude.
Biblioteka relude
Biblioteka relude posiada wiele zalet, są to między innymi:
- Programowanie totalne
- Wydajność
- Wygoda
- Minimalizm
Programowanie totalne
Funkcje powinny być totalne. To znaczy dla każdego zestawu parametrów zwracać jakąś wartość. Jeśli funkcja dla jakichś argumentów nie zwraca wartości, tylko rzuca wyjątek, to jest częściowa, a nie totalna. Wiele funkcji z Prelude rzuca wyjątkami. Biblioteka relude posiada totalne wersje tych funkcji.
Osiąga to za pomocą:
- funkcji zwracających
MaybelubEitherzamiast zgłaszających błędy. - typu
NonEmptyzamiastListtam, gdzie ma to sens. - nie importowania niebezpiecznych funkcji ja
!!x.
Dalej można korzystać z niebezpiecznych funkcji, jeśli zaimportuje się moduł Unsafe:
import qualified Relude.Unsafe as Unsafe
Wydajność
Domyślnie w Haskellu literały tekstowe są typu String, gdzie String to lista znaków:
type String = [Char]
Jest to powolna implementacja napisów i zamiast String należy używać typu Text. Można to zmienić za pomocą przełącznika dla kompilatora ghc-options: -XOverloadedStrings. Można też użyć pragmy (dyrektywy?) kompilatora {-# LANGUAGE OverloadedStrings #-}, ale przełącznik jest lepszym rozwiązaniem, ponieważ działa we wszystkich plikach.
Następnie należy zastąpić wszystkie ewentualne łączenia Stringów za pomocą operatora ++, operatorem <>. W zasadzie to wszelkie łączenia list operatorem ++ można zastąpić operatorem łączenia monoidów <>.
Niestety Text znajduje się w module zewnętrznym text a nie module base będącym biblioteką standardową. Text jest zalecany, ale mimo to w Haskellu wiele funkcji z biblioteki standardowej pobiera lub zwraca typ String.
Biblioteka relude naprawia ten problem i tak:
showjest polimorficzny ze względu na typ zwracany, czyli może wracać zarównoStringjak iText.- funkcja
errorprzyjmujeText.
Dodatkowo mamy miły zestaw funkcji (toText|toLText|toString) do konwersji między typami. Dla typu ByteString jest o wiele prostsza do zapamiętania para funkcji encodeUtf8|decodeUtf8.
Połączenie tego wszystkiego (przełącznik -XOverloadedStrings, funkcja show, funkcja toText lub toString) może sprawić, że czasem Haskell nie będzie w stanie wywnioskować typu. Wszędzie tam, gdzie typ jest obojętny powinniśmy używać typu Text. A wszędzie tam gdzie typ jest trudny do wywnioskowania dla kompilatora Haskella musimy jawnie podać typ:
"Awesome relude!"::Text
Niestety funkcje readMaybe i readEither przyjmują dalej String zamiast Text. Powoduje to, że w niektórych przypadkach dalej lepiej używać Stringa:
readOrError :: Read a => String -> a
readOrError raw = check $ readEither raw where
check (Right result) = result
check (Left message) = error $ message <> " [" <> toText raw <> "]"
Wygoda
Biblioteka relude skraca czas pisania aplikacji, ponieważ wiele importów dzieje się automatycznie. Są to:
basebytestringcontainersdeepseqghc-primhashablemtlstmtexttransformersunordered-containers
Modułów tych nie należy dodawać do zależności samodzielnie, tylko używać ich za pośrednictwem relude. Dzięki temu można usunąć z projektu importy wielu modułów. W moim przypadku było to:
import Control.Applicative
import Control.Monad.Except
import Data.Char
import Data.Map.Strict
import Data.Maybe
import Data.Text
import Numeric.Natural
import Text.Read
Dodatkowo kolejne funkcjonalności można importować za pomocą import Relude.Extra. Mnie przydały się funkcje lookup, next i prev.
Minimalizm
Biblioteka relude stara się być biblioteką minimalistyczną, czyli zależącą od minimalnej ilości modułów zewnętrznych. Jest to trudne, ponieważ Haskell posiada bardzo małą bibliotekę standardową base. O wiele za małą.
Zależności używane przez relude znajdują się na wykresie. Początkowo może się wydawać, że wykres zawiera pół internetu, ale zależności można podzielić na cztery grupy:
textibytestring, czyli obsługa tekstu.unordered-containersicontainers, czyli kontenery (kolekcje). Przy czymcontainerszostało już dodane przeztext.mtlczyli transformatory monad (ContT,ExceptT,ListT,RWST,ReaderT,StateT,WriterT) potrzebne każdemu, kto chce się zmierzyć z zagnieżdżonymi monadami i stanem.stmczyli legendarna Software Transactional Memory to pamięć transakcyjna używana przy współbieżności, która zainspirowała między innymi twórcę języka Clojure.
Czy to dużo? W porównaniu z biblioteką standardową wielu innych języków programowania powiedziałbym, że to bardzo mało.
Dokumentacja
Biblioteka relude posiada bardzo dobrą dokumentację i przewodnik przeprowadzający przez podmianę biblioteki.
Dodatkową zaletą jest linter hlint. Linter hlint jest narzędziem magicznym. Pozwala napisać kod, którego nie jesteśmy w stanie zrozumieć, przynajmniej na początku. Piszemy kod prosty i długi przy użyciu tylko trywialnych funkcji, a hlint podpowiada jak go skrócić i jednocześnie go skomplikować.
Podsumowanie
Czy obietnice składane przez twórców relude są spełnione? Jeszcze nie poznałem wszystkich zalet relude, ale uważam, że tak.
Na githubie znajduje się kod projektów [Helcam] i Helpa po użyciu biblioteki relude.