To jest pytanie dotyczące praktyk projektowania interfejsów API do definiowania własnych instancji Monad dla bibliotek Haskell. Zdefiniowanie instancji Monada wydaje się być dobrym sposobem na odizolowanie DSL, np. Par
monada w monad-par, hdph; Process
w procesie rozproszonym; Eval
równolegle itp ...Kiedy (i kiedy nie) zdefiniować Monadę
Podejmuję dwa przykłady bibliotek haskell, których celem jest IO z backendami baz danych. Przykłady, które biorę, to riak dla Riak IO i hedis dla Redis IO.
W hedis, Redis
monada is defined. Stamtąd uruchomić IO z REDiS jak:
data Redis a -- instance Monad Redis
runRedis :: Connection -> Redis a -> IO a
class Monad m => MonadRedis m
class MonadRedis m => RedisCtx m f | m -> f
set :: RedisCtx m f => ByteString -> ByteString -> m (f Status)
example = do
conn <- connect defaultConnectInfo
runRedis conn $ do
set "hello" "world"
world <- get "hello"
liftIO $ print world
W riak, rzeczy są różne:
create :: Client -> Int -> NominalDiffTime -> Int -> IO Pool
ping :: Connection -> IO()
withConnection :: Pool -> (Connection -> IO a) -> IO a
example = do
conn <- connect defaultClient
ping conn
Dokumentacja runRedis
mówi: „Każde wywołanie runRedis wykonuje połączenia sieciowego z połączenia puli i uruchamia daną akcję Redis, dlatego wywołania runRedis mogą zostać zablokowane, gdy wszystkie połączenia z puli są w użyciu. ". Jednak pakiet riak implementuje także pule połączeń. Odbywa się to bez dodatkowych przypadkach monady na górze monady IO:
create :: Client-> Int -> NominalDiffTime -> Int -> IO Pool
withConnection :: Pool -> (Connection -> IO a) -> IO a
exampleWithPool = do
pool <- create defaultClient 1 0.5 1
withConnection pool $ \conn -> ping conn
więc analogia pomiędzy tymi dwoma pakietami sprowadza się do tych dwóch funkcji:
runRedis :: Connection -> Redis a -> IO a
withConnection :: Pool -> (Connection -> IO a) -> IO a
O ile mogę powiedzieć, pakiet hedis wprowadza monadę Redis
do enkapsulacji działań IO z redis za pomocą runRedis
. W przeciwieństwie do tego pakiet riak w withConnection
po prostu przyjmuje funkcję, która pobiera Connection
i wykonuje ją w Monadzie IO.
Jakie są więc motywy do definiowania własnych instancji Monady i stosów Monadów? Dlaczego pakiety riak i redis różniły się podejściem do tego?
Jako kontekst dla odpowiedzi - w przypadku, gdy nie jest to oczywiste, typy "Redis a" i "Connection -> IO a" są w przybliżeniu równoważne. Jest to w gruncie rzeczy różnica kosmetyczna, porównywalna z 'env -> IO a' vs.' ReaderT env IO a'. –
To oznacza, że być może żadna z nich nie jest poprawna, a "Połączenie gęstościowe IO" było monadą, której chciał od samego początku. –