# Group Theory in Haskell

## Table of Contents

## 1. Introduction

There are a lot of introductions to group theory, but I wanted to do one in the Haskell programming language. For more about Haskell see the haskellbook, this article will assume some Haskell programming knowledge.

### 1.1. Motivation

There are two major motivations for group theory. The first comes from geometry. The geometer Felix Klein tried to find the essence of Euclidean geometry. His answer was that geometry is essentially about transformations of the plane that preserve distance. Another way to express this:

When a geometer makes a proof that triangle \(ABC\) is congruent to triangle \(DEF\) what they are saying is that there exists some transformation of the plane \({\color{red} f : \mathbb{R}^2 \to \mathbb{R}^2}\), so that \(f(ABC) = DEF\). The distance-preserving property could be expressed precisely as \(d(p,q) = d(f(p),f(q))\).

When you consider the set of all such transformations of the plane, you get a structured object called a group. These objects are worthy of study on their own because they show up in many other seemingly unrelated contexts. Like algebra.

Another major motivation for studying groups came from algebra. A young frenchman named Évariste Galois was studying algebra, particularly the existence of solutions to polynomial equations, like:

\[x^3 + 6x^2 + 11x - 20 = 0\]

He developed a theory of how to transform the "zeros" of the polynomial equations, and these also form a group, like Klein's geometric transformations. Galois worked out a theory for why some equations (like quadratics and cubics) have general solutions, and why others don't. The Galois group is central to that theory.

So the idea of a group arises in both algebra and geometry. It shows up in physics too, it's one of those fundamental ideas whose time came in the 19th century. The study of groups is considered the first form of "Modern Algebra". Groups are essential to understanding symmetry, and symmetry is essential to understanding the physical world. The Standard Model of Particle Physics is a glorified study of one particular group: \(U(1) \times U(2) \times SU(3)\), and the application of that one group is… *all the forces and fields in the universe except gravity*. What's extraordinary is how we can start studying groups here on paper and in a Haskell REPL.

## 2. Definition of a group

A ** group** is a pair, \((G, * )\) where \(G\) is a non-empty set, and \(*:G\times G \to G\) where the following laws apply:

- Associativity: \((a * b) * c = a * (b * c)\) for all \(a,b,c \in G\)
- Identity: There exists an \(e \in G\) such that \(e * a = a * e = a\) for all \(a \in G\)
- Invertibility: there's a \(b\) for every \(a\) such that \(a * b = b * a = e\)

### 2.1. Haskell definition of a group

In Haskell, a type `g`

is an instance of a `Group`

if it's a `Monoid`

with an `invert`

function:

class Monoid g => Group g where invert :: m -> m

Such that

a <> (invert a) == (invert a) <> a == mempty

`mempty`

is the `Monoid`

unit, it's the \(e\) in the mathmetical definition above.

The notation used for the inverse of a group element is the superscript -1, so \(a * a^{-1} = e\) means that \(a^{-1}\) is the inverse of \(a\).

Another convention used is to drop the infix \(*\) and just use multiplication notation, so \(aa^{-1}=a^{-1}a=e\)

## 3. The Symmetric Group

The first example of a group we should introduce is the Symmetric group \(\text{Sym}(X)\) on a set \(X\). The definition of this group is \[\text{Sym}(X) = \{ f:X \to X | \text{ where \(f^{-1}\) exists} \}\]

Where the group operation is \(\circ\), function composition. Let's check the definition of group above. The identity function exists. Inverses are in the set by definition. What about associativity?

#### 3.0.1. Proof that function composition is associative:

Function types have an instance of `Monoid`

typeclass which proves that function composition is associative.

instance Monoid m => Monoid (r -> m) where mempty _ = mempty f `mappend` g = \x -> f x `mappend` g x

Exercise: Check that this Haskell code compiles.

### 3.1. Examples of Symmetric Groups on Finite sets

There are two special notations for permutations. We defined \(\text{Sym}(X)\) using functions with inverses, but since we are thinking about these functions as objects in themselves, let's introduce a special syntax.

#### 3.1.1. pmatrix notation

Let's start with \(X = \{0, 1, 2\}\), the three-element set. To represent an invertible function, we can just make a two-row table like this:

\[\begin{pmatrix} 0 & 1 & 2 \\ 1 & 0 & 2 \\ \end{pmatrix}\]

The permutation notation takes the set \(X\) as the first row, and then the set \(f(X)\) as the second row:

\[\begin{pmatrix} x_0 & ... & x_n \\ f(x_0) & ... & f(x_n) \\ \end{pmatrix}\]

Another name used for the Symmetric group on an n-element set is \(S_n\). The group operation for \(S_3\) is function composition, so as an example,

\[\begin{pmatrix} 0 & 1 & 2 \\ 1 & 0 & 2 \\ \end{pmatrix}\circ\begin{pmatrix} 0 & 1 & 2 \\ 1 & 0 & 2 \\ \end{pmatrix} = \begin{pmatrix} 0 & 1 & 2 \\ 0 & 1 & 2 \\ \end{pmatrix}\]

The \(\begin{pmatrix} 0 & 1 & 2 \\ 0 & 1 & 2 \\ \end{pmatrix}\) element is the identity, since it takes each element of \(X\) to itself.

#### 3.1.2. Cycle notation

Notice in 3.1.1 what the \(\begin{pmatrix} 0 & 1 & 2 \\ 1 & 0 & 2 \\ \end{pmatrix}\) element is doing. It is swapping 0 and 1. This is a transposition, whic means it swaps two elements. In cycle notation it would be \((0 \space 1)\).

If you chain together transpositions \((0\space 1)\) and \((1\space 2)\) you get longer cycles (by convention I am dropping the \(\circ\) and just juxtaposing the elements):

\[(0 \space 1)(1\space 2) = (0\space 1 \space 2)\]

The cycle notation is more compact, but harder to implement in code. We will do both in the next section to demonstrate how to implement different notations on Haskell's algebraic type system.

Now let's implement \(S_3\) in Haskell and make it easy to interactively work with at the REPL.

### 3.2. `Data.Group.Permutation`

in Haskell

Since `Monoid`

is so central to Haskell code, it's easy to work with Groups, since they are just Monoids with an inverse function (see 2.1). In Haskell, the analog of a set is a type. So taking our set \(X = \{0,1,2\}\), the statement \(1 \in X\) is expressed as `1 :: X`

in Haskell. The elements of a set are analogous to the *inhabitants* of a type. In the example below, we define a type `X`

with three inhabitants.

#### 3.2.1. show function for pmatrix notation

import Data.Group.Permutation import Data.List data X = Zero | One | Two deriving (Enum, Eq, Show) -- pmatrix notation pmn :: Permutation X -> String pmn p = "\\begin{pmatrix}" ++ (concat $ intersperse " & " $ map show xs) ++ "\\\\" ++ (concat $ intersperse " & " $ map (show . to) xs) ++ "\\end{pmatrix}" where (to, _) = pairwise p xs = [Zero, One, Two] -- show permutations in the notation above instance Show (Permutation X) where show p = pmn p where (to, _) = pairwise p -- Defining the elements of S₃ s0 = permute id id -- identity permutation s1 = permute zo zo where -- transpose Zero/One zo = (\x -> case x of Zero -> One One -> Zero Two -> Two)

The string output of `show s1`

looks like this:
`\begin{pmatrix}Zero & One & Two\\One & Zero & Two\end{pmatrix}`

which renders as:

\[\begin{pmatrix}Zero & One & Two\\One & Zero & Two\end{pmatrix}\]

#### 3.2.2. show function for cycle notation

This is an alternative `show`

function that uses cycle notation.
The problem in Haskell is collecting all the cycles. Consider our \(S_3\) example so far, notice how
\((Zero \space One)\) is a transposition, which is also a 2-cycle. It *turns out* that we can prove that all
permutations can be decomposed into transpositions. (Proof: 4.1.1)

An example of decomposing a permutation into transpositions is the 3-cycle: \((Zero \space One \space Two) = (One \space Two)(Zero \space One)\)

Think about it using function composition, the rightmost function maps \(Zero\) to \(One\), then the next one sends \(One\)
to \(Two\). So we need a generic algorithm for generating the cycle notation for a general `Permutation X`

.

One way to do it is to define a **cycle set**, and observe that the cycle sets will be disjoint, and that they will partition the set \(X\).

- Take an element \(x_1 \in X\)
- Define the
**cycle set**of \(x_1\) as \(\{x_1, f(x_1), f(f(x_1)), ..., f^n(x_1)\}\) - Take the collection of cycle sets for all elements \(x_i\). This partitions \(X\)
- Display the sets as cycles in the output:
`(x1 f(x1) f(f(x1)) ... f(f(...f(x1))))`

import Data.Group.Permutation import Data.List import Data.Set data X = Zero | One | Two deriving (Enum, Eq, Show) -- cycle notation pmc :: Permutation X -> String pmc p = "TODO" -- show permutations in cycle notation instance Show (Permutation X) where show p = pmc p where (to, _) = pairwise p cycleset :: Permutation X -> Set X cycleset p x = fromList $ (take 3 $ iterate f x) -- Defining the elements of S₃ s0 = permute id id -- identity permutation s1 = permute zo zo where -- transpose Zero/One zo = (\x -> case x of Zero -> One One -> Zero Two -> Two)

### 3.3. Exercises (in Haskell)

- Define the group \(S_3\)
- Let \(g_1, ..., g_n \in G\) where \(G\) is a group, prove that \[(g_1g_2...g_n)^{-1} = (g_n^{-1}g_{n-1}^{-1}...g_2^{-1}g_1^{-1})\]

## 4. Appendix

### 4.1. proofs

#### 4.1.1. Proof that all permutations are decomposable into transpositions

Bubble sort exists. Think about it. QED.

### 4.2. My literate programming workflow for this document

I wrote this document in org-mode in Emacs. I export the org file to html to generate the website, and then I run `org-babel-tangle`

to produce the Haskell code. To speed up the workflow I made the function below and then bound it to <F5>

(defun org-babel-tangle-and-compile-perms () "Tangle and compile perms.hs" (interactive) (progn (org-babel-tangle) (compile "/opt/homebrew/bin/stack exec -- ghc perms.hs")))

### 4.3. The `main`

function

./perms.hs is a literate program that this org-mode document produces. Supporting code to make the file compile and run is defined down here in the appendix.

{-# LANGUAGE FlexibleInstances #-} module Main where import Data.Group.Permutation import Data.List data X = Zero | One | Two deriving (Enum, Eq, Show) -- pmatrix notation pmn :: Permutation X -> String pmn p = "\\begin{pmatrix}" ++ (concat $ intersperse " & " $ map show xs) ++ "\\\\" ++ (concat $ intersperse " & " $ map (show . to) xs) ++ "\\end{pmatrix}" where (to, _) = pairwise p xs = [Zero, One, Two] -- show permutations in the notation above instance Show (Permutation X) where show p = pmn p where (to, _) = pairwise p -- Defining the elements of S₃ s0 = permute id id -- identity permutation s1 = permute zo zo where -- transpose Zero/One zo = (\x -> case x of Zero -> One One -> Zero Two -> Two) main :: IO () main = do print s1

### 4.4. the `package.yaml`

file, where we add dependencies

name: perms-example version: 0.0.1 executables: perms-example: main: perms.hs source-dirs: . dependencies: - base >= 4.7 && < 5 - group-theory == 0.2.2

### 4.5. the `stack.yaml`

file, where we define the overall package

resolver: lts-18.17 extra-deps: - group-theory-0.2.2