2012-01-29 9 views
45

Ho un problema che si adatta molto bene usando uno stack di MT (o anche un MT) su IO. Tutto è buono, tranne che usare l'ascensore prima di ogni azione è terribilmente fastidioso! Sospetto che non ci sia davvero niente da fare su questo, ma ho pensato di chiedere comunque.Evitare il sollevamento con i trasformatori Monad

Sono consapevole del sollevamento di interi blocchi, ma cosa succede se il codice è veramente di tipi misti? Non sarebbe bello se GHC aggiungesse dello zucchero sintattico (ad esempio, <-$ = <- lift)?

risposta

51

Per tutte le monadi standard mtl, non è necessario il lift. get, put, ask, tell - funzionano tutti in qualsiasi monade con il trasformatore giusto da qualche parte nella pila. Il pezzo mancante è IO e anche lì lo liftIO solleva un'azione IO arbitraria lungo un numero arbitrario di livelli.

Questo viene fatto con i tipi di caratteri per ogni "effetto" in offerta: ad esempio, MonadState fornisce get e put. Se si desidera creare il proprio newtype wrapper per uno stack del trasformatore, si può fare deriving (..., MonadState MyState, ...) con l'estensione GeneralizedNewtypeDeriving, o rotolare il proprio esempio:

instance MonadState MyState MyMonad where 
    get = MyMonad get 
    put s = MyMonad (put s) 

È possibile utilizzare questo per esporre o nascondere i componenti del vostro trasformatore combinato in modo selettivo , definendo alcune istanze e non altre.

(È possibile estendere facilmente questo approccio ai nuovi effetti monadici definiti dall'utente, definendo il proprio tipo di esemplare e fornendo istanze standard per i trasformatori standard, ma le nuove monadi sono rare, il più delle volte, si Otterrai semplicemente componendo il set standard offerto da mtl.)

+0

Oh penso di essere stupido, hai detto che in una delle tue precedenti risposte, non riuscivo a capirlo al momento. Ora, grazie! – aelguindy

43

Puoi rendere le tue funzioni monodiche usando i typeclasses invece delle pile di monad concrete.

Diciamo che avete questa funzione, ad esempio:

bangMe :: State String() 
bangMe = do 
    str <- get 
    put $ str ++ "!" 
    -- or just modify (++"!") 

Naturalmente, si rendono conto che funziona come un trasformatore e, quindi si potrebbe scrivere:

bangMe :: Monad m => StateT String m() 

Tuttavia, se hai una funzione che utilizza uno stack diverso, diciamo ReaderT [String] (StateT String IO)() o qualsiasi altra cosa, dovrai usare la temuta funzione lift! Quindi, come viene evitato?

Il trucco è rendere la firma della funzione ancora più generica, in modo che si affermi che la monade State può apparire in qualsiasi punto dello stack monad. Questo viene fatto in questo modo:

bangMe :: MonadState String m => m() 

questo costringe m essere una monade che supporta lo stato (virtualmente) in qualsiasi punto dello stack Monade, e la funzione sarà quindi funzionare senza sollevare per tale pila.

C'è un problema, però; poiché IO non fa parte di mtl, non ha un trasformatore (ad esempio IOT) né una classe di tipi pratica per impostazione predefinita. Quindi cosa dovresti fare quando vuoi sollevare arbitrariamente le azioni IO?

Per il salvataggio arriva MonadIO!Si comporta in modo quasi identico a MonadState, MonadReader ecc., L'unica differenza è che ha un meccanismo di sollevamento leggermente diverso. Funziona così: puoi prendere qualsiasi azione IO e usare liftIO per trasformarla in una versione agnostica monade. Quindi:

action :: IO() 
liftIO action :: MonadIO m => m() 

Trasformando tutte le azioni monadici che si desidera utilizzare in questo modo, è possibile si intrecciano monadi quanto si vuole, senza alcun sollevamento noioso.

+0

Grazie per la risposta dettagliata! Picchiato nei tempi da ehird però;) – aelguindy

+4

Me ed ehird forniscono soluzioni un po 'diverse a questo problema. Potrebbe valere la pena di leggere entrambe le risposte per capire le alternative che hai :) – dflemstr

+1

È spiacevole che sia necessaria una buona quantità di piastra. – rightfold