• Stars
    star
    153
  • Rank 243,368 (Top 5 %)
  • Language
    Haskell
  • License
    MIT License
  • Created almost 11 years ago
  • Updated over 3 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A library for expressing "spreadsheet-like" computations with absolute and relative references, using fixed-points of n-dimensional comonads.

ComonadSheet

A library for expressing "spreadsheet-like" computations with absolute and relative references, using fixed-points of n-dimensional comonads. A sheet is an n-dimensionally nested Tape, which is a stream infinite in both left and right directions, with a focus element. For instance, type Sheet1 a = Nested (Flat Tape) a, which is isomorphic to Tape a. Nested Tapes describe multi-dimensional grid-like spaces, which I will refer to, rather leadingly, as sheets made up of cells.

While a conventional spreadsheet combines the construction and evaluation of a space of formulae into one process for the user, these steps are distinct in the ComonadSheet library. To create a self-referencing spreadsheet-like computation, first construct a multi-dimensional space of functions which take as input a space of values and return a single value. Then, take its fixed point using the evaluate function, resulting in a space of values. A type speaks more than a thousand words:

evaluate :: (ComonadApply w) => w (w a -> a) -> w a

But if you want a thousand words, you can read the documentation (below and in the source), or listen to me talk:

Installation

$ cabal update
$ cabal install ComonadSheet

Creating Sheets

Usually, the best way to create a sheet is using the sheet function, or using the pure method of the Applicative interface. The sheet function takes a default element value, and a structure containing more values, and inserts those values into a space initially filled with the default value. For instance, sheet 0 [[1]] :: Sheet2 Int makes a two-dimensional sheet which is 0 everywhere except the focus, which is 1. Note that because of overloading on sheet's operands, it is usually necessary to give a type signature somewhere. This is generally not a problem because GHC can almost always infer the type you wanted if you give it so much as a top-level signature.

References and Manipulation

References to sheets are represented as quasi-heterogeneous lists of absolute and relative references. (In the Names module, I've provided names for referring to dimensions up to 4.) A reference which talks about some dimension n can be used to refer to that same relative or absolute location in any sheet of dimension n or greater.

For instance, rightBy 5 is a relative reference in the first dimension. If I let x = sheet 0 [1..] :: Sheet1 Int, then extract (go (rightBy 5) x) == 6. Notice that I used the extract method from the sheet's Comonad instance to pull out the focus element. Another way to express the same thing would be to say cell (rightBy 5) x -- the cell function is the composition of extract and go. In addition to moving around in sheets, I can use references to slice out pieces of them. For instance, take (rightBy 5) x == [1,2,3,4,5,6]. (Note that references used in extracting ranges are treated as inclusive.) I can also use a reference to point in a direction and extract an infinite stream (or stream- of-stream-of- streams...) pointed in that direction. For instance, view right x == [1..].

References can be relative or absolute. An absolute reference can only be used to refer to an Indexed sheet, as this is the only kind of sheet with a notion of absolute position.

References can be combined using the (&) operator. For example, columnAt 5 & aboveBy 10 represents a reference to a location above the current focus position by 10 cells, and at column 5, regardless of the current column position. Relative references may be combined with one another, and absolute and relative references may be combined, but combining two absolute references is a type error.

Examples

The environment I'll be using as a demo-space looks like:

import Control.Comonad.Sheet
import Data.Stream ( Stream , repeat , (<:>) )

import Control.Applicative ( (<$>), (<*>) )
import Data.List ( intersperse )
import Data.Bool ( bool )

import qualified Prelude as P
import Prelude hiding ( repeat , take )

Iterated Numbers

A one-dimensional sheet which is zero left of the origin and lists the natural numbers right of the origin:

naturals :: Sheet1 Integer
naturals = evaluate $ sheet 0 (repeat (cell left + 1))

When we print this out...

> take (rightBy 10) naturals
[1,2,3,4,5,6,7,8,9,10,11]

Pascal's Triangle

An infinite spreadsheet listing the rows of Pascal's triangle as upwards-rightwards diagonals:

pascal :: Sheet2 Integer
pascal = evaluate . sheet 0 $
   repeat 1 <:> repeat (1 <:> pascalRow)
   where pascalRow = repeat $ cell above + cell left

Notice the fact that I'm using the (+) function to add functions (namely, cell above and cell left). This is thanks to some clever overloading from Data.Numeric.Function.

This looks like:

> take (belowBy 9 & rightBy 9) pascal
[[1,  1,  1,   1,   1,    1,    1,     1,     1,     1], 
 [1,  2,  3,   4,   5,    6,    7,     8,     9,    10], 
 [1,  3,  6,  10,  15,   21,   28,    36,    45,    55], 
 [1,  4, 10,  20,  35,   56,   84,   120,   165,   220], 
 [1,  5, 15,  35,  70,  126,  210,   330,   495,   715], 
 [1,  6, 21,  56, 126,  252,  462,   792,  1287,  2002], 
 [1,  7, 28,  84, 210,  462,  924,  1716,  3003,  5005], 
 [1,  8, 36, 120, 330,  792, 1716,  3432,  6435, 11440], 
 [1,  9, 45, 165, 495, 1287, 3003,  6435, 12870, 24310], 
 [1, 10, 55, 220, 715, 2002, 5005, 11440, 24310, 48620]]

We can also traverse it to find the rows of Pascal's triangle, by defining a function to diagonalize an infinite space:

diagonalize :: Sheet2 a -> [[a]]
diagonalize = 
   zipWith P.take [1..]
   . map (map extract . P.iterate (go (above & right)))
   . P.iterate (go below)

On Pascal's triangle, this results in:

> P.take 15 (diagonalize pascal)
[[1],
 [1,  1], 
 [1,  2,  1], 
 [1,  3,  3,   1], 
 [1,  4,  6,   4,    1], 
 [1,  5, 10,  10,    5,    1], 
 [1,  6, 15,  20,   15,    6,    1], 
 [1,  7, 21,  35,   35,   21,    7,    1], 
 [1,  8, 28,  56,   70,   56,   28,    8,    1], 
 [1,  9, 36,  84,  126,  126,   84,   36,    9,    1], 
 [1, 10, 45, 120,  210,  252,  210,  120,   45,   10,    1], 
 [1, 11, 55, 165,  330,  462,  462,  330,  165,   55,   11,   1], 
 [1, 12, 66, 220,  495,  792,  924,  792,  495,  220,   66,  12,  1], 
 [1, 13, 78, 286,  715, 1287, 1716, 1716, 1287,  715,  286,  78, 13,  1], 
 [1, 14, 91, 364, 1001, 2002, 3003, 3432, 3003, 2002, 1001, 364, 91, 14, 1]]

Fibonacci-like Sequences

We can define a three-dimensional space enumerating all the Fibonacci-like sequences starting from positive seed numbers a and b, and subsequent terms equal to the sum of the two previous terms. (The normal Fibonacci sequence can be recovered with seeds a = 1, b = 1.)

This example is thanks to an enlightening conversation with Eden Zik.

fibLike :: Sheet3 Integer
fibLike = evaluate $ sheet 0 $
   fibSheetFrom 1 1 <:> repeat (fibSheetFrom (cell inward + 1) (cell inward))
   where fibSheetFrom a b = (a          <:> b                <:> fibRow) <:>
                     repeat (cell above <:> (1 + cell above) <:> fibRow)
         fibRow = repeat $ cell (leftBy 1) + cell (leftBy 2)

Examining a slice of this space, we find the following:

> take (rightBy 4 & belowBy 4 & outwardBy 2) fibLike
[[[1,1,2, 3, 5], -- the original Fibonacci sequence
  [1,2,3, 5, 8],
  [1,3,4, 7,11],
  [1,4,5, 9,14],
  [1,5,6,11,17]],
 [[2,1,3, 4, 7],
  [2,2,4, 6,10], -- double the Fibonacci sequence
  [2,3,5, 8,13],
  [2,4,6,10,16],
  [2,5,7,12,19]],
 [[3,1,4, 5, 9], -- a curious coincidence with the opening digits of pi
  [3,2,5, 7,12],
  [3,3,6, 9,15], -- triple the Fibonacci sequence
  [3,4,7,11,18],
  [3,5,8,13,21]]]

Conway's Game of Life

Of course, as this is a comonadic library, we're obligated to implement the canonical nontrivial comonadic computation: Conway's Game of Life.

For convenience, we define a few types:

data Cell = X | O deriving ( Eq , Show )
type Universe = Sheet3 Cell
type Ruleset = ([Int],[Int]) -- list of numbers of neighbors to trigger
                             -- being born, and staying alive, respectively

Then we can define a function which takes a starting configuration (seed) for the Game of Life, and inserts it into the infinite universe of Game-of-Life cells.

Here, we represent the evolution of an instance of the game of life as a three-dimensional space where two axes are space, and the third is time.

In the Conway space, all cells before time zero are always dead cells, and all cells starting at time zero are equal to the Life rule applied to their neighboring cells in the previous time frame. To instantiate a timeline for a seed pattern, it is inserted as a series of constant cells into time frame zero of the blank Conway space. Then, the Conway space is evaluated, resulting in an infinite 3D space showing the evolution of the pattern.

life :: Ruleset -> [[Cell]] -> Universe
life ruleset seed = evaluate $ insert [map (map const) seed] blank
   where blank = sheet (const X) (repeat . tapeOf . tapeOf $ rule)
         rule place  = case (neighbors place `elem`) `onBoth` ruleset of
                            (True,_)  -> O
                            (_,True)  -> cell inward place
                            _         -> X
         neighbors   = length . filter (O ==) . cells bordering
         bordering   = map (inward &) (diagonals ++ verticals ++ horizontals)
         diagonals   = (&) <$> horizontals <*> verticals
         verticals   =        [above, below]
         horizontals = map d2 [right, left]

onBoth :: (a -> b) -> (a,a) -> (b,b)
f `onBoth` (x,y) = (f x,f y)

conway :: [[Cell]] -> Universe
conway = life ([3],[2,3])

For aesthetics, we can define a printer function for generations of the game of life. Note that the printer function is more or less as long as the definition of the real computation!

printLife :: Int -> Int -> Int -> Universe -> IO ()
printLife c r t = mapM_ putStr
   .            ([separator '' '' ''] ++)
   .         (++ [separator '' '' '']) 
   . intersperse (separator '' '' '')
   . map (unlines . map (("" ++) . (++ "")) . frame)
   . take (rightBy c & belowBy r & outwardBy t)
   where
      separator x y z = [x] ++ P.replicate (1 + (1 + c) * 2) y ++ [z] ++ "\n"
      frame = map $ intersperse ' ' . map (bool ' ' '' . (O ==))

Here's how we define a universe containing only a single glider:

glider :: Universe
glider = conway [[X,X,O],
                 [O,X,O],
                 [X,O,O]]

And it works!

> printLife 3 3 4 glider
┌─────────┐
│     ●   │
│ ●   ●   │
│   ● ●   │
│         │
├─────────┤
│   ●     │
│     ● ● │
│   ● ●   │
│         │
├─────────┤
│     ●   │
│       ● │
│   ● ● ● │
│         │
├─────────┤
│         │
│   ●   ● │
│     ● ● │
│     ●   │
├─────────┤
│         │
│       ● │
│   ●   ● │
│     ● ● │
└─────────┘

Here's a Lightweight Spaceship:

spaceship :: Universe
spaceship = conway [[X,X,X,X,X],
                    [X,O,O,O,O],
                    [O,X,X,X,O],
                    [X,X,X,X,O],
                    [O,X,X,O,X]]

When we run it...

> printLife 6 4 4 spaceship
┌───────────────┐
│               │
│   ● ● ● ●     │
│ ●       ●     │
│         ●     │
│ ●     ●       │
├───────────────┤
│     ● ●       │
│   ● ● ● ●     │
│   ● ●   ● ●   │
│       ● ●     │
│               │
├───────────────┤
│   ●     ●     │
│           ●   │
│   ●       ●   │
│     ● ● ● ●   │
│               │
├───────────────┤
│               │
│         ● ●   │
│     ● ●   ● ● │
│     ● ● ● ●   │
│       ● ●     │
├───────────────┤
│               │
│       ● ● ● ● │
│     ●       ● │
│             ● │
│     ●     ●   │
└───────────────┘

More Repositories

1

myxine

Get a GUI fast in any language under the sea!
Haskell
47
star
2

GQFC

Source material for "Functional Pearl: Getting a Quick Fix on Comonads"
Haskell
44
star
3

StrictCheck

Keep your laziness in check!
Haskell
32
star
4

hopscotch

A FIFO queue for efficiently hopping and skipping between tagged items
Rust
9
star
5

Presburger

Implementation of Presburger arithmetic solver
Haskell
8
star
6

Pronunciations

Parser for the CMU pronouncing dictionary and simple homophone REPL
Haskell
5
star
7

Euler14-Optimization

Progressively optimized versions (in Haskell) of a solution to Problem 14 from Project Euler.
Haskell
5
star
8

data-kiln

Sculpt mutable recursive data with reference equality; bake it using a data kiln into an immutable lazy structure
Haskell
3
star
9

NestedFunctor

Nested composition of functors with a type index tracking nesting.
Haskell
3
star
10

linkaj

Persistent associative datatypes with more features than Clojure's built-in maps, including bijective maps, key-attribute-value maps, bipartite graphs, and directed graphs with arbitrary node and edge attributes.
Clojure
3
star
11

GCBP

Gordon Complementary Bijection Principle
TeX
3
star
12

no-exit

Source for guest lecture on existential types and purely functional data structures at Hendrix College, April 7, 2016
Haskell
3
star
13

Tape

Bidirectionally infinite streams, akin to the tape of a Turing machine.
Haskell
2
star
14

text-diagram

Haskell
2
star
15

Catalan-Isomorphisms

Experiments with type-safe encoding and translation of various mathematical objects which are counted by the Catalan numbers, such as Dyck paths, ordered trees, binary trees, and others.
Haskell
2
star
16

Systematic

Teaching network programming & distributed systems, with lots of types
Haskell
2
star
17

ProseDiff

Work in progress on a semantically-aware diff tool for natural-language (e.g. English) prose.
1
star
18

Infinitable

Library providing a datatype and instances for possibly-infinite numbers, designed to wrap any unbounded numeric type.
Haskell
1
star
19

PeanoWitnesses

GADT-style type witnesses for the Peano naturals. Useful in dependent-types-ish kind of situations.
Haskell
1
star
20

cogoldrust-rustbridge-2019

Lecture notes and exercises for the RustBridge workshop from CO Gold Rust conference 2019
Rust
1
star
21

TABA-AWHA

Slides and such from my talk at Compose '16: "There and Back Again" and What Happened After
Haskell
1
star
22

binomial-heaps

Binomial heaps with GADT-enforced shape constraints.
Haskell
1
star
23

IndexedList

Length-indexed and "conic" lists, along with functions to convert between them.
Haskell
1
star
24

ifold

Indenting fold (ifold) is a replacement for the UNIX fold command, which indents every wrapped line by the same amount that the original line was indented. This is useful, e.g. for converting plain-text documents into other formats.
Haskell
1
star