记住有效的功能

gal*_*ais 5 haskell memoization unsafe-perform-io

我开始研究一个将元胞自动机定义为局部转换函数的项目:

newtype Cellular g a = Cellular { delta :: (g -> a) -> a }
Run Code Online (Sandbox Code Playgroud)

无论何时g是a Monoid,都可以通过在应用本地转换之前移动焦点来定义全局转换.这给了我们以下step功能:

step :: Monoid g => Cellular g a -> (g -> a) -> (g -> a)
step cell init g = delta cell $ init . (g <>)
Run Code Online (Sandbox Code Playgroud)

现在,我们可以通过使用简单地运行自动机iterate.通过memo模仿每个步骤,我们可以节省很多(而且我确实意味着很多:它可以节省数小时)重新计算:

run :: (Monoid g, Memoizable g) => Cellular g a -> (g -> a) -> [g -> a]
run cell = iterate (memo . step cell)
Run Code Online (Sandbox Code Playgroud)

我的问题是我推广Cellular到了CelluarT能够在本地规则中使用副作用(例如复制随机邻居):

newtype CellularT m g a = Cellular { delta :: (g -> m a) -> m a }
Run Code Online (Sandbox Code Playgroud)

但是,我只想让效果运行一次,这样如果你多次询问一个单元格的值是多少,那么答案都是一致的.memo我们在这里失败了,因为它节省了有效的计算而不是结果.

如果不使用不安全的功能,我不希望这是可以实现的.我试图使用unsafePerformIOa IORef和a Map g a来存储已计算的值:

memoM :: (Ord k, Monad m) => (k -> m v) -> (k -> m v)
memoM =
  let ref = unsafePerformIO (newIORef empty) in
  ref `seq` loopM ref

loopM :: (Monad m, Ord k) => IORef (Map k v) -> (k -> m v) -> (k -> m v)
loopM ref f k =
  let m = unsafePerformIO (readIORef ref) in
  case Map.lookup k m of
    Just v  -> return v
    Nothing -> do
      v <- f k
      let upd = unsafePerformIO (writeIORef ref $ insert k v m)
      upd `seq` return v
Run Code Online (Sandbox Code Playgroud)

但它以不可预测的方式运行:memoM putStrLn正确记忆,同时memoM (\ str -> getLine)尽管传递相同的参数,仍保持获取行.