DISCLAIMER: I don’t know Haskell at all. I know the code in this article is not idiomatic and I look forward to receiving constructive criticism.
In part 1 of this series I began to implement the game of life cellular automaton in C# using the functional programming style (at least as I know it). Part 1 defined the data model and implemented a function to render the state of the system to HTML.
For this part 2 of the series I port my C# solution to Haskell.
Firstly, the data structure. As with the C# version I model the game of life as a list of living cells. The definition of a cell
is:
data Cell = Cell {
x :: Int,
y :: Int
} deriving (Show)
where x and y are functions that return the cartesian coordinates of the cell. It is the same as the C# version, except that this Cell
type is immutable.
Having defined my Cell
type I then defined a set of functions to do useful things with a Cell
or a list of cells. renderLocation
is a function that converts a point (xv,yv) and a boolean (indicating if the cell at that location is living) into its HTML string.
renderLocation :: Int -> Int -> Bool -> String
renderLocation xv yv on = printf
"<div class=\"cell %s\" style=\"left: %dpx; top: %dpx;\"> </div>\n"
(if on then "on" else "") (20 * (xv+1)) (20 * (yv+1))
The first line is a type definition for the function. Its C# equivalent would be Func<int,int,bool,string>
.
To make renderLocation
useful I need a function to determine if a given location contains a living cell:
isOn :: [Cell] -> Int -> Int -> Bool
isOn cs xv yv = any cellMatch cs
where cellMatch c = x c == xv && y c == yv
This roughly translates to the C# cs.Any(c => x(c) == xv && y(c) == yv)
.
The other helper functions required calculate the largest X and Y values to determine the size of the system:
largestX :: [Cell] -> Int
largestX cs = maximum $ map x cs
largestY :: [Cell] -> Int
largestY cs = maximum $ map y cs
largestX
translated to C# is:
public int largestX(Cell[] cs) {
return cs.Select(c => x(c)).Max();
}
Next I created three living cells, at coordinates (1,2), (3,5) and (14,13):
cells = [Cell 1 2, Cell 3 5, Cell 14 13]
Now I can create a render
function that produces the HTML representation of a system:
render :: [Cell] -> String
render cs = concat [renderLocation x y (isOn cs x y) | x <- [0..xBound], y <- [0..yBound]]
where xBound = largestX cs
yBound = largestY cs
The expression [renderLocation x y (isOn cs x y) | x <- [0..xBound], y <- [0..yBound]]
is a list comprehension. The part after the pipe x <- [0..xBound], y <- [0..yBound]
produces the cartesian product of all possible x values ([0..xBound]
) and all possible y values ([0..yBound]
). For each point the renderLocation
function is called. The results are collected into a list of strings which is then flattened to a single string by the concat
function.
This is equivalent to the C# version:
var results = from x in Enumerable.Range(0, largestX+1)
from y in Enumerable.Range(0, largestY+1)
select buildCell(x, y, IsOn(cells, x, y));
Now to get the program to actually do something I define a list of cells and then use the programs main
function to render my list of cells and write the result to stdout.
cells = [Cell 1 2, Cell 3 5, Cell 14 13]
main = (putStrLn . render) cells
The output of this program is:
Here is the full program:
import Text.Printf
import Data.List
data Cell = Cell {
x :: Int,
y :: Int
} deriving (Show)
renderLocation :: Int -> Int -> Bool -> String
renderLocation xv yv on = printf
"<div class=\"cell %s\" style=\"left: %dpx; top: %dpx;\"> </div>\n"
(if on then "on" else "") (20 * (xv+1)) (20 * (yv+1))
isOn :: [Cell] -> Int -> Int -> Bool
isOn cs xv yv = any cellMatch cs
where cellMatch c = x c == xv && y c == yv
largestX :: [Cell] -> Int
largestX cs = maximum $ map x cs
largestY :: [Cell] -> Int
largestY cs = maximum $ map y cs
render :: [Cell] -> String
render cs = concat [renderLocation x y (isOn cs x y) | x <- [0..xBound], y <- [0..yBound]]
where xBound = largestX cs
yBound = largestY cs
--cells = [Cell 1 2, Cell 3 5, Cell 14 13]
--new Cell(1,0), new Cell(0,1), new Cell(2,1), new Cell(7,16)
cells = [Cell 1 0, Cell 0 1, Cell 2 1, Cell 7 16]
main = (putStrLn . render) cells
It is a bit longer than the C# version, but that is because I have used more explicit functions and specified more explicit types.