Memorizing and repeating IO monads

EDITED 2015-11-29 : see bottom

I am trying to write an application with a do-last-action-again button. The team in question may request input, and my thought on how to do this was to simply restart the resulting monad with memoized IO.

There are many posts on SO with similar questions, but none of the solutions seem to work here.

I removed the memoIO code from this SO answer and changed the implementation to MonadIO .

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

I have a small review of my approach to the application, the only real difference is that my application has a large transformer stack instead of just working 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 

The idea is that I can save the action with memoized IO to IORef (via repeatable ) when I record what was done last and then repeat it with doRepeat .

I am testing this through:

 -- 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 () 

with expected output:

 name> isovector hello isovector hello isovector 

but the actual conclusion is:

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

I'm not quite sure what the problem is, or even how to debug it. A gun to my head, I would suggest that a lazy assessment bites me, but I canโ€™t understand where.

Thanks in advance!


EDIT 2015-11-29 . My intended use case for this is to implement repeat the last change of the statement in the vim clone. Each action can perform an arbitrary number of arbitrary I / O calls, and I would like it to indicate which ones should be stored in memory (reading a file, maybe not. User request for input, yes).

+5
source share
2 answers

I came up with a solution. It requires wrapping the original monad in a new transformer, which records the I / O results and enters them the next time the main monad is started.

Spending it here so that my answer is complete.

 {-# 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 ma = ReplayT { runReplayT :: RWST () [Dynamic] [Dynamic] ma } 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 mr 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 ma -> m (ma) record action = do (a, w) <- evalRWST (runReplayT action) () [] return $ do evalRWST (runReplayT action) () w return a 
0
source

the problem is that you create a new note every time you invoke an action

you need to move memoized <- memoIO getName up above the action

 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: this is acceptable

 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 () 
+5
source

Source: https://habr.com/ru/post/1237036/


All Articles