# Part I: From Theory to Pretty Pictures

29 Nov 2014

As of March 2020, School of Haskell has been switched to read-only mode.

Cellular automata are one of the "go to" examples for comonads in Haskell.

Dan Piponi wrote his article on using comonads to evaluate cellular automata back in 2006, and that was pretty much my introduction to comonads in general. He used a list zipper.

Today, I want to use something a little bit more general and maybe draw some pictures.

# Minding The Store

To that end, let's define the `Store` `Comonad`.

``````{-# LANGUAGE DeriveFunctor #-}

-- show
data Store s a = Store (s -> a) s deriving Functor

instance Comonad (Store s) where
extract (Store f s) = f s
duplicate (Store f s) = Store (Store f) s
-- /show

experiment :: Functor f => (s -> f s) -> Store s a -> f a
experiment k (Store f s) = f <\$> k s

main = putStrLn "It typechecks, so it must be correct!"``````

A `Store s a` describes some "test" that takes a configuration `s` and will produce a value of type `a`, where we also have some ambient initial configuration of type `s` that is known with which we could start the experiment.

The `experiment` combinator characterizes a `Store` completely. It lets you explore variations on the initial conditions of our test.

``````experiment :: Functor f => (s -> f s) -> Store s a -> f a
experiment k (Store f s) = f <\$> k s ``````

`Store` gives you a little bit more power than we want in a cellular automaton, as you can do both relative and global addressing, but it happens to be a very general construction, so we'll start there. It has the benefit that if we decide we want to play with automata in more than 1 dimension all we have to do is change out the state type.

The `Store` comonad has a lot of different uses that aren't immediately obvious. It is used heavily inside of the `lens` library.

# A Glimpse Down the Rabbit Hole

(This section is completely skippable and is included as a highly technical aside)

An interesting exercise for the advanced Haskeller is to flip the definition of `experiment` and take that as the definition for `Store`.

``````{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE RankNTypes #-}
-- show
newtype Pretext s a = Pretext {
runPretext :: forall f. Functor f => (s -> f s) -> f a
} deriving Functor

experiment :: Functor f => (s -> f s) -> Pretext s a -> f a
experiment f (Pretext k) = k f
-- /show
main = putStrLn "It typechecks, so it must be correct!"``````

Defining the `Comonad` instance for that type is a particularly enlightening challenge.

If you replace the `Functor` constraint in the definition above with `Applicative` you get a `Comonad` I call the `Bazaar`. This `Comonad` is used to derive many of the most brain-bending `Traversal` and `uniplate`-derived combinators in `lens`.

The code for its `Comonad` instance is identical to the instance for `Pretext` above, but it can also be made `Applicative`.

``````{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE RankNTypes #-}
import Control.Applicative
-- show
newtype Bazaar s a = Bazaar {
runBazaar :: forall f. Applicative f => (s -> f s) -> f a
} deriving Functor
-- /show
main = putStrLn "It typechecks, so it must be correct!"``````

If you try to search for the `Store`-like analogue to the `Bazaar`, you wind you looking at what Twan van Laarhoven called a `FunList` in "A non-regular data type challenge".

``````{-# LANGUAGE DeriveFunctor #-}
import Control.Applicative
-- show
data FunList s a
= Done a
| More s (FunList s (s -> a))
deriving Functor
-- /show
main = putStrLn "It typechecks, so it must be correct!"``````

An interesting exercise is to derive the `Applicative` and `Comonad` instances for `FunList`. This exercise is much easier than the `Pretext` and `Bazaar` derivations, but still quite challenging.

Surprisingly `FunList` is actually a less powerful type than `Bazaar` in the presence of infinite traversals as many tools you can build will not terminate when you manipulate an infinite traversal with them built using a `FunList`, but will terminate when they are constructed using the `Bazaar`!

# Following the Rules

Stephen Wolfram described a rather concise encoding of 2-color automata that can only look at their neighbors in "A New Kind of Science".

We can encode his family of 2-color rules as a comonadic action:

``````rule :: Num s => Word8 -> Store s Bool -> Bool
rule w (Store f s) = testBit w \$
0 & partsOf (taking 3 bits) .~ [f (s+1), f s, f (s-1)]``````

That is rather dense, so let's unpack it.

Wolfram numbers his rules from 0 to 255 because if you look at the current cell and the neighbor to the left and right of it, we have 3 inputs to consider. Each is a `Bool` so we have 2^3 different results to give. If we bundle all those possible results together as the bits of a `Word8`, the `Word8` perfectly describes all of the possible 2-color cellular automata that can look at the current and neighboring cells.

So now the trick is doing that indexing. To do so, first we need to figure out which bit in our `Word8` we are interested in. To do that we need to use the 3 booleans we obtain by tweaking our position and asking to perform our "experiment" there at the slightly modified positions instead.

Now we want to compose 3 bits together into an `Int`. We could do this with a bunch of conditional logic, etc. but there is a slightly cute encoding we can get when we use `lens`.

`bits` provides a `Traversal` of the individual bits of any instance of `Bits`. (In the case of `Integer`, though, because it is infinite sadly the `Traversal` can never finish reassembling the `Integer`, and so it devolves to merely a `Fold`.

`taking n t` takes a `Traversal t` and yields a `Traversal` that only touches the first `n` targets of the original `Traversal`.

Therefore `taking 3 bits` is the `Traversal` of the first 3 bits of a number.

`partsOf` takes a `Traversal` and gives you a (slightly hinky) `Lens` to a list of all of the targets of the traversal. You can freely replace that list with a new list (of the same length!). It is only a law abiding `Lens` if you do not change the length of the list of targets, but even if you violate these assumptions it is well behaved operationally. In fact you can safely remove `taking n` from the definition of rule above, and its semantics do not change.

And finally, we can use the fact that every `Lens` is a valid `Setter` to make the assignment.

``0 & partsOf (taking 3 bits) .~ [f (s+1), f s, f (s-1)]``

then builds an `Int` n between 0 and 7 by starting with a 0 and setting its first 3 bits accordingly.

With that in hand we can now test the nth bit of the rule number and obtain our result.

Since `Store s` forms a `Comonad` though, we can `extend` our `rule n` to obtain a new `Store s Bool` from out existing `Store s Bool`.

Now if we, say, `extend (rule 110)` we get a function from one world to a new world, where that rule has been applied uniformly across the entire world at the same time.

``extend (rule 110) :: Num s => Store s Bool -> Store s Bool``

By choosing an appropriate number type for `s` we can choose the topology for our automaton to live on!

We could repeatedly run our rules with

``````slowLoop :: (Store s a -> a) -> Store s a -> [Store s a]
slowLoop f = iterate (extend f)``````

# Got the Memo?

...but we'd get explosive slowdown. Why?

After a each loop iteration we depend on 3x as many evaluations as we did for the iteration before, because each evaluation is asking for all of the other old versions of the old neighbors, etc.

So the trick is to memoize our function. The easiest way to do that without reasoning about `IO` is to use a memo combinator package like `data-memocombinators` or my own `representable-tries`. I'll buck my trend and use Luke's package instead of mine.

But which function?

We don't want to memoize the comonad algebra itself. The argument to that is of type `Store s a`, and memoizing function spaces of function spaces gets truly messy. Let's make a function that turns a value in our `Store` comonad into one that memoizes its answers by memoizing the experiment it contains.

``````tab :: Memo s -> Store s a -> Store s a
tab opt (Store f s) = Store (opt f) s``````

`tab` takes a way to memoize a function from the context of our `Store` and a `Store` and yields a new `Store` that memoizes its results.

`Memo` comes from `data-memocombinators`.

``type Memo a = forall r. (a -> r) -> a -> r``

A value of type `Memo a` describes a memoization strategy for functions from values of type `a`. It takes a function and turns it into a function that memoizes its results. It does so in a completely pure way that is worth exploring in its own right, but...

If we just use the fact that `integral` provides us with such a memoization strategy that works for any `Integral` type, we can derive a smarter `loop`!

``````loop :: Integral s => (Store s a -> a) -> Store s a -> [Store s a]
loop f = iterate (extend f . tab integral)``````

Here when we are given a new `Store` before each iteration we simply upgrade it to memoize its results for each position as it is asked before handing it to our rule for further evaluation.

# Let's Do the Time Warp Again

Now let's timewarp back to the stone age and print out endless reams of paper filled with automaton states.

To do that we need a way to see what some slice of our world looks like:

``````-- TODO: copy the whole program below here
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE DeriveFunctor #-}

import Control.Lens as L
import Data.Bits
import Data.Bits.Lens as L
import qualified Data.ByteString as Strict
import qualified Data.ByteString.Lazy as Lazy
import Data.MemoCombinators
import Data.Word
import Diagrams.Backend.SVG
import Diagrams.Prelude as D
import Text.Blaze.Svg.Renderer.Utf8
import Yesod

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

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

experiment :: Functor f => (s -> f s) -> Store s a -> f a
experiment k (Store f s) = f <\$> k s

rule :: Num s => Word8 -> Store s Bool -> Bool
rule w (Store f s) = testBit w \$
0 L.& partsOf (taking 3 L.bits) .~ [f (s+1), f s, f (s-1)]

tab :: Memo s -> Store s a -> Store s a
tab opt (Store f s) = Store (opt f) s

loop :: Integral s => (Store s a -> a) -> Store s a -> [Store s a]
loop f = iterate (extend f . tab integral)

-- show
window :: (Enum s, Num s) => s -> s -> Store s a -> [a]
window l h = experiment \$ \ s -> [s-l..s+h]

xo :: Bool -> Char
xo True  = 'X'
xo False = ' '

main = mapM_ (putStrLn . map xo . window 50 0) \$
take 50 \$ loop (rule 110) \$ Store (==0) 0
-- /show``````

I probably should have told that thing stop printing a little sooner. Sorry. ;)

`window` varies our position on the number line up or down a bit so we can see several data points.

`xo` converts each result into a form we might want to see.

Then we put it all together and run Wolfram's rule 110 starting with a single point at position 0 as our initial condition.

# Pretty as a Picture

It isn't the stone age any more.

Matt Sottile did a pretty looking forest fire cellular automata example a couple of years back. But he had to render everything by hand using OpenGL.

Nowadays we can draw pretty pictures using Brent Yorgey's awesome `diagrams` package rather than carve ASCII `X`'s into the walls of our cave.

Now that we have the windows of data we want, all we need to do is turn each `window` into a a bunch of squares and stitch those rows together into a `Diagram`.

``````grid :: [[Bool]] -> Diagram SVG R2
grid = vcat . map (hcat . cell) where
cell b = unitSquare D.# fc (if b then black else white)``````

This post was spawned from a discussion with Rein Henrichs on #haskell. He supplied the initial version of the `diagrams` code. His version was much prettier.

`diagrams` supports rendering to a ton of formats including SVG, so we can transform our diagram into a document using `diagrams-svg` and `blaze-svg`. We could also render it directly to `cairo` and get out a PNG, get out an HTML canvas, a postscript document, etc.

We could use the `renderSVG` function to generate a file on disk, but it also isn't the 80s. Command line tools that spit out files are passé. So lets just get our hands on the file here in memory as a `ByteString` and make sure it's strict to deal with the impedence mismatch between the tools I'm using.

``````svg :: Diagram SVG R2 -> Strict.ByteString
svg = Strict.concat . Lazy.toChunks .
renderSvg . renderDia SVG (SVGOptions (Width 400) Nothing)``````

# But is it Web Scale?

The School of Haskell supports running full-fledged web-based applications from an "active" Haskell snippet, so lets give it a try.

If we put them these pieces of code together you should be able to click run below and get out pretty pictures out of a custom web server that all but fits on your screen.

Click Run!

``````{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE DeriveFunctor #-}

import Control.Lens as L
import Data.Bits
import Data.Bits.Lens as L
import qualified Data.ByteString as Strict
import qualified Data.ByteString.Lazy as Lazy
import Data.MemoCombinators
import Data.Word
import Diagrams.Backend.SVG
import Diagrams.Prelude as D
import Text.Blaze.Svg.Renderer.Utf8
import Yesod

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

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

experiment :: Functor f => (s -> f s) -> Store s a -> f a
experiment k (Store f s) = f <\$> k s

rule :: Num s => Word8 -> Store s Bool -> Bool
rule w (Store f s) = testBit w \$ 0 L.& partsOf (taking 3 L.bits) .~ [f (s+1), f s, f (s-1)]

tab :: Memo s -> Store s a -> Store s a
tab opt (Store f s) = Store (opt f) s

loop :: Integral s => (Store s a -> a) -> Store s a -> [Store s a]
loop f = iterate (extend f . tab integral)

window :: (Enum s, Num s) => s -> s -> Store s a -> [a]
window l h = experiment \$ \ s -> [s-l..s+h]

grid :: [[Bool]] -> Diagram SVG R2
grid = cat unitY . reverse . map (hcat . map cell) where
cell b = unitSquare D.# fc (if b then black else white)

svg :: Diagram SVG R2 -> Strict.ByteString
svg = Strict.concat . Lazy.toChunks . renderSvg . renderDia SVG (SVGOptions (Width 400) Nothing)

data App = App

instance Yesod App

mkYesod "App" [parseRoutes| / ImageR GET |]

getImageR :: MonadHandler m => m TypedContent
getImageR = sendResponse \$ toTypedContent (typeSvg, toContent img)

img = svg . grid . map (window 49 0) . take 50 . loop (rule 110) \$ Store (==0) (0 :: Int)

main = warpEnv App``````

That clocks in at 60 lines of code. In that much space we defined the `Store` comonad, defined a generic evaluator that can handle any of Wolfram's 2-color automata, built a system of memoization to avoid asymptotic slowdown, took a cross section of our universe, and then rendered it to a diagram and built a custom web server to display that content here on the internet.

Almost all of the components we built are generic. We can define new types of automata, try out new initial conditions, jump around in time, with some work we can support multiple colors, new topologies, render the same diagram to different file formats conditionally based on browser preferences. The code above can be edited live here in your browser or downloaded and run locally on your own machine.

In the interest of full disclosure, the SVG that is rendered is far from optimal. The `diagrams` crew is aware of the issue and they are hard at work improving the way `diagrams` streams primitives to its backends, allowing it to take advantage of all the glorious structure that they have inside the `Diagram` type described in Brent's excellent functional pearl. Currently the communication process between `diagrams` and the backend is duplicating the transformation matrix and styling on a per element basis, and this is resulting in a much inflated document. When those changes go into `diagrams` and `diagrams-svg`, then this example will become much faster with no changes to this code.

I hope this shows how you can use a little bit of theory and some of the more practical components of the Haskell ecosystem to accomplish a lot with very little code.

-- Edward Kmett August 15, 2013