记忆和重复IO monad

时间:2015-11-29 06:46:42

标签: haskell io memoization

编辑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调用,我希望它能够指定哪些应该被记忆(读取文件,可能不是。要求用户输入,是)。

2 个答案:

答案 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
相关问题