Ecco un problema di incollare insieme le monadi. Non in una pila, ma nella forma di dover scartare una Monade per eseguire l'operazione all'interno di un'altra.Qual è un modo pulito per gestire una chiamata di monade in un'altra?
Due domini: Weblog e App. Tuttavia, tieni presente che il dominio App chiamerà in altri nello stesso modo in cui attualmente chiama Weblog. Entrambi hanno i propri stack monad. Entrambi tengono traccia del proprio stato.
newtype WeblogM a = WeblogM (ReaderT Weblog (ErrorT WeblogError IO) a)
deriving (Monad, MonadIO, Reader.MonadReader Weblog, Error.MonadError WeblogError)
newtype AppM a = AppM (ReaderT App (EitherT AppError IO) a)
deriving (Functor, Applicative, Monad
, MonadReader App, MonadError AppError)
Al fine di eseguire un'operazione di WeblogM
all'interno di una funzione AppM
, ho constatato che devo scartare il WeblogM
e Rewrap, usando le funzioni in questo modo:
runWeblogHere :: forall a. Weblog.Weblog -> Weblog.WeblogM a -> AppM a
runWeblogHere weblog action =
runIO (left . WeblogError) (Weblog.runWeblog weblog action)
runIO :: (e -> EitherT AppError IO a) -> IO (Either e a) -> AppM a
runIO handler = AppM . lift . handleT handler . EitherT
Tuttavia, che non lasciare le mie operazioni passthrough effettivi piuttosto semplice:
getPage :: Weblog.PageId -> AppM Weblog.WikiPage
getPage pageid = do
App{weblog} <- ask
runWeblogHere weblog $ Weblog.getWikiPage pageid
Questo mi dà fastidio già perché ho altre librerie monadici che So già che collegherò all'architettura AppM
e sono preoccupato per la scrittura di un metodo runXHere
, che è veramente standard, per ognuno di essi.
Ho un suggerimento per creare una classe MonadWeblog
corrispondere alla WeblogM
, più o meno nello stesso modo in cui MonadReader
corrisponde ReaderT
. Mi piace di più perché posso iniziare ad isolare la colla monade nella mia istanza di MonadWeblog
(o, in realtà, MonadX
).
Questo 'MonadWeblog' ti aiuta comunque? Sembra che tu voglia una versione generica di 'runXHere'; potresti creare una classe che includa come metodi tutti gli assunti necessari per implementare 'runXHere'. Tuttavia, non è così irragionevole dover implementare questo per ogni monade. Ogni monade di solito viene con un eliminatore in normali valori Haskell, che devi scrivere. Non è un crimine per i tuoi eliminatori andare in "AppM". – luqui
Oh penso di vedere dove stavi andando con la classe. Tutte le tue monadi saranno 'ReaderT r (ErrorT e IO)'? Potresti considerare di astrarre ciò come per esempio 'GenApp rea', basando tutte le tue monadi su questo, poi scrivendo i combinatori come' embed :: (r '-> r) -> (e -> e') -> GenApp rea -> GenApp r 'e' a', che rende 'runXHere' banale. – luqui
Sì a entrambe le domande.Ho dimenticato cosa sia esattamente 'WeblogM', ma' runWeblog' restituisce 'IO (O WeblogError a)'. Il contratto per ciascuno dei miei domini è che posso eseguirlo per ottenere 'IO (o errore a)'. E questo potrebbe essere il punto chiave per me per astrarre meglio le cose. –