Constructing Polymorphic Programs With Quotient Types
Constructing Polymorphic Programs With Quotient Types
Types
1 Introduction
The efficient representation and manipulation of data is one of the fundamental tasks
in the construction of large software systems. More precisely, one aims to achieve
amongst other properties: i) abstraction so as to hide implementation details and thereby
facilitate modular programming; ii) expressivity so as to uniformly capture as wide a
class of data types as possible; iii) disciplined recursion principles to provide convenient
methods for defining generic operations on data structures; and iv) formal semantics to
underpin reasoning about the correctness of programs. The most successful approach
to date has been Hindley-Milner polymorphism which provides predefined mechanisms
for manipulating data structures providing they are parametric in the data. Canonical
examples of such parametric polymorphic functions are the map and fold operations
which can be used to define a wide variety of programs in a structured and easy to reason
about manner.
However, a number of useful data types and associated operations are not expressible
in the Hindley-Milner type system and this has lead to many proposed extensions
including, amongst others, generic programming, dependent types (4), higher order
types (6) shapely types (9), imaginary types (13) and type classes. However, one area
which has received less attention is that of quotient types such as, for example, unordered
pairs, cyclic lists and the bag type. This is because the problem is fundamentally rather
difficult — on the one hand one wants to allow as wide a theory as possible so as to
encompass as many quotient types as possible while, on the other hand, one wants to
restrict ones definition to derive a well behave meta-theory which provides support for
key programming paradigms such as polymorphic programming etc.
This paper is, as far as we know, the first paper to give a detailed analysis of how to
program with quotient types in a polymorphic fashion. In particular we
• We show how the syntax of such a declaration gives rise to a quotient datatype.
To execute this program of research we extend our work on container datatypes (1; 2; 3).
Container types represent types via a set of shapes and locations in each shape
where data may be stored. They are therefore like Jay’s shapely types (9) but more
primitive as we discuss later. In previous papers cited above, we have shown how these
container types are closed under a wide variety of useful constructions and can also
be used as a framework for generic programming, eg they support a generic notion of
differentiation. (3).
This paper extends containers to cover quotient datatypes by saying that certain labellings
of locations with data are equivalent to others. We call these structures quotient
containers. As such they correspond to the step from normal functors to analytic functors
in Joyal’s work (11). However, our quotient containers are more general than analytic
functors as the allow infinite sets of positions to model coinductive datatypes. In addition,
our definition of the morphisms between quotient containers is new as is all of their
applications to programming. In addition, while pursuing the above program, we also
use a series of running examples to aid the reader. We assume only the most basic
definitions from category theory of category, functor and natural transformations. The
exception is the use of left Kan extensions for which we supply the reader with the
required information.
The paper is structured as follows. In section 2 we recall the basic theory of containers,
container morphisms and their application to polymorphic programming. We also discuss
the relationship between containers and shapely types. In section 3 we discuss how
quotient datatypes can be represented in container theoretic terms while in section
4 we discuss how polymorphic programs between quotient types can be represented
uniquely as morphisms between quotient containers. We conclude in section 5 with some
conclusions and proposals for further work.
Notation: Before we begin, some notation. We write N or the set of natural numbers
and if n ∈ N, we write n for the set {0, . . . , n − 1}. We assume the basic definitions of
category theory and if f : X → Y and g : Y → Z are morphisms in a category, we write
their composite g ◦ f : X → Z as is standard categorical practice. We write K1 for the
constantly 1 valued functor from any category to Sets. If A is a set and B is an A indexed
family of sets, we write Σ a:A.B(a) for the set {(a, b) | a ∈ A, b ∈ B(a)}. We write ! for the
empty map from the empty set to any other set. Injections into the coproduct are written
inl and inr.
This paper uses left Kan extensions to extract a universal property of containers which
is not immediately visible. We understand that many readers will not be familiar with
these structures so we supply all definitions and refer the reader to (12) for more details.
Their use is limited to a couple of places and hence doesn’t make the paper inaccessible
to the non-cogniscenti. Given a functor I : A → B and a category C , precomposition
with I defines a functor ◦ I : [B, C ] → [A , C ]. The problem of left Kan extensions is
the problem of finding a left adjoint to ◦ I. More concretely, given a functor F : A → C ,
the left Kan extension of F along I is written Lan I F defined via the natural isomorphism
[B, C ](LanI F, H) ∼
= [A , C ](F, H ◦ I)
One can use the following coend formula to calculate the action of a left Kan extension
when C = Sets and A is small
Z
(LanI F)X = B(IA, X) × FA
A∈A
What are Containers? Containers capture the idea that concrete datatypes consist of
memory locations where data can be stored. For example, any element of the type of lists
List(X) of X can be uniquely written as a natural number n given by the length of the
list, together with a function {0, . . . , n − 1} → X which labels each position within the
list with an element from X:
n:N , f : {0 . . . n − 1} → X .
We may think of the set {0, . . . , n − 1} as n memory locations while the function f
attaches to these memory locations, the data to be stored there. Similarly, any binary tree
tree can be uniquely described by its underlying shape (which is obtained by deleting the
data stored at the leaves) and a function mapping the positions in this shape to the data
thus:
•
•
• X
• x3 ∼
= x3 .
x1 x2 x2 x
1
More generally, we are led to consider datatypes which are given by a set of shapes S
and, for each s ∈ S, a set of positions P(s) which we think of as locations in memory
where data can be stored. This is precisely a container
Of course, in general we do not want to restrict ourselves to the category of sets since
we want our theory to be applicable to domain theoretic models. Rather, we would
develop our theory over over locally cartesian closed categories (7), certain forms of
fibrations such as comprehension categories (8) or models of Martin-L öf type theory —
our previous work (1; 2; 3) for such a development. However, part of the motivation for
this paper was to make containers accessible to the programming community where we
believe they provide a flexible platform for supporting generic forms of programming.
Consequently, we have deliberately chosen to work over Sets so as to enable us to get
our ideas across without an overly mathematical presentation.
Example 2 The list type is given by the container with shapes given by the natural
numbers N and, for n ∈ N, define the positions P(n) to be the set {0, . . . , n − 1}.
To summarise containers are our presentations of datatypes in the same way that data
declarations are presentations of datatypes in Haskell. The semantics of a container is an
endofunctor on some category which, in this paper, is Sets. This is given by
Σn : N. {0, . . . ..n − 1} → X .
Such pairs clearly bijectively correspond to lists.
The theory of containers was developed in a series of recent papers (1; 2; 3) which
showed that containers encompass a wide variety of types as they are closed under
various type forming operations such as sums, products, constants, fixed exponentiation,
(nested) least fixed points and (nested) greatest fixed points. Thus containers encapsulate
a large number of datatypes. So far, we have dealt with containers in one variable whose
extensions are functors on Sets. The extension to n-ary containers, whose extensions are
functors Setsn → Sets, is straightforward. Such containers consist of a set of shapes S,
and for each s ∈ S there are n position sets Pn (s). See the above references for details.
We finish this section with a more abstract presentation of containers which will be
used to exhibit the crucial universal property that they satisfy. This universal property
underlies the key result about containers. First, note that the data in a container S . P
can be presented as a functor P : S → Sets where here we regard the set S as a discrete
category and P maps each s to P(s). In future, we will switch between these two views
of a container at will. The semantic functor TS.P has a universal property given by the
following lemma.
Lemma 4. Let P : S → Sets be a container. Then TS.P is the left Kan extension of K1
along P
P
S Sets
K1
TS.P = LanP K1
Sets
Z
(LanP K1 )X = Sets(Ps, X) × K1 s = Σ s : S. Sets(Ps, X) = TS.P (X)
s:S
where the first equality is the classic coend formula for coends, the second equality
holds as S is a discrete category and 1 is the unit for the product, and the last equality is
the definition of TS.P (X). 2
As we shall see, this universal property will be vital to our representation theorem.
2.1 Container Morphisms
To understand these container morphisms, consider the effect of the reverse function on
a list written in container form (n, f ). Its reversal must be a list and hence of the form
(n0 , f 0 ). In addition, n0 should only depend upon n since reverse is polymorphic and hence
shouldn’t depend upon the actual data in the list given by f . Thus there is a function
N → N. In the case of reverse, the length of the list doesn’t change and hence this is the
identity. To define f 0 which associates to each position in the output a piece of data, we
should first associate to each position in the output a position in the input and then look
up the data using f . Pictorially we have:
l1 l0
p0 p0 X
p1 p1
p2 p2
p3 p3
Here we start with a list l0 which has 4 positions and a labelling f function into X. The
result of reversing l0 is list with 4 positions with labelling function f 0 into X given by the
above composite. In general, we therefore define
Example 6 The reverse program is given by the container morphism (Id, f n ) where the
function f n : {0, . . . , n − 1} → {0, . . . , n − 1} is defined by f n (i) = n − i − 1
Note how f n says that the data stored at the i0th cell after reversing a list is that data
stored at the n − i − 1’th cell in the input list. Here is an example of the tail function
tail:: List(X) → 1 + List(X). The shapes of the datatype 1+List(X) is 1+N with
the shapes above inl(∗) being empty while the shapes above inr(n) is the set {0, . . . , n −
1}. We therefore write this container as (1 + n : N . 0 + n).
Example 7 The tail function (u, f ) is given by the container morphism
(n : N . n) → (1 + n : N . 0 + n) is given by
The last clause says that the i’th cell in the output of a nonempty list comes from the
i + 1’th cell in the input list. The reader may like to check their understanding at this
point by wondering what function is defined by setting f n+1 (i) = i in the above example.
We finish this section with two final points. First, a categorical re-interpretation of a
container morphism analogous to the categorical interpretation of a container A . B as a
presheaf B : A → Sets as in lemma 4.
Set
Proof Since A and C are discrete categories, a functor is just a function. Since A is
discrete, the natural transformation is just a family of maps of the given form. 2
Finally, containers are more than just a tool to program with — they can also simplify
reasoning. For example, consider the problem of proving that reverse . reverse is the
identity. Using the usual recursive definition one soon runs into problems and one has
to strengthen the inductive hypothesis to include properties reflecting the interaction of
reverse and append. For the container definition, this problem is trivial as
Our key theorem is the following which ensures that our syntax for defining polymorphic
functions as container morphisms is flexible enough to cover all polymorphic functions.
Proof The proof is a special case of the one we give later for quotient types in
Theorem 19. Alternatively, see (2; 1) 2
Containers vs Shapely Types: In (10) and (9) shapely types (in one parameter) in
Sets are pullback preserving functors F : Sets → Sets equipped with a cartesian natural
transformation to the list functor. Essentially, this means there are natural maps FX →
List(X) which extracts the data from an element of FX and places it in a list thereby
obtaining a decomposition of data into shapes and positions similar to what occurs in
containers.
Note however that the positions in a list have a notion of order and hence a shapely type
is also equipped with an order on positions. Typically, when we declare a datatype we do
not want to declare such an ordering over it and, indeed, in the course of programming
we may wish to traverse a data structure with different orders. At a more theoretical level,
by reducing datatypes to lists a classification theorem such as Theorem 9 would reduce
polymorphic functions to polymorphic functions between lists but would not be able
to classify what these are. Containers do not impose such an order and instead reduce
datatypes to the more primitive idea of a family of positions indexed by a set of shapes.
The purpose of this paper is to extend our previous results on containers to cover quotient
datatypes and polymorphic functions between them. In particular we want to
• Prove the representation theorem for these polymorphic programs thereby proving
that the quotient container morphisms really are polymorphic functions and that all
such polymorphic functions are captured by quotient container morphisms.
We begin with an example of unordered pairs. Note first that the type X × X is given by
the container (1 . 2). These are ordered pairs of elements of X. The type of unordered
pairs of X is written as X ⊗ X and is defined as X × X/ ∼ where ∼ is the equivalence
relation defined by
hx, yi ∼ hy, xi
Recall our analysis of the pair type was as one position, containing two shapes (1 . 2).
Lets call these positions p1 and p2 . An element of the pair type then consists of a labelling
for these positions, ie a function f : {p1 , p2 } → X for a set X. To move from unordered
pairs to ordered pairs is exactly to note that the labelling f should be regarded the same
as the labelling f ◦ swap where swap : {p1 , p2 } → {p1 , p2 } is the function sending p1 to
p2 and p2 to p1 . Thus
Lets cement our intuitions by doing another example. A cyclic list is a list with no starting
point. Here is a cyclic list of length 5
x1
x5 x2
x4 x3
Can we represent cyclic lists in the same style as we used for unordered pairs? First recall
that lists were given by
List(X) ≡ Σn : N. {0, . . . ..n − 1} → X .
Now, in a cyclic list of length n, a labelling f : {0, . . . ..n − 1} → X should be equivalent
to the labelling f ◦ λ i.(i + k)mod n where k ∈ {0, . . . , n − 1}. Thus we may define
The observant reader will have spotted that, in example 11, there is actually an
equivalence relation for each shape. However for clarity we leave this implicit. Also
we will henceforth write A → B/ ∼ rather than (A → B)/ ∼ for equivalence classes of
functions.
Examples 10 and 11 exemplify the kind of structures we wish to compute with, ie the
structures for which we want to find a clean syntax supporting program construction and
reasoning. In general they consist of a container as defined before with an equivalence
relation on labellings of positions. To ensure the equivalence relation is structural, ie
independent of data as one would expect in a polymorphic setting, the equivalence
relation is defined by identifying a labelling f : P(s) → X with the labelling f ◦ α where
α is one of a given set of isomorphisms on P(s). Hence we define
Lemma 13. A quotient container is exactly a functor P : S → Sets where every morphism
of S is both an endomorphism and an isomorphism
Proof Given a quotient container (S . P/G), we think of S as the category with objects
elements s ∈ S and, as endomorphisms of s, the set G(s). The functor P is then then
obvious functor mapping s to P(s). 2
So a quotient container is like a container except that in the datatype it gives rise to, a
labelling f of positions with data is defined to be the same as the labelling f ◦ g obtained
by bijectively renaming the positions using g ∈ G(s) and then performing the labelling
f . The more abstract formulation is given by the next lemma which uses lemma 13 to
regard a quotient container (S . P/G) as a presheaf P : S → Sets
Lemma 15. Let P : S → Sets be a quotient container. Then TS.P/G is the left Kan
extension of K1 along P
P
S Set
K1
TS.P = LanP K1
Set
= Σ s : S. Sets(Ps, X)/ ∼
= TS.P (X)
where by first equality is the classic coend formula, the second is the reduction of coends
to colimits and the third is the definition of TS.P/G . This is because, the equivalence
relation ∼ in the coend has f ∼ f 0 iff there is a g : s → s such that f 0 = f ◦ P(g) where
f , f 0 : P(s) → X. This is exactly the definition of the extension of a quotient container. 2
The theory of containers thus generalises naturally to quotient containers as the same
formula of left Kan extension calculates both the semantics of a container and that of a
quotient container.
We finish this section on the presentation of quotient types with the example of finite bags
as promised. Bags of other sizes are of course a straightforward generalisation. given this
remark, we henceforth refer to finite bags as simply bags. The key intuition is that a bag
is like a set but elements may have multiple occurrences - one may thus define a bag as
Bag(X) = X → N. By putting all the elements of a bag in a list we get a representation
of the bag but of course there are many such representations since there is no order of
elements in the bag but there is in a list. Thus we get a bijection between bags and lists
quotiented out by all rearrangements of positions. Hence
Example 16 The bag type is the quotient container (S . P/G) where (S . P) is the
container for lists and, for n : N, we take G(n) to be the set of all isomorphisms on
the set {0, . . . , n − 1}
• Since the maps f s : R(us) → P(s) are labellings, the quotient given by H says that
a quotient container morphism (u, f s ) is the same as another quotient container
morphism (u, f s0 ) if for each s ∈ S, there is an h ∈ H(us) such that f = f 0 ◦ h.
• Given a map f s : Q(us) → P(s) and a g ∈ G(s) then the labellings f and g ◦ f should
be regarded as equal as labellings of Q(us). Hence there should be an h ∈ H(us) such
that f ◦ h = g ◦ f .
Hence we define
fs
Q(us) P(s)
h g
Q(us) P(s)
fs
(u, f ) ∼ (u, f 0 ) iff for all s ∈ S, there exists h ∈ H(us) such that f s = fs0 ◦ h
Intuitively, the first condition is precisely the naturality of the quotient container
morphism while the second reflects the fact that labellings are defined upto quotient.
Is this a good definition of a polymorphic program between quotient containers? We
answer them two ways. On a theoretical level we show that all polymorphic programs
can be uniquely captured by such quotient container morphisms while on a practical
level we demonstrate a number of examples.
Lemma 18. The quotient container morphisms (S . P/G) → (Q . R/H) are in one-to-
one bijections with natural transformations K1 → TQ.R/H P where in the latter we regard
P : S → Sets as a presheaf as described in lemma 4.
Proof Such natural transformations are exactly S-indexed maps K1 (s) → TQ.R/H P(s)
which are natural in S. Thus we have maps
1 → Σ q : Q. R(q) → P(s)/ ∼H
Theorem 19. Quotient container morphisms are in one-to-one bijection with natural
transformations between their extensions. Hence there is a full and faithful embedding
T : QCont → [Sets, Sets]
Notice the key role of the left Kan extension here. It identifies a universal property of the
extension of a quotient container which is exactly what is required to prove Theorem 19.
Also note that Theorem 9 is a corollary as there is clearly a full and faithful embedding
of Cont into QCont
We now finish with some examples. Firstly, if (S . P/G) is a container and for each s ∈ S
the group G(s) is a subgroup of H(s), then there is a morphism of quotient containers
(S . P/G) → (S . P/H). Thus
Example 20 The canonical maps List(X) → CList(X) → Bag(X) are polymorphic as,
for a given n ∈ N, they arise as container morphisms from the inclusions of the singleton
group into the group of functions {λ i.i + kmodn|k ∈ {0, . . . , n − 1}} and of this group
into the group of all isomorphisms on {0, . . . , n − 1}
In general this shows how simple our theory is to apply in practise. We simply have one
condition to check!
As for further work, we believe containers are an excellent platform for generic program
and we wish to develop this application of containers. With specific relationship to these
quotient containers, we have begun investigating their application to programming in the
Theory of Species which was Joyal’s motivation for developing analytic functors.
Bibliography
[1] Michael Abbott. Categories of Containers. PhD thesis, University of Leicester,
2003.
[2] Michael Abbott, Thorsten Altenkirch, and Neil Ghani. Categories of containers. In
Andrew Gordon, editor, Proceedings of FOSSACS 2003, number 2620 in Lecture
Notes in Computer Science, pages 23–38. Springer-Verlag, 2003.
[3] Michael Abbott, Thorsten Altenkirch, Neil Ghani, and Conor McBride. Derivatives
of containers. In Martin Hofmann, editor, Typed Lambda Calculi and Applications,
TLCA 2003, number 2701 in Lecture notes in Computer Science, pages 16–30.
Springer, 2003.
[4] Thorsten Altenkirch and Conor McBride. Generic programming within
dependently typed programming. In Generic Programming, 2003. Proceedings
of the IFIP TC2 Working Conference on Generic Programming, Schloss Dagstuhl,
July 2002.
[5] E. S. Bainbridge, Peter J. Freyd, A. Scedrov, and P. J. Scott. Functorial
polymorphism, preliminary report. In Gerard Huet, editor, Logical Foundations
of Functional Programming, chapter 14, pages 315–327. Addison-Wesley, 1990.
[6] Marcelo Fiore, Gordon Plotkin, and Daniele Turi. Abstract syntax and variable
binding (extended abstract). In Proc. 14th LICS Conf., pages 193–202. IEEE,
Computer Society Press, 1999.
[7] Martin Hofmann. On the interpretation of type theory in locally cartesian closed
categories. In CSL, pages 427–441, 1994.
[8] Bart Jacobs. Categorical Logic and Type Theory. Number 141. Elsevier, 1999.
[9] C. Barry Jay. A semantics for shape. Science of Computer Programming, 25:251–
283, 1995.
[10] C. Barry Jay and J. R. B. Cockett. Shapely types and shape polymorphism.
In D. Sannella, editor, Programming Languages and Systems - ESOP ’94: 5th
European Symposium on Programming, U.K., April 1994, Proceedings, Lecture
Notes in Computer Science, pages 302–316. Springer-Verlag, 1994.
[11] André Joyal. Foncteurs analytiques et espéces de structures. In Combinatoire
énumérative, number 1234 in LNM, pages 126 – 159. 1986.
[12] Saunders Mac Lane. Categories for the Working Mathematician. Number 5 in
Graduate Texts in Mathematics. Springer-Verlag, 1971.
[13] M.P.Fiore and T.Leinster. Objects of categories as complex numbers. Advances in
Mathematics., 2004. To appear.