Sono un utente di trasformatore monade da molto tempo, scrittore di trasformatori monade prima volta ... E mi sento come se avessi fatto qualcosa di non necessario.Scrivere un Monad Transformer, ha davvero bisogno di così tante istanze hardcoded
Stiamo lavorando su un progetto che ha più tabelle DB, e l'hardcoding del set in diversi stack monad stava diventando poco maneggevole, così abbiamo deciso di suddividerlo in diversi trasformatori monad innestabili, permettendoci di scegliere il tipo di funzione livello, come questo
doSomething::(HasUserTable m, HasProductTable m)=>Int->m String
(HasXTable è la classe, xTablet è il trasformatore monade calcestruzzo). Questi separatori separati possono essere inseriti o rimossi in modo completamente modulare e memorizzeranno gli handle DB, richiedono ResourceT, ecc ....
Il mio primo tentativo è stato quello di racchiudere ReaderT, che sarebbe stato utilizzato per tenere l'handle del DB. Divenne immediatamente evidente che ciò non avrebbe funzionato, in quanto ReaderT (e StateT, ecc.) Non possono essere impilati senza usare catene di "lift" hardcoded, rompendo così la modularità collegabile degli elementi stack.
L'unica soluzione sembrava essere quella di scrivere copie completamente separate della monade ReaderT, ognuna delle quali consentiva l'accesso agli altri ad un livello inferiore. Questo funziona, ma la soluzione è pieno di codice standard, qualcosa di simile
class HasUserTable m where
getUser::String->m User
newtype UserTableT m r = UserTableT{runUserTableT::String->m r}
--Standard monad instance stuff, biolerplate copy of ReaderT
instance Functor m=>Functor (UserTableT m) where....
instance Applicative m=>Applicative (UserTableT m) where....
instance Monad m=>Monad (UserTableT m) where....
instance Monad m=>HasUserTable (UserTableT m) where....
--Gotta hardcode passthrough rules to every other monad transformer
--in the world, mostly using "lift"....
instance MonadTrans BlockCacheT where....
instance (HasUserTable m, Monad m)=>HasUserTable (StateT a m)....
instance (HasUserTable m, Monad m)=>HasUserTable (ResourceT m)....
.... etc for all other monad transformers
--Similarly, need to hardcode passthrough rules for all other monads
--through the newly created one
instance MonadResource m=>MonadResource (UserTableT m) where....
instance MonadState a m=>MonadState a (UserTableT m) where....
instance (MonadBaseControl IO m) => MonadBaseControl IO (UserTableT m)....
.... etc for all other monad transformers
Ciò che rende questo ancora peggiore è che abbiamo bisogno di aggiungere regole ancora più passthrough per ogni nuovo trasformatore monade aggiungiamo (IE- ogni nuova tabella aggiungiamo necessità di passare attraverso tutti gli altri trasformatori di tabella, quindi abbiamo bisogno di n^2 dichiarazioni di istanza!)
C'è un modo più pulito per fare questo?
Questo sembra molto simile agli effetti estensibili. https://hackage.haskell.org/package/free-vl ha un'implementazione e riferimenti a documenti che lo spiegano. –
"quindi abbiamo bisogno di n^2 dichiarazioni di istanza" questo è un problema ben noto con lo stile mtl dei trasformatori monad. Se scrivi il tuo tipo come 'ReaderT String m r', invece, puoi usare newtype generalizzato derivante per derivare quelle istanze che sono identiche a quelle per il lettore (che sembra la maggior parte di esse qui). È possibile sostituire la maggior parte delle istanze con 'MonadTrans t, HasUserTable m => HasUserTable (t m)' ma questo tipo di uccisione annulla il tipo e richiede diverse estensioni. – user2407038
@ user2407038 il problema con l'utilizzo di un 'MonadTrans t, HasUserTable m => HasUserTable (t m)' è stato applicato anche a UserTableT, in conflitto con l'istanza corretta che avevo bisogno di scrivere. Sospetto che questo sia il motivo per cui il problema n^2 esiste del tutto (altrimenti lo avrebbero fatto solo per tutti i trasformatori monad). Penso che il tuo commento sul problema n^2 potrebbe essere la risposta alla mia domanda, anche se non felice ... Non puoi fare niente di meglio con i trasformatori di Monade e forse anche con Haskell. Ho un riferimento per discutere di questo problema, lo accetterei come risposta. – jamshidh