わかりました、この問題に対する「正しい」答えをまだ誰も与えていないことに失望しています。なぜなら、それが存在することは知っていますが、うまく説明することはできないからです。私の答えはhttp://blog.sigfpe.com/2006/12/evaluating-cellular-automata-is.htmlに基づいています
まず、標準の 1d ジッパーは次のようになります。
Data U x = U [x] x [x]
最初の要素は、フォーカスの「左」にあるすべての要素の逆リストであり、次にフォーカス要素、次にフォーカスの「右」にあるすべての要素のリストです。例えば:
U [-1,-2,-3] 0 [1,2,3]
次に、ジッパーを左右に動かします。私たちがグリッドの端から離れたときに何をすべきかを決めなければなりません。元の投稿では、コーナー ケースは読者の演習として残されるように、単純に無限グリッドを想定していました。
left (U (a:as) x zs) = U as a (x:zs)
right (U as x (z:zs)) = U (x:as) z zs
コンテナのように見えるものはすべて Functor にする必要があります。
instance Functor U where
fmap f (U a x z) = U (map f a) (f x) (map f z)
この時点で、私がやろうとしていることとその理由を誰かに説明してもらいたいと思います。U
のインスタンスを作成しますControl.Comonad
。私が説明できる最善のことは、コモナドは裏返しのモナドのようなものだということです。1 つの要素を与えて、新しい値を持つコンテナーを作成するように求める代わりに(>>= :: Monad m => m a -> (a -> m b) -> m b)
、Comonads は構造全体を与えて、フォーカスに属する値のみを要求します。(=>>) :: Comonad w=>w a -> (w a -> b) -> w
したがって、comonad-3.0.2 パッケージの Control.Comonad の条件を使用すると、次のようになります。
Instance Comonad U where
-- extract :: U a -> a -- called coreturn in the post
extract (U _ x _) = x
-- duplicate :: U a -> U (U a) -- called cojoin in the post
duplicate x = U (tail $ iterate left x) x (tail $ iterate right x)
duplicate は、Zippers の Zipper を提供し、それぞれが最後の要素よりも 1 つ多く左または右にシフトされます。膨大な量のメモリのように見えますが、Haskell はレイジーであり、実際のメモリ フットプリントは非常に小さく、完全なセットの場合は O(n)、まったく見回さない場合は O(1) のオーダーです。
しかし、これはすべて 1 つの次元にすぎません。繰り返しになりますが、これを 2 次元に拡張することを簡単に説明できるほど賢くありません。
data U2 x = U2 (U(U x))
instance Functor U2 where
fmap f (U2 y) = U2 $ fmap (fmap f) y
instance Comonad U2 where
extract (U2 y) = extract (extract y)
duplicate (U2 y) = fmap U2 $ U2 $ roll $ role y where
iterate' f = tail . iterate f
role x = U (iterate' (fmap left) x) x (iterate' (fmap right) x)
複製関数は、適切にシフトされたグリッドのグリッドを作成するようになりました。そう
goLeft u = let (U _ (U x _ _) _) = duplicate u in x
goRight u = let (U _ (U _ _ x) _) = duplicate u in x
goUp = left . duplicate
goDown = right . duplicate
here = extract
Haskell はレイジーなので、これらはすべて O(1) 関数です。さらに興味深いことにhere
、時間とメモリの両方で O(1) コストを変更し、計算で近傍セルを使用できます。これにより、game of life
セルオートマトンのようなものを実装するのと同じくらい簡単になります
rule (U2 (U
(U (u0:_) u1 (u2:_):_)
(U (u3:_) u4 (u5:_))
(U (u6:_) u7 (u8:_):_))) =
let n = length $ filter id [u0,u1,u2,u3,u5,u6,u7,u8] in
u4 && (n==2 || n==3) || (not u4) && n==3
-- assume u is the original graph each step is
step u = u =>> rule
上記のブログ投稿に加えて、Google で Comonad を検索して詳細を確認することをお勧めします。