我开始研究一个将元胞自动机定义为局部转换函数的项目:
newtype Cellular g a = Cellular { delta :: (g -> a) -> a }
无论何时g
是 a Monoid
,都可以通过在应用局部过渡之前转移焦点来定义全局过渡。这为我们提供了以下step
功能:
step :: Monoid g => Cellular g a -> (g -> a) -> (g -> a)
step cell init g = delta cell $ init . (g <>)
现在,我们可以简单地使用iterate
. memo
我们可以通过调整每个步骤来节省很多(我的意思是很多:它确实节省了几个小时)的重新计算:
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
、 anIORef
和 aMap 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)
继续获取行,尽管传递给它的参数相同。