2016-02-20 17 views
10

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?

+1

Questo sembra molto simile agli effetti estensibili. https://hackage.haskell.org/package/free-vl ha un'implementazione e riferimenti a documenti che lo spiegano. –

+5

"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

+0

@ 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

risposta

8

Sì, questo è uno dei problemi con i trasformatori monod: quando aggiungi un nuovo trasformatore, devi scrivere un numero sempre crescente di istanze di piastre. Sono le istanze n ogni volta, per un totale di O (n^2) istanze. Ad esempio, è possibile osservare questo problema di ridimensionamento in the mtl source code. I trasformatori Monad non sono facilmente estensibili.

Ora, una buona percentuale delle monadi che usiamo quotidianamente può essere espressa come una combinazione dei trasformatori forniti da mtl, il che significa che qualcun altro ha già fatto il lavoro di scrivere tutte quelle noiose istanze. Ma questi trasformatori non coprono certamente lo ogni monade e ti mordere ogni volta che devi scriverlo da solo.

Ecco perché è in corso uno sforzo per elaborare nuovi approcci per effettuare la digitazione. Un buon esempio in Haskell è Kiselyov et al's extensible-effects library, che adotta un approccio algebrico per effettuare la digitazione, basato sulle monadi gratuite. Il design di questa libreria è descritto in due articoli: An Alternative to Monad Transformers, che impiega un po 'di tempo a descrivere problemi con l'approccio mtl e More Extensible Effects, descrivendo un'implementazione aggiornata e ottimizzata della libreria.

Se si desidera vedere fino a che punto è possibile eseguire la digitazione dell'effetto sicuro ed estendibile, vedere effects library di Edwin Brady per il linguaggio Idris. Esistono molte risorse che spiegano effects: un tutorial, l'articolo originale Programming and Reasoning with Algebraic Effects e Resource-dependent Algebraic Effects che descrive alcune funzionalità più recenti di effects. Probabilmente ci sono altre risorse che ho dimenticato in questa lista.

+0

Grazie per il chiarimento e il puntatore agli effetti estensibili. Darò sicuramente un'occhiata agli effetti estensibili, anche se ora considero risolta questa domanda, poiché più persone hanno sottolineato che il problema n^2 è reale e un problema di ricerca in corso nella comunità di Haskell. FWIW, a mio avviso questo è un problema molto importante da risolvere (a meno che l'effetto estensibile non lo abbia già risolto), probabilmente uno degli unici grandi problemi che ho incontrato in Haskell. – jamshidh

+1

Beh, come sempre, è un compromesso. I trasformatori Monad sono certamente più semplici di "effetti estensibili" (e più semplice è ancora in rotazione) e in pratica il prezzo che si paga in termini di flessibilità non è in realtà così ripido, come ho accennato nella mia risposta. E l'effetto della digitazione è facoltativo per iniziare - la maggior parte delle lingue non lo forzano su di te come fa Haskell, e siamo stati sorprendentemente produttivi come un'industria per 50 anni senza di essa! –

Problemi correlati