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.
Oh penso di essere stupido, hai detto che in una delle tue precedenti risposte, non riuscivo a capirlo al momento. Ora, grazie! – aelguindy