I started working on a project defining a cellular automaton as a local transition function:
newtype Cellular g a = Cellular { delta :: (g -> a) -> a }
Whenever g
is a Monoid
, it is possible to define a global transition by shifting the focus before applying the local transition. This gives us the following step
function:
step :: Monoid g => Cellular g a -> (g -> a) -> (g -> a)
step cell init g = delta cell $ init . (g <>)
Now, we can simply run the automaton by using iterate
. And we can save a lot (and I do mean a lot: it literally saves hours) of re-computations by memo
izing each one of the steps:
run :: (Monoid g, Memoizable g) => Cellular g a -> (g -> a) -> [g -> a]
run cell = iterate (memo . step cell)
My problem is that I generalised Cellular
to CelluarT
so that I would be able to use side effects in the local rules (e.g. copying a random neighbour):
newtype CellularT m g a = Cellular { delta :: (g -> m a) -> m a }
However, I only want the effects to be run once so that if you ask a cell multiple times what its value is, the answers are all consistent. memo
fails us here because it saves the effectful computation rather than its result.
I don't expect this to be achievable without using unsafe features. I've tried to have a go at it using unsafePerformIO
, an IORef
and a Map g a
to store the values already computed:
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
But it behaves in unpredictable ways: memoM putStrLn
is correctly memoized whilst memoM (\ str -> getLine)
keeps fetching lines despite the same argument being passed to it.