2015-11-29 14 views
5

CURA 2015/11/29: vedi fondoMemoizing e ripetendo IO monadi

Sto cercando di scrivere un'applicazione che ha un pulsante do-ultima-azione-di nuovo. Il comando in questione può chiedere input, e il mio pensiero su come ottenere questo risultato è stato solo rieseguire la monade risultante con IO memorizzato.

Ci sono molti post su SO con domande simili, ma nessuna delle soluzioni sembra funzionare qui.

Ho sollevato il codice memoIO da this SO answer e modificato l'implementazione per eseguire MonadIO.

-- Memoize an IO function 
memoIO :: MonadIO m => m a -> m (m a) 
memoIO action = do 
    ref <- liftIO $ newMVar Nothing 
    return $ do 
     x <- maybe action return =<< liftIO (takeMVar ref) 
     liftIO . putMVar ref $ Just x 
     return x 

Ho un piccolo Repro dell'approccio di mia app, l'unica vera differenza è la mia applicazione ha un grosso stack trasformatore invece di correre in IO:

-- Global variable to contain the action we want to repeat 
actionToRepeat :: IORef (IO String) 
actionToRepeat = unsafePerformIO . newIORef $ return "" 

-- Run an action and store it as the action to repeat 
repeatable :: IO String -> IO String 
repeatable action = do 
    writeIORef actionToRepeat action 
    action 

-- Run the last action stored by repeatable 
doRepeat :: IO String 
doRepeat = do 
    x <- readIORef actionToRepeat 
    x 

L'idea dell'essere Posso memorizzare un'azione con memoized IO in un IORef (tramite repeatable) quando registro ciò che è stato fatto l'ultima volta, quindi lo eseguo nuovamente con doRepeat.

I test di questa via:

-- IO function to memoize 
getName :: IO String 
getName = do 
    putStr "name> " 
    getLine 

main :: IO() 
main = do 
    repeatable $ do 
     memoized <- memoIO getName 
     name <- memoized 
     putStr "hello " 
     putStrLn name 
     return name 
    doRepeat 
    return() 

con uscita prevista:

name> isovector 
hello isovector 
hello isovector 

ma effettiva di uscita:

name> isovector 
hello isovector 
name> wasnt memoized 
hello wasnt memoized 

io non sono del tutto sicuro qual è il problema, o anche come fare per eseguire il debug di questo. Spara alla mia testa, suppongo che la valutazione pigra mi morde da qualche parte, ma non riesco a capire dove.

Grazie in anticipo!


EDIT 2015/11/29: Il mio caso destinazione d'uso per questo è di implementare l'operatore repeat last change in vim-clone. Ogni azione può eseguire un numero arbitrario di chiamate IO arbitrarie, e vorrei che fosse in grado di specificare quali dovrebbero essere memoizzate (leggendo un file, probabilmente no, chiedendo all'utente di inserire input, sì).

+0

Si desidera rieseguire l'azione o si desidera restituire il risultato dell'ultima azione? Questa è una differenza importante. Se quest'ultimo, si desidera memoize il valore di ritorno dell'ultima azione, mentre se il primo, si desidera ricordare l'intera azione. Ad esempio, se si desidera leggere un file e restituirne il contenuto, si desidera leggere nuovamente il file (possibilmente ottenere dati aggiornati) o semplicemente restituire il contenuto memorizzato nella cache? –

+0

Voglio ripetere l'azione (in I/O BigMonadStackT arbitrario), con cache (restituendo solo il risultato di) chiamate IO al suo interno. Il caso d'uso previsto è implementare l'operatore [ripeti l'ultimo cambiamento] (http://vim.wikia.com/wiki/Repeat_last_change) in vim, che richiede l'input solo la prima volta che lo esegui. –

+0

Credo che questo sia un po 'problematico. Se si decide di memoizzare solo alcune chiamate, cosa succede se le altre chiamate non memorizzate modificano il flusso di controllo? Ad esempio, cosa succede se l'input dell'utente è quale file deve essere letto? –

risposta

0

Ho trovato una soluzione. Richiede il wrapping della monade originale in un nuovo trasformatore che registra i risultati dell'IO e li inietta la prossima volta che viene eseguita la monad sottostante.

Inseriscilo qui, quindi la mia risposta è completa.

{-# LANGUAGE FlexibleContexts #-} 
{-# LANGUAGE GeneralizedNewtypeDeriving #-} 
{-# LANGUAGE LambdaCase #-} 

import Control.Applicative (Applicative(..)) 
import Data.Dynamic 
import Data.Maybe (fromJust) 
import Control.Monad.RWS 

-- | A monad transformer adding the ability to record the results 
-- of IO actions and later replay them. 
newtype ReplayT m a = 
    ReplayT { runReplayT :: RWST() [Dynamic] [Dynamic] m a } 
    deriving (Functor 
      , Applicative 
      , Monad 
      , MonadIO 
      , MonadState [Dynamic] 
      , MonadWriter [Dynamic] 
      , MonadTrans 
      ) 

-- | Removes the first element from a list State and returns it. 
dequeue :: MonadState [r] m 
     => m (Maybe r) 
dequeue = do 
    get >>= \case 
     []  -> return Nothing 
     (x:xs) -> do 
      put xs 
      return $ Just x 

-- | Marks an IO action to be memoized after its first invocation. 
sample :: (MonadIO m 
      , Typeable r) 
     => IO r 
     -> ReplayT m r 
sample action = do 
    a <- dequeue >>= \case 
     Just x -> return . fromJust $ fromDynamic x 
     Nothing -> liftIO action 
    tell [toDyn a] 
    return a 

-- | Runs an action and records all of its sampled IO. Returns a 
-- action which when invoked will use the recorded IO. 
record :: Monad m 
     => ReplayT m a 
     -> m (m a) 
record action = do 
    (a, w) <- evalRWST (runReplayT action)() [] 
    return $ do 
     evalRWST (runReplayT action)() w 
     return a 
5

il problema è nel principale si sta creando un nuovo promemoria ogni volta che si chiama l'azione

è necessario spostare memoized <- memoIO getName sopra l'azione

main :: IO() 
main = do 
    memoized <- memoIO getName --moved above repeatable $ do 
    repeatable $ do 
           --it was here 
     name <- memoized 
     putStr "hello " 
     putStrLn name 
     return name 
    doRepeat 
    return() 

edit: è questo accettabile

import Data.IORef 
import System.IO.Unsafe 

{-# NOINLINE actionToRepeat #-} 
actionToRepeat :: IORef (IO String) 
actionToRepeat = unsafePerformIO . newIORef $ return "" 

type Repeatable a = IO (IO a) 

-- Run an action and store the Repeatable part of the action 
repeatable :: Repeatable String -> IO String 
repeatable action = do 
    repeatAction <- action 
    writeIORef actionToRepeat repeatAction 
    repeatAction 

-- Run the last action stored by repeatable 
doRepeat :: IO String 
doRepeat = do 
    x <- readIORef actionToRepeat 
    x 

-- everything before (return $ do) is run just once 
hello :: Repeatable String 
hello = do 
    putStr "name> " 
    name <- getLine 
    return $ do 
     putStr "hello " 
     putStrLn name 
     return name 

main :: IO() 
main = do 
    repeatable hello 
    doRepeat 
    return() 
+0

Cool, grazie per la risposta! Sfortunatamente, non sono sicuro che questo accadrà sulla mia vera applicazione (un clone di vim), comunque. C'è un modo per definirli sul posto come nel mio esempio? Alcune delle mie azioni sono ricorsive, con ogni iterazione che esegue un IO che vorrei memoize. Un'azione prende input dall'utente getChar-by-getChar fino a quando l'utente decide di fermarsi, e mi piacerebbe poterlo riprodurre in toto. –