Eli Jordan

http://eli-jordan.github.io

I am... a Scala Developer

I am not... a Theorist

I am not... an expert in Category Theory

I am... a Functional Programmer

# Game Plan

Zipper as a Metaphor for Comonads

Conway's Game of Life

``````
def unit[A](a: A): F[A]
def join[A](ffa: F[F[A]]): F[A]
}

def counit[A](fa: F[A]): A
def cojoin[A](fa: F[A]): F[F[A]]
}``````

``````
unit  :   A  => F[A]
counit: F[A] => A

join  : F[F[A]] => F[A]
cojoin:    F[A] => F[F[A]]``````

## unit: Apple => Box[Apple]

Takes an apple and puts it in a box

## counit: Box[Apple] => Apple

Takes an apple in a box, and extracts the apple.

## join: Box[Box[Apple]] => Box[Apple]

Takes an apple in a box, in a box, and throws out one of the boxes.

### cojoin: Box[Apple] => Box[Box[Apple]]

Takes an apple in a box, and puts it in another box.

## unit / counit

In a Monad the unit function takes a pure value, and wraps it in an F structure F[A]

In a Comonad the counit function takes an F structure and extracts a pure value A

## join / cojoin

In a Monad the join function takes two layers of F structure F[F[A]] and collapses it into one layer F[A]

In a Comonad the cojoin function takes one layer of F structure F[A] and duplicates it F[F[A]]

# The Code

``````case class StreamZipper[A](
left: Stream[A], focus: A, right: Stream[A]) {

def moveLeft: StreamZipper[A] =
new StreamZipper[A](

def moveRight: StreamZipper[A] =
new StreamZipper[A](
}``````

## Whats that got to do with comonads?

• counit extracts an A from an F[A]

• If F == StreamZipper, this is trivial
we just access the focus element.
``````

def counit[A](fa: StreamZipper[A]): A =
fa.focus

// ...
}``````

## What about the other half a.k.a cojoin?

• We need to generate a StreamZipper[StreamZipper[A]]

• The key insight required to define cojoin, is that we want to
• Duplicate the original zipper, but  with the focus shifted.
• There should be a duplicate with the focus set on every element in the original zipper.
``````case class StreamZipper[A](
left: Stream[A], focus: A, right: Stream[A]) {
// ...

// A stream of zippers, with the focus set to each
// element on the left
private lazy val lefts: Stream[StreamZipper[A]] =
Stream.iterate(this)(_.moveLeft)
.tail.zip(left).map(_._1)

// A stream of zippers, with the focus set to each
// element on the right
private lazy val rights: Stream[StreamZipper[A]] =
Stream.iterate(this)(_.moveRight)
.tail.zip(right).map(_._1)

lazy val cojoin: StreamZipper[StreamZipper[A]] =
new StreamZipper[StreamZipper[A]](lefts, this, rights)
}``````
``````

def counit[A](fa: StreamZipper[A]): A =
fa.focus

def cojoin[A](fa: StreamZipper[A]):
StreamZipper[StreamZipper[A]] = fa.cojoin
}``````

Now, the comonad instance is trivial

# Recap

• It has two operations counit and cojoin

• A zipper is an example of a Comonad

• But, Monads also have the flatMap operation

• How do we define coflatMap?

## We Derive It

But, lets first look at our definitions again

``````trait Monad[F[_]] {
def unit[A](a: A): F[A]
def join[A](ffa: F[F[A]]): F[A]

def flatMap[A, B](fa: F[A])(f: A => F[B])
(implicit F: Functor[F]): F[B] = ???
}``````
``````trait Comonad[F[_]] {
def counit[A](fa: F[A]): A
def cojoin[A](fa: F[A]): F[F[A]]

def coflatMap[A, B](fa: F[A])(f: F[A] => B)
(implicit F: Functor[F]): F[B] = ???
}``````
``````trait Monad[F[_]] {
def unit[A](a: A): F[A]
def join[A](ffa: F[F[A]]): F[A]

def flatMap[A, B](fa: F[A])(f: A => F[B])(...): F[B] =
join(fa.map(f))
}``````
``````trait Comonad[F[_]] {
def counit[A](fa: F[A]): A
def cojoin[A](fa: F[A]): F[F[A]]

def coflatMap[A, B](fa: F[A])(f: F[A] => B)(...): F[B] =
cojoin(fa).map(f)
}``````

## A Mental Model of coflatMap

• The function passed to coflatMap can be thought of as a local computation.

• We first cojoin, to get a view of the structure from all perspectives.

• We then use map to apply a local computation from every perspective.

## cojoin(                ) =

cojoin(fa).map(f)

cojoin(fa).map(f)

f: StreamZipper[Int] => Int

# Sliding Average

``````def avg(a: StreamZipper[Int]): Double = {
val left = a.moveLeft.focus
val current = a.focus
val right = a.moveRight.focus
(left + current + right) / 3d
}

// Note: The StreamZipper constructor reverses
//       the first list.
StreamZipper(List(1, 2, 3), 4, List(5, 6, 7))
.coflatMap(avg).toList

// List(1.33.., 2.0, 3.0, 4.0, 5.0, 6.0, 6.66..)``````

# What is it?

• A two-dimensional grid of evolving cells that are alive or dead.

• At each evolution, the next state of a cell is determined by the state of its neighbours.

# The Rules

1. Current = alive and < 2 neighbours alive       => dead (underpopulation)

2. Current = alive and 2 or 3 neighbours alive  => alive

3. Current = alive and > 3 neighbours alive       => dead  (over population)

4. Current = dead  and 3 neighbours alive         => alive (reproduction)

## Modelling The Solution

• The next state of a cell is determined by its local neighbourhood

• The rules need to be extended from a local definition to apply globally.

• Does that remind you of coflatMap? (it should!)

• We will use this alignment, and an extension of the StreamZipper to implement the Game Of Life
``````case class Grid[A](
value: StreamZipper[StreamZipper[A]]) {

def moveUp: Grid[A] =
Grid(value.moveLeft)

def moveDown: Grid[A] =
Grid(value.moveRight)

def moveLeft: Grid[A] =
Grid(value.map(_.moveLeft))

def moveRight: Grid[A] =
Grid(value.map(_.moveRight))

def counit: A =
value.counit.counit

def cojoin: Grid[Grid[A]] = ??? // TODO
}``````

### Extending The Zipper into 2 Dimensions

``````case class Grid[A](
value: StreamZipper[StreamZipper[A]]) {

def cojoin: Grid[Grid[A]] =
Grid(layer(layer(value))).map(Grid.apply)

// Notice that the implementation is very similar to
// what we had in our original zipper except that we
// need to make use of the 'map' function in the iteration.
private def layer[X](u: StreamZipper[StreamZipper[X]]):
StreamZipper[StreamZipper[StreamZipper[X]]] = {

val lefts = Stream.iterate(u)(ssx => ssx.map(_.moveLeft))
.tail.zip(u.left).map(_._1)

val rights = Stream.iterate(u)(ssx => ssx.map(_.moveRight))
.tail.zip(u.right).map(_._1)

StreamZipper(lefts, u, rights)
}
}``````

Using the same derivation of coflatMap we get

``````case class Grid[A](...) {
// ...
def coflatMap[B](f: Grid[A] => B): Grid[B]
}``````

The function f is what we need to implement.
It will define the rules of the game.

``def conway(grid: Grid[Boolean]): Boolean = ???``
``````def conway(grid: Grid[Boolean]): Boolean = {
val liveCount = neighbours(grid).count(identity)

grid.counit match {
// under population
case true if liveCount < 2 => false
// thriving
case true if liveCount == 2 || liveCount == 3 => true
// over population
case true if liveCount > 3 => false
// reproduction
case false if liveCount == 3 => true
}
}``````

Now, translating the rules is very simple...

``````def neighbours[A](grid: Grid[A]): List[A] =
List(
grid.moveUp,
grid.moveDown,
grid.moveLeft,
grid.moveRight,
grid.moveUp.moveLeft,
grid.moveUp.moveRight,
grid.moveDown.moveLeft,
grid.moveDown.moveRight
).map(_.counit)``````

For completeness

# Run Our Game!

## Review

• We explored the definition of a Comonad

• Provided an intuition for Comonads, using the Zipper

• Demonstrated the use of a Comonad to solve a programming problem, but implementing
Conway's Game Of Life

# Appendix

``````// Left identity