2011-07-06 18 views
9

Un titolo confuso per una domanda confusa! Capisco a) monade, b) la monade IO, c) il monito (Control.Monad.Cont), e d) il monad di trasformatore di continuazione ContT. (E capisco vagamente i trasformatori di Monade in generale - sebbene non sia abbastanza per rispondere a questa domanda.) Capisco come scrivere un programma dove all le funzioni sono in Cont monad (Cont r a), e capisco come scrivere un programma dove all le funzioni sono contenute nella monade Cont/IO combinata (ContT r IO a).Escaping dalla monade IO all'interno della monade Continuazione

Ma mi chiedo come potrei scrivere un programma in cui alcune funzioni sono in una monade combinato Cont/IO (ContT r IO a) e altre funzioni sono solo in monade Cont (Cont r a). Fondamentalmente, voglio scrivere l'intero programma in uno stile di continuazione, ma uso solo la monade IO dove necessario (molto simile al codice Haskell "normale", uso solo la monade IO dove necessario).

Per esempio prendere in considerazione queste due funzioni, in stile non-continuazione:

foo :: Int -> IO Int 
foo n = do 
    let x = n + 1 
    print x 
    return $ bar x 

bar :: Int -> Int 
bar m = m * 2 

Nota che foo richiede IO ma bar è puro. Ora ho capito come scrivere questo codice completamente utilizzando la monade di continuazione, ma avevo bisogno di infilare IO attraverso bar così:

foo :: Int -> ContT r IO Int 
foo n = do 
    let x = n + 1 
    liftIO $ print x 
    bar x 

bar :: Int -> ContT r IO Int 
bar m = return $ m * 2 

ho faccio vogliono tutto il mio codice in stile continuazione, ma io don' t desidera utilizzare la monade IO su funzioni che non lo richiedono. Fondamentalmente, io piacerebbe definire bar in questo modo:

bar :: Int -> Cont r Int 
bar m = return $ m * 2 

Purtroppo, non riesco a trovare un modo per chiamare una funzione Cont r a monade (bar) dall'interno di una funzione ContT r IO a monade (foo). C'è un modo per "sollevare" una monade non trasformata in una trasformata? Ad esempio, come posso modificare la linea "bar x" in foo in modo che possa chiamare correttamente bar :: Int -> Cont r Int?

risposta

17

Questo è dove Control.Monad.Class viene in Fai bar polimorfico in quanto monade può funzionare in:.

bar :: MonadCont m => Int -> m Int 
bar m = return $ m * 2 

Nota che l'elenco dei casi in fondo alla pagina mostra che le istanze di MonadCont noti al momento i documenti sono stati creati includono sia Cont r e Monad m => ContT r m. Inoltre, la classe MonadCont è ciò che definisce la funzione callCC, che è ciò che è necessario per utilizzare le funzionalità di continuazione. Ciò significa che è possibile utilizzare la piena espressività delle continuazioni all'interno di bar, anche se questo esempio no.

In questo modo, si scrivono funzioni che non possono essere utilizzate in modo probabile IO, perché non hanno un vincolo MonadIO, né il loro tipo menziona esplicitamente IO. Ma sono polimorfici in cui funzionano all'interno, in modo tale che possano essere chiamati banalmente da contesti che includono IO.

+1

Grazie. Che funzioni. Ho anche trovato la mia soluzione, che mi ha dato esattamente quello che volevo (non ho dovuto cambiare 'Bar'):' liftCont :: Cont (m r) a -> ContT r m a'; 'liftCont c = ContT $ runCont c'. La mia soluzione scompatta il 'Cont' e costruisce un' ContT'. Penso che la tua soluzione sia migliore perché è polimorfica e non richiede una reale manipolazione delle strutture dati, quindi spunta per te. Ma posterò il mio come un'altra risposta, poiché è utile nel caso in cui non si possa modificare 'bar'. Anche +1 per la spiegazione del motivo per cui sarebbe impossibile usare IO in 'bar'. – mgiuca

5

ho trovato che questo fa esattamente quello che volevo (senza dover cambiare Bar):

liftCont :: Cont (m r) a -> ContT r m a 
liftCont = ContT . runCont 

Questa scompatta l'Cont e costruisce un ContT.

posso quindi utilizzare liftCont chiamare Bar da Foo:

foo n = do 
    let x = n + 1 
    liftIO $ print x 
    liftCont $ bar x 

Non credo che questo è "più bello" rispetto alla soluzione di Carl (gli ho dato il segno di spunta), ma ho postato qui perché consente di utilizzare Bar senza modificare il suo tipo, quindi utile se non è possibile modificare Bar. (Probabilmente ha prestazioni peggio però.)