セルオートマトンをローカル遷移関数として定義するプロジェクトに取り組み始めました。
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
。memo
そして、各ステップを izing することで、再計算を大幅に節約できます (つまり、文字通り何時間も節約できます) 。
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 }
ただし、効果を 1 回だけ実行して、セルの値を複数回尋ねても、答えがすべて一致するようにしたいと考えています。結果ではなく有効な計算memo
を保存するため、ここでは失敗します。
安全でない機能を使用しなければ、これが達成できるとは思えません。unsafePerformIO
、 an 、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
しかし、それは予測不可能な方法で動作します:同じ引数が渡されているにもかかわらず、行をフェッチし続けるmemoM putStrLn
間、正しくメモ化されます。memoM (\ str -> getLine)