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
Maybe
lubEither
zamiast zgłaszających błędy. - typu
NonEmpty
zamiastList
tam, 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:
show
jest polimorficzny ze względu na typ zwracany, czyli może wracać zarównoString
jak iText
.- funkcja
error
przyjmujeText
.
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:
base
bytestring
containers
deepseq
ghc-prim
hashable
mtl
stm
text
transformers
unordered-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:
text
ibytestring
, czyli obsługa tekstu.unordered-containers
icontainers
, czyli kontenery (kolekcje). Przy czymcontainers
zostało już dodane przeztext
.mtl
czyli transformatory monad (ContT
,ExceptT
,ListT
,RWST
,ReaderT
,StateT
,WriterT
) potrzebne każdemu, kto chce się zmierzyć z zagnieżdżonymi monadami i stanem.stm
czyli 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
.