mirror of
https://github.com/hmemcpy/milewski-ctfp-pdf.git
synced 2024-11-22 11:32:00 +03:00
Chapter 3.8 - F-Algebras
This commit is contained in:
parent
ccc974aaa4
commit
7836fc1177
@ -1,12 +1,4 @@
|
||||
\begin{quote}
|
||||
This is part 24 of Categories for Programmers. Previously:
|
||||
\href{https://bartoszmilewski.com/2017/01/02/comonads/}{Comonads}. See
|
||||
the
|
||||
\href{https://bartoszmilewski.com/2014/10/28/category-theory-for-programmers-the-preface/}{Table
|
||||
of Contents}.
|
||||
\end{quote}
|
||||
|
||||
We've seen several formulations of a monoid: as a set, as a
|
||||
\lettrine[lhang=0.17]{W}{e've seen several} formulations of a monoid: as a set, as a
|
||||
single-object category, as an object in a monoidal category. How much
|
||||
more juice can we squeeze out of this simple concept?
|
||||
|
||||
@ -14,9 +6,9 @@ Let's try. Take this definition of a monoid as a set \code{m} with a
|
||||
pair of functions:
|
||||
|
||||
\begin{verbatim}
|
||||
μ :: m × m -> m η :: 1 -> m
|
||||
μ :: m × m -> m
|
||||
η :: 1 -> m
|
||||
\end{verbatim}
|
||||
|
||||
Here, 1 is the terminal object in \textbf{Set} --- the singleton set.
|
||||
The first function defines multiplication (it takes a pair of elements
|
||||
and returns their product), the second selects the unit element from
|
||||
@ -27,23 +19,21 @@ and just consider ``potential monoids.'' A pair of functions is an
|
||||
element of a cartesian product of two sets of functions. We know that
|
||||
these sets may be represented as exponential objects:
|
||||
|
||||
\begin{verbatim}
|
||||
μ ∈ m m×m η ∈ m1
|
||||
\end{verbatim}
|
||||
|
||||
\begin{Verbatim}[commandchars=\\\{\}]
|
||||
μ \ensuremath{\in} m \textsuperscript{m×m}
|
||||
η \ensuremath{\in} m\textsuperscript{1}
|
||||
\end{Verbatim}
|
||||
The cartesian product of these two sets is:
|
||||
|
||||
\begin{verbatim}
|
||||
m m×m × m1
|
||||
\end{verbatim}
|
||||
|
||||
\begin{Verbatim}[commandchars=\\\{\}]
|
||||
m m×m × m\textsuperscript{1}
|
||||
\end{Verbatim}
|
||||
Using some high-school algebra (which works in every cartesian closed
|
||||
category), we can rewrite it as:
|
||||
|
||||
\begin{verbatim}
|
||||
m m×m + 1
|
||||
\end{verbatim}
|
||||
|
||||
\begin{Verbatim}[commandchars=\\\{\}]
|
||||
m \textsuperscript{m×m + 1}
|
||||
\end{Verbatim}
|
||||
The plus sign stands for the coproduct in \textbf{Set}. We have just
|
||||
replaced a pair of functions with a single function --- an element of
|
||||
the set:
|
||||
@ -51,7 +41,6 @@ the set:
|
||||
\begin{verbatim}
|
||||
m × m + 1 -> m
|
||||
\end{verbatim}
|
||||
|
||||
Any element of this set of functions is a potential monoid.
|
||||
|
||||
The beauty of this formulation is that it leads to interesting
|
||||
@ -63,15 +52,15 @@ addition as a binary operation, zero as the unit, and negation as the
|
||||
inverse. To define a group we would start with a triple of functions:
|
||||
|
||||
\begin{verbatim}
|
||||
m × m -> m m -> m 1 -> m
|
||||
m × m -> m
|
||||
m -> m
|
||||
1 -> m
|
||||
\end{verbatim}
|
||||
|
||||
As before, we can combine all these triples into one set of functions:
|
||||
|
||||
\begin{verbatim}
|
||||
m × m + m + 1 -> m
|
||||
\end{verbatim}
|
||||
|
||||
We started with one binary operator (addition), one unary operator
|
||||
(negation), and one nullary operator (identity --- here zero). We
|
||||
combined them into one function. All functions with this signature
|
||||
@ -105,7 +94,6 @@ object \code{a}, and a morphism
|
||||
\begin{verbatim}
|
||||
F a -> a
|
||||
\end{verbatim}
|
||||
|
||||
The object is often called the carrier, an underlying object or, in the
|
||||
context of programming, the carrier \newterm{type}. The morphism is often
|
||||
called the evaluation function or the structure map. Think of the
|
||||
@ -117,7 +105,6 @@ Here's the Haskell definition of an F-algebra:
|
||||
\begin{verbatim}
|
||||
type Algebra f a = f a -> a
|
||||
\end{verbatim}
|
||||
|
||||
It identifies the algebra with its evaluation function.
|
||||
|
||||
In the monoid example, the functor in question is:
|
||||
@ -125,27 +112,33 @@ In the monoid example, the functor in question is:
|
||||
\begin{verbatim}
|
||||
data MonF a = MEmpty | MAppend a a
|
||||
\end{verbatim}
|
||||
|
||||
This is Haskell for \code{1\ +\ a\ ×\ a} (remember
|
||||
\href{https://bartoszmilewski.com/2015/01/13/simple-algebraic-data-types/}{algebraic
|
||||
This is Haskell for \code{1 + a × a} (remember
|
||||
\hyperref[simple-algebraic-data-types]{algebraic
|
||||
data structures}).
|
||||
|
||||
A ring would be defined using the following functor:
|
||||
|
||||
\begin{verbatim}
|
||||
data RingF a = RZero | ROne | RAdd a a | RMul a a | RNeg a
|
||||
data RingF a = RZero
|
||||
| ROne
|
||||
| RAdd a a
|
||||
| RMul a a
|
||||
| RNeg a
|
||||
\end{verbatim}
|
||||
|
||||
which is Haskell for \code{1\ +\ 1\ +\ a\ ×\ a\ +\ a\ ×\ a\ +\ a}.
|
||||
which is Haskell for \code{1 + 1 + a × a + a × a + a}.
|
||||
|
||||
An example of a ring is the set of integers. We can choose
|
||||
\code{Integer} as the carrier type and define the evaluation function
|
||||
as:
|
||||
|
||||
\begin{verbatim}
|
||||
evalZ :: Algebra RingF Integer evalZ RZero = 0 evalZ ROne = 1 evalZ (RAdd m n) = m + n evalZ (RMul m n) = m * n evalZ (RNeg n) = -n
|
||||
\end{verbatim}
|
||||
|
||||
\begin{Verbatim}[commandchars=\\\{\}]
|
||||
evalZ :: \textbf{Algebra} RingF Integer
|
||||
evalZ RZero = 0
|
||||
evalZ ROne = 1
|
||||
evalZ (RAdd m n) = m + n
|
||||
evalZ (RMul m n) = m * n
|
||||
evalZ (RNeg n) = -n
|
||||
\end{Verbatim}
|
||||
There are more F-algebras based on the same functor \code{RingF}. For
|
||||
instance, polynomials form a ring and so do square matrices.
|
||||
|
||||
@ -162,15 +155,22 @@ instance, an arbitrary expression in a ring is generated by this
|
||||
tree-like data structure:
|
||||
|
||||
\begin{verbatim}
|
||||
data Expr = RZero | ROne | RAdd Expr Expr | RMul Expr Expr | RNeg Expr
|
||||
data Expr = RZero
|
||||
| ROne
|
||||
| RAdd Expr Expr
|
||||
| RMul Expr Expr
|
||||
| RNeg Expr
|
||||
\end{verbatim}
|
||||
|
||||
We can replace the original ring evaluator with its recursive version:
|
||||
|
||||
\begin{verbatim}
|
||||
evalZ :: Expr -> Integer evalZ RZero = 0 evalZ ROne = 1 evalZ (RAdd e1 e2) = evalZ e1 + evalZ e2 evalZ (RMul e1 e2) = evalZ e1 * evalZ e2 evalZ (RNeg e) = -(evalZ e)
|
||||
evalZ :: Expr -> Integer
|
||||
evalZ RZero = 0
|
||||
evalZ ROne = 1
|
||||
evalZ (RAdd e1 e2) = evalZ e1 + evalZ e2
|
||||
evalZ (RMul e1 e2) = evalZ e1 * evalZ e2
|
||||
evalZ (RNeg e) = -(evalZ e)
|
||||
\end{verbatim}
|
||||
|
||||
This is still not very practical, since we are forced to represent all
|
||||
integers as sums of ones, but it will do in a pinch.
|
||||
|
||||
@ -183,7 +183,6 @@ define a depth-one tree as:
|
||||
\begin{verbatim}
|
||||
type RingF1 a = RingF (RingF a)
|
||||
\end{verbatim}
|
||||
|
||||
We are filling the holes in the definition of \code{RingF} with
|
||||
depth-zero trees generated by \code{RingF\ a}. Depth-2 trees are
|
||||
similarly obtained as:
|
||||
@ -191,19 +190,16 @@ similarly obtained as:
|
||||
\begin{verbatim}
|
||||
type RingF2 a = RingF (RingF (RingF a))
|
||||
\end{verbatim}
|
||||
|
||||
which we can also write as:
|
||||
|
||||
\begin{verbatim}
|
||||
type RingF2 a = RingF (RingF1 a)
|
||||
\end{verbatim}
|
||||
|
||||
Continuing this process, we can write a symbolic equation:
|
||||
|
||||
\begin{verbatim}
|
||||
type RingFn+1 a = RingF (RingFn a)
|
||||
\end{verbatim}
|
||||
|
||||
\begin{Verbatim}[commandchars=\\\{\}]
|
||||
type RingF\textsubscript{n+1} a = RingF (RingF\textsubscript{n} a)
|
||||
\end{Verbatim}
|
||||
Conceptually, after repeating this process infinitely many times, we end
|
||||
up with our \code{Expr}. Notice that \code{Expr} does not depend on
|
||||
\code{a}. The starting point of our journey doesn't matter, we always
|
||||
@ -220,37 +216,33 @@ point}, an object defined as:
|
||||
\begin{verbatim}
|
||||
Fix f = f (Fix f)
|
||||
\end{verbatim}
|
||||
|
||||
The intuition behind this definition is that, since we applied
|
||||
\code{f} infinitely many times to get \code{Fix\ f}, applying it one
|
||||
\code{f} infinitely many times to get \code{Fix f}, applying it one
|
||||
more time doesn't change anything. In Haskell, the definition of a fixed
|
||||
point is:
|
||||
|
||||
\begin{verbatim}
|
||||
newtype Fix f = Fix (f (Fix f))
|
||||
\end{verbatim}
|
||||
|
||||
Arguably, this would be more readable if the constructor's name were
|
||||
different than the name of the type being defined, as in:
|
||||
|
||||
\begin{verbatim}
|
||||
newtype Fix f = In (f (Fix f))
|
||||
\end{verbatim}
|
||||
|
||||
but I'll stick with the accepted notation. The constructor \code{Fix}
|
||||
(or \code{In}, if you prefer) can be seen as a function:
|
||||
|
||||
\begin{verbatim}
|
||||
Fix :: f (Fix f) -> Fix f
|
||||
\end{verbatim}
|
||||
|
||||
There is also a function that peels off one level of functor
|
||||
application:
|
||||
|
||||
\begin{verbatim}
|
||||
unFix :: Fix f -> f (Fix f) unFix (Fix x) = x
|
||||
unFix :: Fix f -> f (Fix f)
|
||||
unFix (Fix x) = x
|
||||
\end{verbatim}
|
||||
|
||||
The two functions are the inverse of each other. We'll use these
|
||||
functions later.
|
||||
|
||||
@ -261,12 +253,12 @@ constructing some new objects, see if they form a category. Not
|
||||
surprisingly, algebras over a given endofunctor \code{F} form a
|
||||
category. Objects in that category are algebras --- pairs consisting of
|
||||
a carrier object \code{a} and a morphism
|
||||
\code{F\ a\ ->\ a}, both from the original category
|
||||
\code{F a -> a}, both from the original category
|
||||
\emph{C}.
|
||||
|
||||
To complete the picture, we have to define morphisms in the category of
|
||||
F-algebras. A morphism must map one algebra \code{(a,\ f)} to another
|
||||
algebra \code{(b,\ g)}. We'll define it as a morphism \code{m} that
|
||||
F-algebras. A morphism must map one algebra \code{(a, f)} to another
|
||||
algebra \code{(b, g)}. We'll define it as a morphism \code{m} that
|
||||
maps the carriers --- it goes from \code{a} to \code{b} in the
|
||||
original category. Not any morphism will do: we want it to be compatible
|
||||
with the two evaluators. (We call such a structure-preserving morphism a
|
||||
@ -276,18 +268,21 @@ F-algebras. First, notice that we can lift \code{m} to the mapping:
|
||||
\begin{verbatim}
|
||||
F m :: F a -> F b
|
||||
\end{verbatim}
|
||||
|
||||
we can then follow it with \code{g} to get to \code{b}.
|
||||
Equivalently, we can use \code{f} to go from \code{F\ a} to
|
||||
Equivalently, we can use \code{f} to go from \code{F a} to
|
||||
\code{a} and then follow it with \code{m}. We want the two paths to
|
||||
be equal:
|
||||
|
||||
\begin{verbatim}
|
||||
g ∘ F m = m ∘ f
|
||||
g ◦ F m = m ◦ f
|
||||
\end{verbatim}
|
||||
|
||||
\includegraphics[width=2.09375in]{images/alg.png}
|
||||
\begin{figure}[H]
|
||||
\centering
|
||||
\includegraphics[width=50mm]{images/alg.png}
|
||||
\end{figure}
|
||||
|
||||
\noindent
|
||||
It's easy to convince yourself that this is indeed a category (hint:
|
||||
identity morphisms from \emph{C} work just fine, and a composition of
|
||||
homomorphisms is a homomorphism).
|
||||
@ -295,108 +290,124 @@ homomorphisms is a homomorphism).
|
||||
An initial object in the category of F-algebras, if it exists, is called
|
||||
the \newterm{initial algebra}. Let's call the carrier of this initial
|
||||
algebra \code{i} and its evaluator
|
||||
\code{j\ ::\ F\ i\ ->\ i}. It turns out that \code{j},
|
||||
\code{j :: F i -> i}. It turns out that \code{j},
|
||||
the evaluator of the initial algebra, is an isomorphism. This result is
|
||||
known as Lambek's theorem. The proof relies on the definition of the
|
||||
initial object, which requires that there be a unique homomorphism
|
||||
\code{m} from it to any other F-algebra. Since \code{m} is a
|
||||
homomorphism, the following diagram must commute:
|
||||
|
||||
\includegraphics{images/alg2.png}
|
||||
\begin{figure}[H]
|
||||
\centering
|
||||
\includegraphics[width=50mm]{images/alg2.png}
|
||||
\end{figure}
|
||||
|
||||
Now let's construct an algebra whose carrier is \code{F\ i}. The
|
||||
evaluator of such an algebra must be a morphism from \code{F\ (F\ i)}
|
||||
to \code{F\ i}. We can easily construct such an evaluator simply by
|
||||
\noindent
|
||||
Now let's construct an algebra whose carrier is \code{F i}. The
|
||||
evaluator of such an algebra must be a morphism from \code{F (F i)}
|
||||
to \code{F i}. We can easily construct such an evaluator simply by
|
||||
lifting \code{j}:
|
||||
|
||||
\begin{verbatim}
|
||||
F j :: F (F i) -> F i
|
||||
\end{verbatim}
|
||||
|
||||
Because \code{(i,\ j)} is the initial algebra, there must be a unique
|
||||
homomorphism \code{m} from it to \code{(F\ i,\ F\ j)}. The following
|
||||
Because \code{(i, j)} is the initial algebra, there must be a unique
|
||||
homomorphism \code{m} from it to \code{(F i, F j)}. The following
|
||||
diagram must commute:
|
||||
|
||||
\includegraphics{images/alg3a.png}
|
||||
\begin{figure}[H]
|
||||
\centering
|
||||
\includegraphics[width=50mm]{images/alg3a.png}
|
||||
\end{figure}
|
||||
|
||||
\noindent
|
||||
But we also have this trivially commuting diagram (both paths are the
|
||||
same!):
|
||||
|
||||
\includegraphics{images/alg3.png}
|
||||
\begin{figure}[H]
|
||||
\centering
|
||||
\includegraphics[width=50mm]{images/alg3.png}
|
||||
\end{figure}
|
||||
|
||||
\noindent
|
||||
which can be interpreted as showing that \code{j} is a homomorphism of
|
||||
algebras, mapping \code{(F\ i,\ F\ j)} to \code{(i,\ j)}. We can
|
||||
algebras, mapping \code{(F i, F j)} to \code{(i, j)}. We can
|
||||
glue these two diagrams together to get:
|
||||
|
||||
\includegraphics[width=3.12500in]{images/alg4.png}
|
||||
\begin{figure}[H]
|
||||
\centering
|
||||
\includegraphics[width=80mm]{images/alg4.png}
|
||||
\end{figure}
|
||||
|
||||
\noindent
|
||||
This diagram may, in turn, be interpreted as showing that
|
||||
\code{j\ ∘\ m} is a homomorphism of algebras. Only in this case the
|
||||
two algebras are the same. Moreover, because \code{(i,\ j)} is
|
||||
\code{j ◦ m} is a homomorphism of algebras. Only in this case the
|
||||
two algebras are the same. Moreover, because \code{(i, j)} is
|
||||
initial, there can only be one homomorphism from it to itself, and
|
||||
that's the identity morphism \code{idi} --- which we know is a
|
||||
homomorphism of algebras. Therefore \code{j\ ∘\ m\ =\ idi}. Using this
|
||||
that's the identity morphism \code{id\textsubscript{i}} --- which we know is a
|
||||
homomorphism of algebras. Therefore \code{j ◦ m = id\textsubscript{i}}. Using this
|
||||
fact and the commuting property of the left diagram we can prove that
|
||||
\code{m\ ∘\ j\ =\ idFi}. This shows that \code{m} is the inverse of
|
||||
\code{m ◦ j = id\textsubscript{Fi}}. This shows that \code{m} is the inverse of
|
||||
\code{j} and therefore \code{j} is an isomorphism between
|
||||
\code{F\ i} and \code{i}:
|
||||
|
||||
\begin{verbatim}
|
||||
F i ≅ i
|
||||
\end{verbatim}
|
||||
\code{F i} and \code{i}:
|
||||
|
||||
\begin{Verbatim}[commandchars=\\\{\}]
|
||||
F i \ensuremath{\cong} i
|
||||
\end{Verbatim}
|
||||
But that is just saying that \code{i} is a fixed point of \code{F}.
|
||||
That's the formal proof behind the original hand-waving argument.
|
||||
|
||||
Back to Haskell: We recognize \code{i} as our \code{Fix\ f},
|
||||
Back to Haskell: We recognize \code{i} as our \code{Fix f},
|
||||
\code{j} as our constructor \code{Fix}, and its inverse as
|
||||
\code{unFix}. The isomorphism in Lambek's theorem tells us that, in
|
||||
order to get the initial algebra, we take the functor \code{f} and
|
||||
replace its argument \code{a} with \code{Fix\ f}. We also see why
|
||||
replace its argument \code{a} with \code{Fix f}. We also see why
|
||||
the fixed point does not depend on \code{a}.
|
||||
|
||||
\subsection{Natural Numbers}\label{natural-numbers}
|
||||
\section{Natural Numbers}\label{natural-numbers}
|
||||
|
||||
Natural numbers can also be defined as an F-algebra. The starting point
|
||||
is the pair of morphisms:
|
||||
|
||||
\begin{verbatim}
|
||||
zero :: 1 -> N succ :: N -> N
|
||||
zero :: 1 -> N
|
||||
succ :: N -> N
|
||||
\end{verbatim}
|
||||
|
||||
The first one picks the zero, and the second one maps all numbers to
|
||||
their successors. As before, we can combine the two into one:
|
||||
|
||||
\begin{verbatim}
|
||||
1 + N -> N
|
||||
\end{verbatim}
|
||||
|
||||
The left hand side defines a functor which, in Haskell, can be written
|
||||
like this:
|
||||
|
||||
\begin{verbatim}
|
||||
data NatF a = ZeroF | SuccF a
|
||||
\end{verbatim}
|
||||
|
||||
The fixed point of this functor (the initial algebra that it generates)
|
||||
can be encoded in Haskell as:
|
||||
|
||||
\begin{verbatim}
|
||||
data Nat = Zero | Succ Nat
|
||||
\end{verbatim}
|
||||
|
||||
A natural number is either zero or a successor of another number. This
|
||||
is known as the Peano representation for natural numbers.
|
||||
|
||||
\section{Catamorphisms}\label{catamorphisms}
|
||||
|
||||
Let's rewrite the initiality condition using Haskell notation. We call
|
||||
the initial algebra \code{Fix\ f}. Its evaluator is the contructor
|
||||
the initial algebra \code{Fix f}. Its evaluator is the contructor
|
||||
\code{Fix}. There is a unique morphism \code{m} from the initial
|
||||
algebra to any other algebra over the same functor. Let's pick an
|
||||
algebra whose carrier is \code{a} and the evaluator is \code{alg}.
|
||||
|
||||
\includegraphics{images/alg5.png}\\
|
||||
\begin{figure}[H]
|
||||
\centering
|
||||
\includegraphics[width=60mm]{images/alg5.png}
|
||||
\end{figure}
|
||||
|
||||
\noindent
|
||||
By the way, notice what \code{m} is: It's an evaluator for the fixed
|
||||
point, an evaluator for the whole recursive expression tree. Let's find
|
||||
a general way of implementing it.
|
||||
@ -405,18 +416,21 @@ Lambek's theorem tells us that the constructor \code{Fix} is an
|
||||
isomorphism. We called its inverse \code{unFix}. We can therefore flip
|
||||
one arrow in this diagram to get:
|
||||
|
||||
\includegraphics{images/alg6.png}
|
||||
\begin{figure}[H]
|
||||
\centering
|
||||
\includegraphics[width=60mm]{images/alg6.png}
|
||||
\end{figure}
|
||||
|
||||
\noindent
|
||||
Let's write down the commutation condition for this diagram:
|
||||
|
||||
\begin{verbatim}
|
||||
m = alg . fmap m . unFix
|
||||
\end{verbatim}
|
||||
|
||||
We can interpret this equation as a recursive definition of \code{m}.
|
||||
The recursion is bound to terminate for any finite tree created using
|
||||
the functor \code{f}. We can see that by noticing that
|
||||
\code{fmap\ m} operates underneath the top layer of the functor
|
||||
\code{fmap m} operates underneath the top layer of the functor
|
||||
\code{f}. In other words, it works on the children of the original
|
||||
tree. The children are always one level shallower than the original
|
||||
tree.
|
||||
@ -435,25 +449,25 @@ gives us the function we called \code{m}. This higher order function
|
||||
is called a catamorphism:
|
||||
|
||||
\begin{verbatim}
|
||||
cata :: Functor f => (f a -> a) -> Fix f -> a cata alg = alg . fmap (cata alg) . unFix
|
||||
cata :: Functor f => (f a -> a) -> Fix f -> a
|
||||
cata alg = alg . fmap (cata alg) . unFix
|
||||
\end{verbatim}
|
||||
|
||||
Let's see an example of that. Take the functor that defines natural
|
||||
numbers:
|
||||
|
||||
\begin{verbatim}
|
||||
data NatF a = ZeroF | SuccF a
|
||||
\end{verbatim}
|
||||
|
||||
Let's pick \code{(Int,\ Int)} as the carrier type and define our
|
||||
Let's pick \code{(Int, Int)} as the carrier type and define our
|
||||
algebra as:
|
||||
|
||||
\begin{verbatim}
|
||||
fib :: NatF (Int, Int) -> (Int, Int) fib ZeroF = (1, 1) fib (SuccF (m, n)) = (n, m + n)
|
||||
fib :: NatF (Int, Int) -> (Int, Int)
|
||||
fib ZeroF = (1, 1)
|
||||
fib (SuccF (m, n)) = (n, m + n)
|
||||
\end{verbatim}
|
||||
|
||||
You can easily convince yourself that the catamorphism for this algebra,
|
||||
\code{cata\ fib}, calculates Fibonacci numbers.
|
||||
\code{cata fib}, calculates Fibonacci numbers.
|
||||
|
||||
In general, an algebra for \code{NatF} defines a recurrence relation:
|
||||
the value of the current element in terms of the previous element. A
|
||||
@ -466,14 +480,12 @@ A list of \code{e} is the initial algebra of the following functor:
|
||||
\begin{verbatim}
|
||||
data ListF e a = NilF | ConsF e a
|
||||
\end{verbatim}
|
||||
|
||||
Indeed, replacing the variable \code{a} with the result of recursion,
|
||||
which we'll call \code{List\ e}, we get:
|
||||
which we'll call \code{List e}, we get:
|
||||
|
||||
\begin{verbatim}
|
||||
data List e = Nil | Cons e (List e)
|
||||
\end{verbatim}
|
||||
|
||||
An algebra for a list functor picks a particular carrier type and
|
||||
defines a function that does pattern matching on the two constructors.
|
||||
Its value for \code{NilF} tells us how to evaluate an empty list, and
|
||||
@ -484,10 +496,11 @@ For instance, here's an algebra that can be used to calculate the length
|
||||
of a list (the carrier type is \code{Int}):
|
||||
|
||||
\begin{verbatim}
|
||||
lenAlg :: ListF e Int -> Int lenAlg (ConsF e n) = n + 1 lenAlg NilF = 0
|
||||
lenAlg :: ListF e Int -> Int
|
||||
lenAlg (ConsF e n) = n + 1
|
||||
lenAlg NilF = 0
|
||||
\end{verbatim}
|
||||
|
||||
Indeed, the resulting catamorphism \code{cata\ lenAlg} calculates the
|
||||
Indeed, the resulting catamorphism \code{cata lenAlg} calculates the
|
||||
length of a list. Notice that the evaluator is a combination of (1) a
|
||||
function that takes a list element and an accumulator and returns a new
|
||||
accumulator, and (2) a starting value, here zero. The type of the value
|
||||
@ -498,22 +511,21 @@ Compare this to the traditional Haskell definition:
|
||||
\begin{verbatim}
|
||||
length = foldr (\e n -> n + 1) 0
|
||||
\end{verbatim}
|
||||
|
||||
The two arguments to \code{foldr} are exactly the two components of
|
||||
the algebra.
|
||||
|
||||
Let's try another example:
|
||||
|
||||
\begin{verbatim}
|
||||
sumAlg :: ListF Double Double -> Double sumAlg (ConsF e s) = e + s sumAlg NilF = 0.0
|
||||
sumAlg :: ListF Double Double -> Double
|
||||
sumAlg (ConsF e s) = e + s
|
||||
sumAlg NilF = 0.0
|
||||
\end{verbatim}
|
||||
|
||||
Again, compare this with:
|
||||
|
||||
\begin{verbatim}
|
||||
sum = foldr (\e s -> e + s) 0.0
|
||||
\end{verbatim}
|
||||
|
||||
As you can see, \code{foldr} is just a convenient specialization of a
|
||||
catamorphism to lists.
|
||||
|
||||
@ -525,23 +537,25 @@ direction of the morphism is reversed:
|
||||
\begin{verbatim}
|
||||
a -> F a
|
||||
\end{verbatim}
|
||||
|
||||
Coalgebras for a given functor also form a category, with homomorphisms
|
||||
preserving the coalgebraic structure. The terminal object
|
||||
\code{(t,\ u)} in that category is called the terminal (or final)
|
||||
coalgebra. For every other algebra \code{(a,\ f)} there is a unique
|
||||
\code{(t, u)} in that category is called the terminal (or final)
|
||||
coalgebra. For every other algebra \code{(a, f)} there is a unique
|
||||
homomorphism \code{m} that makes the following diagram commute:
|
||||
|
||||
\includegraphics{images/alg7.png}
|
||||
\begin{figure}[H]
|
||||
\centering
|
||||
\includegraphics[width=50mm]{images/alg7.png}
|
||||
\end{figure}
|
||||
|
||||
\noindent
|
||||
A terminal colagebra is a fixed point of the functor, in the sense that
|
||||
the morphism \code{u\ ::\ t\ ->\ F\ t} is an isomorphism
|
||||
the morphism \code{u :: t -> F t} is an isomorphism
|
||||
(Lambek's theorem for coalgebras):
|
||||
|
||||
\begin{verbatim}
|
||||
F t ≅ t
|
||||
\end{verbatim}
|
||||
|
||||
\begin{Verbatim}[commandchars=\\\{\}]
|
||||
F t \ensuremath{\cong} t
|
||||
\end{Verbatim}
|
||||
A terminal coalgebra is usually interpreted in programming as a recipe
|
||||
for generating (possibly infinite) data structures or transition
|
||||
systems.
|
||||
@ -550,24 +564,23 @@ Just like a catamorphism can be used to evaluate an initial algebra, an
|
||||
anamorphism can be used to coevaluate a terminal coalgebra:
|
||||
|
||||
\begin{verbatim}
|
||||
ana :: Functor f => (a -> f a) -> a -> Fix f ana coalg = Fix . fmap (ana coalg) . coalg
|
||||
ana :: Functor f => (a -> f a) -> a -> Fix f
|
||||
ana coalg = Fix . fmap (ana coalg) . coalg
|
||||
\end{verbatim}
|
||||
|
||||
A canonical example of a coalgebra is based on a functor whose fixed
|
||||
point is an infinite stream of elements of type \code{e}. This is the
|
||||
functor:
|
||||
|
||||
\begin{verbatim}
|
||||
data StreamF e a = StreamF e a deriving Functor
|
||||
data StreamF e a = StreamF e a
|
||||
deriving Functor
|
||||
\end{verbatim}
|
||||
|
||||
and this is its fixed point:
|
||||
|
||||
\begin{verbatim}
|
||||
data Stream e = Stream e (Stream e)
|
||||
\end{verbatim}
|
||||
|
||||
A coalgebra for \code{StreamF\ e} is a function that takes the seed of
|
||||
A coalgebra for \code{StreamF e} is a function that takes the seed of
|
||||
type \code{a} and produces a pair (\code{StreamF} is a fancy name
|
||||
for a pair) consisting of an element and the next seed.
|
||||
|
||||
@ -584,24 +597,26 @@ the sieve of Eratosthenes. This coalgebra is implemented by the
|
||||
following function:
|
||||
|
||||
\begin{verbatim}
|
||||
era :: [Int] -> StreamF Int [Int] era (p : ns) = StreamF p (filter (notdiv p) ns) where notdiv p n = n `mod` p /= 0
|
||||
era :: [Int] -> StreamF Int [Int]
|
||||
era (p : ns) = StreamF p (filter (notdiv p) ns)
|
||||
where notdiv p n = n `mod` p /= 0
|
||||
\end{verbatim}
|
||||
|
||||
The anamorphism for this coalgebra generates the list of primes:
|
||||
|
||||
\begin{verbatim}
|
||||
primes = ana era [2..]
|
||||
\end{verbatim}
|
||||
|
||||
A stream is an infinite list, so it should be possible to convert it to
|
||||
a Haskell list. To do that, we can use the same functor \code{StreamF}
|
||||
to form an algebra, and we can run a catamorphism over it. For instance,
|
||||
this is a catamorphism that converts a stream to a list:
|
||||
|
||||
\begin{verbatim}
|
||||
toListC :: Fix (StreamF e) -> [e] toListC = cata al where al :: StreamF e [e] -> [e] al (StreamF e a) = e : a
|
||||
toListC :: Fix (StreamF e) -> [e]
|
||||
toListC = cata al
|
||||
where al :: StreamF e [e] -> [e]
|
||||
al (StreamF e a) = e : a
|
||||
\end{verbatim}
|
||||
|
||||
Here, the same fixed point is simultaneously an initial algebra and a
|
||||
terminal coalgebra for the same endofunctor. It's not always like this,
|
||||
in an arbitrary category. In general, an endofunctor may have many (or
|
||||
@ -616,16 +631,15 @@ functor is modified to produce a \code{Maybe} pair:
|
||||
\begin{verbatim}
|
||||
unfoldr :: (b -> Maybe (a, b)) -> b -> [a]
|
||||
\end{verbatim}
|
||||
|
||||
The value of \code{Nothing} will terminate the generation of the list.
|
||||
|
||||
An interesting case of a coalgebra is related to lenses. A lens can be
|
||||
represented as a pair of a getter and a setter:
|
||||
|
||||
\begin{verbatim}
|
||||
set :: a -> s -> a get :: a -> s
|
||||
set :: a -> s -> a
|
||||
get :: a -> s
|
||||
\end{verbatim}
|
||||
|
||||
Here, \code{a} is usually some product data type with a field of type
|
||||
\code{s}. The getter retrieves the value of that field and the setter
|
||||
replaces this field with a new value. These two functions can be
|
||||
@ -634,24 +648,21 @@ combined into one:
|
||||
\begin{verbatim}
|
||||
a -> (s, s -> a)
|
||||
\end{verbatim}
|
||||
|
||||
We can rewrite this function further as:
|
||||
|
||||
\begin{verbatim}
|
||||
a -> Store s a
|
||||
\end{verbatim}
|
||||
|
||||
where we have defined a functor:
|
||||
|
||||
\begin{verbatim}
|
||||
data Store s a = Store (s -> a) s
|
||||
\end{verbatim}
|
||||
|
||||
Notice that this is not a simple algebraic functor constructed from sums
|
||||
of products. It involves an exponential \code{as}.
|
||||
|
||||
A lens is a coalgebra for this functor with the carrier type \code{a}.
|
||||
We've seen before that \code{Store\ s} is also a comonad. It turns out
|
||||
We've seen before that \code{Store s} is also a comonad. It turns out
|
||||
that a well-behaved lens corresponds to a coalgebra that is compatible
|
||||
with the comonad structure. We'll talk about this in the next section.
|
||||
|
||||
@ -662,12 +673,12 @@ with the comonad structure. We'll talk about this in the next section.
|
||||
\item
|
||||
Implement the evaluation function for a ring of polynomials of one
|
||||
variable. You can represent a polynomial as a list of coefficients in
|
||||
front of powers of \code{x}. For instance, \code{4x2-1} would be
|
||||
front of powers of \code{x}. For instance, \code{4x\textsuperscript{2}-1} would be
|
||||
represented as (starting with the zero'th power)
|
||||
\code{{[}-1,\ 0,\ 4{]}}.
|
||||
\code{{[}-1, 0, 4{]}}.
|
||||
\item
|
||||
Generalize the previous construction to polynomials of many
|
||||
independent variables, like \code{x2y-3y3z}.
|
||||
independent variables, like \code{x\textsuperscript{2}y-3y\textsuperscript{3}z}.
|
||||
\item
|
||||
Implement the algebra for the ring of 2×2 matrices.
|
||||
\item
|
||||
@ -676,8 +687,4 @@ with the comonad structure. We'll talk about this in the next section.
|
||||
\item
|
||||
Use \code{unfoldr} to generate a list of the first \code{n}
|
||||
primes.
|
||||
\end{enumerate}
|
||||
|
||||
Next:
|
||||
\href{https://bartoszmilewski.com/2017/03/14/algebras-for-monads/}{Algebras
|
||||
for Monads}.
|
||||
\end{enumerate}
|
@ -95,7 +95,7 @@ PDF compiled by @url{https://github.com/hmemcpy/milewski-ctfp-pdf, Igal Tabachni
|
||||
\chapter{Products and Coproducts}\label{products-and-coproducts}
|
||||
\subfile{content/1.5/Products and Coproducts}
|
||||
|
||||
\chapter{Simple Algebraic Data Types}
|
||||
\chapter{Simple Algebraic Data Types}\label{simple-algebraic-data-types}
|
||||
\subfile{content/1.6/Simple Algebraic Data Types}
|
||||
|
||||
\chapter{Functors}
|
||||
@ -135,6 +135,10 @@ PDF compiled by @url{https://github.com/hmemcpy/milewski-ctfp-pdf, Igal Tabachni
|
||||
\chapter{Comonads}\label{comonads}
|
||||
\subfile{content/3.7/Comonads}
|
||||
|
||||
\chapter{F-Algebras}\label{f-algebras}
|
||||
\subfile{content/3.8/F-Algebras}
|
||||
|
||||
|
||||
\backmatter
|
||||
|
||||
@unnumbered Acknowledgments
|
||||
|
Loading…
Reference in New Issue
Block a user