Conway play of life performance using comonad store

I wrote a simple Conway Game of Life implementation using the Store comonad (see code below). My problem is that mesh generation becomes noticeably slower from the fifth iteration. Is my problem related to the fact that I am using Store comonad? Or am I making a blatant mistake? As far as I can tell, other implementations that are based on the Zipper comonads are efficient.

import Control.Comonad

data Store s a = Store (s -> a) s

instance Functor (Store s) where
    fmap f (Store g s) = Store (f . g) s

instance Comonad (Store s) where
    extract (Store f a) = f a
    duplicate (Store f s) = Store (Store f) s

type Pos = (Int, Int)

seed :: Store Pos Bool
seed = Store g (0, 0)
    where
        g ( 0,  1) = True
        g ( 1,  0) = True
        g (-1, -1) = True
        g (-1,  0) = True
        g (-1,  1) = True
        g _        = False

neighbours8 :: [Pos]
neighbours8 = [(x, y) | x <- [-1..1], y <- [-1..1], (x, y) /= (0, 0)]

move :: Store Pos a -> Pos -> Store Pos a
move (Store f (x, y)) (dx, dy) = Store f (x + dx, y + dy)

count :: [Bool] -> Int
count = length . filter id

getNrAliveNeighs :: Store Pos Bool -> Int
getNrAliveNeighs s = count $ fmap (extract . move s) neighbours8

rule :: Store Pos Bool -> Bool
rule s = let n = getNrAliveNeighs s
        in case (extract s) of
            True  -> 2 <= n && n <= 3
            False -> n == 3

blockToStr :: [[Bool]] -> String
blockToStr = unlines . fmap (fmap f)
    where
        f True  = '*'
        f False = '.'

getBlock :: Int -> Store Pos a -> [[a]]
getBlock n store@(Store _ (x, y)) =
    [[extract (move store (dx, dy)) | dy <- yrange] | dx <- xrange]
    where
        yrange = [(x - n)..(y + n)]
        xrange = reverse yrange

example :: IO ()
example = putStrLn
        $ unlines
        $ take 7
        $ fmap (blockToStr . getBlock 5)
        $ iterate (extend rule) seed

      

+3


source to share


1 answer


The comonad store itself doesn't actually store anything (except in the abstract sense that the function is a "container"), but must compute it from scratch. This obviously becomes very inefficient over a few iterations.

You can mitigate this without changing your code, though if you just back up the function s -> a

with some remembering :

import Data.MemoTrie

instance HasTrie s => Functor (Store s) where
  fmap f (Store g s) = Store (memo $ f . g) s

instance HasTrie s => Comonad (Store s) where
  extract (Store f a) = f a
  duplicate (Store f s) = Store (Store f) s

      



Didn't check if this actually gives acceptable performance.

By the way, Edward Kmett had an explicitly flagged version in the old version of the packagecomonad-extras

, but it's gone. I recently looked to see if it still works (it looks like it works after setting up the dependencies).

+6


source







All Articles