【发布时间】:2015-09-11 14:33:32
【问题描述】:
我开始研究一个将元胞自动机定义为局部转换函数的项目:
newtype Cellular g a = Cellular { delta :: (g -> a) -> a }
只要g 是Monoid,就可以通过在应用局部过渡之前移动焦点来定义全局过渡。这为我们提供了以下step 函数:
step :: Monoid g => Cellular g a -> (g -> a) -> (g -> a)
step cell init g = delta cell $ init . (g <>)
现在,我们可以使用iterate 简单地运行自动机。通过memoizing 每个步骤,我们可以节省很多(我的意思是很多:它确实节省了几个小时)重新计算:
run :: (Monoid g, Memoizable g) => Cellular g a -> (g -> a) -> [g -> a]
run cell = iterate (memo . step cell)
我的问题是我将Cellular 概括为CelluarT 以便能够在本地规则中使用副作用(例如复制随机邻居):
newtype CellularT m g a = Cellular { delta :: (g -> m a) -> m a }
但是,我只希望效果运行一次,这样如果您多次询问一个单元格的值是多少,答案都是一致的。 memo 在这里失败了,因为它保存了有效的计算,而不是它的结果。
如果不使用不安全的功能,我不认为这是可以实现的。我尝试使用unsafePerformIO、IORef 和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
但它的行为方式不可预知:memoM putStrLn 被正确记忆,而 memoM (\ str -> getLine) 尽管传递了相同的参数,但仍继续获取行。
【问题讨论】:
-
您使用的是哪个备忘录库? memoize?
-
您的数据类型等价于
ContandContT。type Cellular g a = Cont a g和type CellularT m g a = ContT a m g
标签: haskell memoization unsafe-perform-io