In [Part I](https://www.fpcomplete.com/user/edwardk/cellular-automata/part-1), I showed how we can build up cellular automata in Haskell and render them to the web as custom SVGs. In [Part II](https://www.fpcomplete.com/user/edwardk/cellular-automata/part-2), I replaced the SVG writer with a hand-rolled PNG writer.
The last article got lost in the weeds playing with PNG writing and the comonadic structure of folds. This time around I want to go back to focusing on the automata themselves. If you haven't read those yet, I'd highly recommend at least skimming at least the first one to familiarize yourself with its contents before proceeding.
One of the issues I raised back in part I was that `Store` was in some sense too big to describe automata because the automata could know and use their absolute position information when computing their answers. I'd like to fix that.
Another thing I hinted at was that it was possible to build automata on strange topologies. I'd like to show how we can enable interesting topologies through the very act of fixing the previous problem.
Moves like Jagger
=================
The problem that we had was that the `Store`/`Context` comonad gave you direct access to the current location. So nothing prevents a rule from acting very differently in position 34 than it does anywhere else in the space.
We can fix that by introducing a notion of a relative movement rather than just having absolute positioning.
Now, we an define our notion of a rule as something that can use relative position information alone, and can combine answers to questions about nearby locations to generate a local answer.
```haskell
type Rule m a = (m -> a) -> a
```
Under this scheme, our old 2-color Wolfram-style [elementary cellular automata](http://mathworld.wolfram.com/ElementaryCellularAutomaton.html) rules look like:
```haskell
data Move = R | S | L deriving (Eq, Enum, Bounded, Show, Read)
rule :: Word8 -> Rule Move Bool
rule w f = testBit w $ 0 L.& partsOf L.bits .~ (f <$> [minBound .. maxBound])
```
If we want to increase the "speed of light" we can make a new `Move` type, and generalize `rule`.
```haskell
data Move2 = RR | R | S | L | LL deriving (Eq, Enum, Bounded, Show, Read)
rule :: (Enum m, Bounded m, Num n, Bits n) => n -> Rule m Bool
rule w f = testBit w $ 0 L.& partsOf L.bits .~ (f <$> [minBound .. maxBound])
```
Now `rule` can work for pretty much any enumerable, bounded move type and provide you with Wolfram-like rules.
Now we just need some way to act on a `Rule`!
Act Upon The World
==================
It turns out it is better to think of our rules as acting upon our automaton's topology rather than vice versa.
We can do that by describing the ["action"](http://en.wikipedia.org/wiki/Semigroup_action) of our movements on our position. If our movements were always composable, then we would want this to be a "semigroup action", or even a "monoid action" if we had a unit. If you are curious to know more about monoid actions, I'd encourage you to read [Brent's Pearl](http://www.cis.upenn.edu/~byorgey/pub/monoid-pearl.pdf), but then if you've been following all of the myriad links I've been throwing out from the beginning, then you may already have done so. I cited it for other reasons back in Part I.
However, since we want to ensure we have a discrete "speed of light" governing information travel in our automaton and we don't want to deal with relativistic effects, for now, let's just not let them compose for now.
```haskell
type Act m s = m -> s -> s
```
If we wanted to be more correct, we should likely have different universe types for each action, but it'll suffice to let the action define our topology instead.
An action transforms a move into a function that transforms the current location.
```haskell
flat :: Act Move Int
flat L i = i-1
flat S i = i
flat R i = i+1
```
Another example topology for the first `Move` type we started with would be to just treat the movement as relative in a world.
```haskell
modulo :: Int -> Act Move Int
modulo n m i = flat m i `mod` n
```
Now we just need to define the step function:
I Dream of Genie
================
```haskell
step :: Act m s -> Rule m a -> Context s s a -> a
```
Let's say for a minute that we didn't know how to write this! What could we do?
Back in 2005, Lennart Augustsson [wrote](http://permalink.gmane.org/gmane.comp.lang.haskell.general/12747) a wonderful tool named `djinn` for doing just this sort of thing.
From the release announcement:
> For the curious, Djinn uses a decision procedure for intuitionistic
propositional calculus due to Roy Dyckhoff. It's a variation of
Gentzen's LJ system. This means that (in theory) Djinn will always
find a function if one exists, and if one doesn't exist Djinn will
terminate telling you so.
So let's use it!
On [irc.freenode.net](webchat.freenode.net/?channels=haskell&uio=d4), our well-loved mechanical assistant `lambdabot` has a version of `djinn` installed, which is available via the `@djinn` command.
```haskell
[03:35] edwardk: @djinn a -> a
[03:35] lambdabot: f a = a
[03:35] edwardk: @djinn a -> b -> c
[03:35] lambdabot: -- f cannot be realized.
[03:35] edwardk: @djinn a -> b -> a
[03:35] lambdabot: f a _ = a
```
She doesn't know anything about our `Context`, though, so let's help her out.
```haskell
[03:35] edwardk: @djinn-add data Context a b t = Context (b -> t) a
```
Now she's fully capable of deriving for us the definitions for extract:
```haskell
[03:37] edwardk: @djinn Context a a t -> t
[03:37] lambdabot: f a =
[03:37] lambdabot: case a of
[03:37] lambdabot: Context b c -> b c
```
and even how to `extend` `Context` as an indexed comonad:
```haskell
[03:39] edwardk: @djinn (Context b c t -> r) -> Context a c t -> Context a b r
[03:39] lambdabot: f a b =
[03:39] lambdabot: case b of
[03:39] lambdabot: Context c d -> Context (\ e -> a (Context c e)) d
```
So, let's teach her about rules and actions.
```haskell
[03:42] edwardk: @djinn-add type Rule m a = (m -> a) -> a
[03:42] edwardk: @djinn-add type Act m s = m -> s -> s
```
and ask her for a definition for `step`.
```haskell
[03:43] edwardk: @djinn Act m s -> Rule m a -> Context s s a -> a
[03:43] lambdabot: f _ a b =
[03:43] lambdabot: case b of
[03:43] lambdabot: Context c d -> a (\ _ -> c d)
```
Ack! Something went wrong.
What happened was that the problem was under-constrained.
She didn't have to apply the action, so she didn't.
Similarly, if we try for the unindexed version of `extend`, we run into the same problem!
```haskell
[03:45] edwardk: @djinn (Context a a t -> r) -> Context a a t -> Context a a r
[03:45] lambdabot: f a b =
[03:45] lambdabot: case b of
[03:45] lambdabot: Context c d -> Context (\ e -> a (Context (\ _ -> c d) e)) d
```
So, let's split apart the positive and negative uses of 's' in our original problem.
```haskell
[03:46] edwardk: @djinn-add type Acts m s t = m -> s -> t
[03:46] edwardk: @djinn Acts m s t -> Rule m a -> Context s t a -> a
[03:46] lambdabot: f a b c =
[03:46] lambdabot: case c of
[03:46] lambdabot: Context d e -> b (\ f -> d (a f e))
```
There we have it!
```haskell
step :: Act m s -> Rule m a -> Context s s a -> a
step a b (Context d e) = b (\f -> d (a f e))
```
If we want to clean that up a bit:
```haskell
step :: Act m s -> Rule m a -> Context s s a -> a
step top r (Context f s) = r (f . flip top s)
```
Thats All For Now
=================
Putting all of that together a greyscale version of the PNG writer from my Mandelbrot snippet yields the following code.
Click Run!
```active haskell web
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE DeriveFunctor #-}
import Codec.Compression.Zlib
import Control.Comonad
import Control.Lens as L
import Control.Lens.Internal.Context
import Data.Binary
import Data.Binary.Put
import Data.Bits
import Data.Bits.Lens as L
import Data.ByteString.Lazy as Lazy
import Data.Foldable as F
import Data.List as List
import Data.MemoCombinators
import Data.Monoid
import Data.Vector.Unboxed as Unboxed
import Yesod
-- show
type Rule m a = (m -> a) -> a
data Move = R | S | L deriving (Eq, Enum, Bounded, Show, Read)
rule :: (Enum m, Bounded m) => Word8 -> Rule m Bool
rule w f = testBit w $ 0 L.& partsOf L.bits .~ (f <$> [minBound .. maxBound])
type Act m s = m -> s -> s
modulo :: Int -> Act Move Int
modulo m L i = (i-1) `mod` m
modulo _ S i = i
modulo m R i = (i+1) `mod` m
-- /show
step :: Act m s -> Rule m a -> Context s s a -> a
step top r (Context f s) = r (f . flip top s)
loop :: Integral s => (Context s s a -> a) -> Context s s a -> [Context s s a]
loop f = List.iterate (tab . extend f) . tab where
tab (Context g s) = Context (integral g) s
run :: Word8 -> Int -> Int -> [[Word8]]
run r x m0 = fmap line $ loop (step (modulo m0) (rule r)) $ Context (==0) 0 where
line = fmap bw . window (x `div` 2)
bw True = 0
bw False = 255
window w = iexperiment $ \ s -> [s-w..s+w]
crc32 :: Lazy.ByteString -> Word32
crc32 = complement . Lazy.foldl' f 0xffffffff where
f r b = unsafeShiftR r 8 `xor` crcs Unboxed.! fromIntegral (xor r (fromIntegral b) .&. 0xff)
crcs = Unboxed.generate 256 (go.go.go.go.go.go.go.go.fromIntegral)
go c = unsafeShiftR c 1 `xor` if c .&. 1 /= 0 then 0xedb88320 else 0
putChunk :: Lazy.ByteString -> Lazy.ByteString -> Put
putChunk h b = do
putWord32be $ fromIntegral (Lazy.length b)
putLazyByteString h
putLazyByteString b
putWord32be $ crc32 (h <> b)
png :: Int -> Int -> [[Word8]] -> Lazy.ByteString
png w h fs = runPut $ do
putLazyByteString "\x89PNG\r\n\x1a\n"
putChunk "IHDR" $ runPut $ do
putWord32be (fromIntegral w)
putWord32be (fromIntegral h)
putWord8 8 -- 8 bit
putWord8 0 -- greyscale
putWord8 0
putWord8 0
putWord8 0
putChunk "IDAT" $
compressWith defaultCompressParams { compressLevel = bestSpeed } $
runPut $ F.forM_ (Prelude.take h fs) $ \xs -> do
putWord8 0
F.forM_ (Prelude.take w xs) put
putChunk "IEND" mempty
data App = App
instance Yesod App
mkYesod "App" [parseRoutes| / ImageR GET |]
main :: IO ()
main = warpEnv App
-- show
getImageR :: MonadHandler m => m TypedContent
getImageR = sendResponse $ toTypedContent (typePng, toContent img) where
img = png 280 280 $ run 110 280 30
-- /show
```
With all of that we're still well under a hundred lines.
We're not limited to simple small world topologies, but if you want to connect random points in space, you're probably better off doing so in a 2d automaton, simply because there are more interesting combinations.
In the interest of full disclosure, Djinn isn't perfect. It can't deal with rank-n types. It also doesn't really understand typeclasses as they behind the scenes _also_ involve rank-n types. It is however, an incredibly useful tool that helps showcase the power of [free theorems](http://ttic.uchicago.edu/~dreyer/course/papers/wadler.pdf) to constrain down the space of possible implementations to the point where only reasonable programs can type check at all.
By making our programs _more_ generic we are able to restrict them to fewer possible implementations, leaving us with only one reasonable thing that typechecks.
-[Edward Kmett](mailto:ekmett@gmail.com)
September 15th, 2013