编辑2015-11-29 :见底部
我正在尝试编写一个具有do-last-action-again按钮的应用程序。有问题的命令可以请求输入,我对如何实现这一点的想法就是用memoized IO重新运行生成的monad。
SO上有很多关于类似问题的帖子,但这些解决方案似乎都没有。
我从this SO answer解除了memoIO
代码,并将实施更改为在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
我的应用程序的方法有一个小的重复,唯一真正的区别是我的应用程序有一个大变换器堆栈,而不是只在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
我的想法是,当我记录上次完成的内容时,我可以在IO
(通过IORef
)中存储带有记忆repeatable
的操作,然后使用{再次执行此操作{1}}。
我通过以下方式测试:
doRepeat
预期输出:
-- 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 ()
但实际输出:
name> isovector
hello isovector
hello isovector
我不完全确定问题是什么,甚至不知道如何进行调试。枪到我的头上,我假设懒惰的评价在某处咬我,但我无法弄清楚在哪里。
提前致谢!
编辑2015-11-29 :我的预期用例是在vim-clone中实现repeat last change运算符。每个动作都可以执行任意数量的任意IO调用,我希望它能够指定哪些应该被记忆(读取文件,可能不是。要求用户输入,是)。
答案 0 :(得分:5)
问题在于,每次调用动作时都会创建一个新的备忘录
您需要将memoized <- memoIO getName
移到行动
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 ()
编辑:这是可接受的
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 ()
答案 1 :(得分:0)
我想出了一个解决方案。它需要将原始monad包装在一个新的变换器中,该变换器记录IO的结果并在下次运行底层monad时注入它们。
在此发布,以便我的答案完整。
{-# 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