Professional Documents
Culture Documents
Abstract.: Con-Structor Calculus Specialisation Extensions Functorial Type System Arities
Abstract.: Con-Structor Calculus Specialisation Extensions Functorial Type System Arities
C.Barry Jay
University of Technology, Sydney
cbj@it.uts.edu.au
1 Introduction
Generic programming [BS98,Jeu00] applies the key operations of the Bird-Meertens
style, such as mapping and folding to a general class of data structures that includes
initial algebra types for lists and trees. Such operations are at the heart of data ma-
nipulation, so that any improvement here can have a major impact on the size of
programs and the cost of their construction. Most treatments of generic program-
ming either focus on the semantics [MFP91], or use type information to drive the
evaluation [JJ97,Jan00,Hin00]. Functorial ML (fml) [JBM98] showed how evalua-
tion could be achieved parametrically, without reference to types, but was unable
to dene generic functions and so had to represent them as combinators. Such def-
initions require a better understanding of data structures that demonstrates why
pairing builds them but lambda-abstraction does not. The usual approach, based
on introduction-elimination rules for types, does not do so as it derives both pairing
and lambda-abstraction from introduction rules. As data structures are built using
constructors, the challenge is to account for them in a new way. This is done in the
constructor calculus.
Generic programs are polymorphic in the choice of structure used to hold the
data. A second challenge is to represent this polymorphism within a type system.
This requires an account of data types that demonstrates why the product of two
data types is a data type but their function type is not. The functorial type system
will represent a typical data type as the application of a functor F (representing
the structure) to a type (or tuple of types) X representing the data. Functor ap-
plications are the fundamental operations for constructing data types, in the sense
that function types are fundamental for constructing program types. Quantication
over functors will capture polymorphism in the structure.
This last statement is a slight simplication. Dierent functors take dierent
numbers of type arguments, and produce dierent numbers of results. This infor-
mation is captured by giving functors kinds of the form m ! n where m and n are
the arities of the arguments and results. Further, a typical functor is built from a
variety of functors, all of dierent kinds. It follows that a typical generic function
cannot be dened for functors of one kind only, but must be polymorphic in arities,
too. The inability to quantify over arities was the biggest drawback of fml, whose
primitive constants came in families indexed by arities.
In its basic form, the resulting system supports a large class of concrete data
types whose terms, built from the given (nite) set of constructors, can be handled
by generic operations. In practice, however, programmers need to dene their own
(abstract) data types. If these contribute new constructors then it is not at all clear
how generic functions can be applied to them without additional coding. For exam-
ple, when a new data type is dened in Haskell [Aa97] then the various fragments of
code required for mapping, etc. are added by the programmer. This is better than
re-dening the whole function but it is still something less than full genericity.
The solution adopted here is to create the abstract data structures by tagging
the underlying concrete data structures with the appropriate names. Since naming
can be treated in a uniform fashion, we are able to apply existing generic programs
to novel datatypes. Figure 1 contains a (tidied) session from the implementation
of FISh2 language that illustrates some of these ideas. Lines beginning with >-|>
and ending with ;; are input by the programmer. The others are responses from
the system. A datatype of binary trees is declared. This introduces a new functor
tree which takes two arguments. tr : tree(int;
oat) is a small example of such a
tree. tr2 is obtained by mapping the functions f and g over tr. The generic function
map2 is a specialised form of the generic function map whose type is given in (3)
in Section 1.2. Note that even though trees are a new kind of data structure the
mapping algorithm works immediately, without any further coding. The session
concludes with an application of the generic addition function plus which is able to
handle any data structure containing any kind of numerical data such as integers
and
oats.
let equal x y =
match (x; y) with
un; un ! true
j (x0 ; x1 ); (y0 ; y1 ) ! (equal x0 y0 ) && (equal x1 y1 )
j inl x0 ; inl y0 ! equal x0 y0
j inr x0 ; inr y0 ! equal x0 y0
j ! false
2 Kinds
Tuples of types will be characterised by their arities. The absence of any types is
represented by the arity 0, a single type by the arity 1. The pairing of an m-tuple
and an n-tuple of types will have arity (m; n). Hence the arities are generated by
m; n ::= a j 0 j 1 j (m; n)
where a is an arity variable. We will informally denote (1; 1) by 2. Note, however,
that 3 would be ambiguous as (2; 1) and (1; 2) are distinct arities (as are (m; 0) and
m). The importance of this distinction is that we will be able to index types within
a tuple by a sequence of lefts and rights instead of by an integer, and so will only
need a pair of constructors instead of an innite family.
The kinds (meta-variable k) are used to characterise the functors. They are of
the form m ! n where m and n are arities. If F is a functor that acts on m-tuples
of types and produces n-tuples of types then it has kind m ! n.
Arity contexts, substitutions and uniers are dened in the usual way.
3 Functors and their constructors
A single syntactic class represents both functors in general and types. Each functor
F has an associated kind k, written F :: k. The types are dened to be those functors
T whose kind is T :: 0 ! 1. We shall use the meta-variables F; G and H for functors
and T for types. When F :: 0 ! n then it is a tuple of types and we may write its
kinding as F :: n. If all the functors involved in an expression are types we may
omit their kinds altogether. The type schemes (meta-variable S ) are obtained by
quantifying types with respect to both arity variables and kinded functor variables.
The functors and raw type schemes are formally introduced in Figure 3. Let us
introduce them informally rst.
The functors (stripped of their kinds) and type schemes are given by
F; G; T ::= X j P j C j K j (F; G) j L j R j G F j F j F ! G
S ::= T j 8X :: k:S j 8a:S
X represents a functor variable. The nite product functor P has kind P :: m ! 1
for any arity m. When m is 0 then P is a type, namely the unit type. Its constructor
is
intrU : P:
Unfortunately, the constructors do not yet have descriptive names. When m is 1
then P is the unary product. Its constructor is
intrE : 8X: X ! PX:
When m is (p; q) then its constructor is
intrF : 8m; n:8X :: m; Y :: n: PX ! PY ! P (X; Y ):
Thus, the usual, binary pairing is given by pair x y = intrF (intrE x) (intrE y). The
intrE's convert raw data into simple data structures (one-tuples) which are then
combined using intrF. We may write (x; y) for pair x y from now on.
The nite coproduct functor C :: m ! 1 is dual to the product. When m is 0
then C is the empty type, and has no constructor. When m is 1 then the constructor
is
intrC : 8X: X ! CX:
When m is (p; q) then the coproduct has two inclusions
intrA : 8m; n:8X :: m; Y :: n: CX ! C (X; Y )
intrB : 8m; n:8X :: m; Y :: n: CY ! C (X; Y ):
The usual inclusions to the binary coproduct may thus be written as inl x =
intrA (intrC x) and inr y = intrB (intrC y)
The functors P and C convert tuples of types into types. Now we must consider
how to build the former. First we have empty tuples of types, constructed by the
kill functor K :: m ! 0. It is used to convert a type T into a \constant functor"
that ignores its argument. Its constructor is
intrK : 8m:8X :: 1; Y :: m: X ! (XK )Y:
For example, the empty list is built using intrK intrU : (PK )(A; X ) where A is the
type of the list entries and X is the list type itself.
If F :: p ! m and G :: p ! n are functors able to act on the same arguments
then their pairing is (F; G) :: p ! (m; n). There are no constructors for pairs of
functors as they are not types. Rather, we shall have to adapt the constructors to
handle situations in which functor pairing is relevant.
Corresponding to functor pairing we have left and right functor projections
L :: (m; n) ! m and R :: (m; n) ! n with constructors1
intrL : 8m; n:8F :: m ! 1; X :: m; Y :: n: FX ! (FL)(X; Y )
intrR : 8m; n:8F :: n ! 1; X :: m; Y :: n: FY ! (FR)(X; Y ):
They are used to introduce \dummy" functor arguments. For example, to build
leaf x : tree(A; B ) from some term x : A we begin with
intrL (intrL (intrE x)) : ((PL)L) ((A; B ); tree(A; B ))
to convert it into a data structure built from data of type A; B and tree(A; B ).
Application of intrE introduces a functor application which supports the two ap-
plication of intrL. Note that were F to be elided from the type of intrL then the
outermost application above would fail.
The kinding of L and R is made possible by the way the arities are structured.
For example, we have LL(2; 1) ! 1. By contrast, in fml arities are given by natural
numbers which must then be used to index the projection functors such as 03 ::
3 ! 1. This indexing then leaks into the term languages, with onerous results.
If F :: m ! n and G :: n ! p are functors then GF :: m ! p is their composite
functor. When F is a type or tuple of types then we may speak of applying G to
F . Composition associates to the right, so that GFX is to be read as G(FX ). The
associated constructor is
intrG : 8m:8G :: 1 ! 1; F :: m ! 1; X :: m: G(FX ) ! (GF )X:
The restriction on the kind of G is a consequence of the tension between functors
and types discussed in the introduction. It is necessary to be able to dene functions
like map in Section 7. We also need a constructor for handling composites involving
pairs of functors, namely
intrH : 8m:8H ::2 ! 1; F :: m ! 1; G :: m ! 1:X :: m: H (FX; GX ) ! (H (F; G))X:
With the structure available so far we can construct arbitrary polynomial functors.
Now let us consider their initial algebras. For example, lists with entries of type A
are often described as a solution to the domain isomorphism
X= 1+AX
given by X :1+AX . Here X indicates that the smallest solution to the domain iso-
morphism is sought, i.e. the inductive type or initial algebra for the functor F where
F (A; X )
= 1 + A X . We can represent such an F as follows. A X is just P (A; X )
and 1 becomes P which becomes (PK )(A; X ). Thus F = C (PK; P ) :: (1; 1) ! 1.
Now we must represent the initial algebra construction. Instead of introducing a type
variable X only to bind it again, we adopt the convention that it is the second argu-
ment to the functor that represents the recursion variable. That is, if F :: (m; n) ! n
then F :: m ! n. For example, listp = C (PK; P ) :: 1 ! 1 is a functor for lists.
The corresponding constructor is
intrI : 8m:8F :: (m; 1) ! 1; X :: m: F (X; (F )X ) ! (F )X:
For example, the empty list is given by nilp = intrI (intrH (inl (intrK intrU))).
Binary trees can be represented by the functor (PL)L + (PR)L (PR PR)
called treec where (PL)L represents the leaf data, (PR)L represents the node data
and PR represents the sub-trees.
1
In earlier drafts there were four constructors here. The original intrL and intrR have
been dropped and their names taken by the other two.
Let us consider functions between functors, or natural transformations. If F ::
m ! n and G :: m ! n are functors of the same kind then F ! G :: 0 ! n
is their function functor. If X and Y are types then we can build lambda-terms
(x : X ):(t : Y ) : X ! Y in the usual way but x is not a data constructor in the
formal sense employed here.
We can recover a type from F ! G :: 0 ! n by applying the product functor
P :: n ! 1 to get P (F ! G) (as appears in the type for map). Can we build any
terms of such types? If fi : Xi ! Yi for i = 0; 1 then there is a pair
(f0 ; f1) :: P (X0 ! Y0 ; X1 ! Y1 )
whose type is structurally dierent to P ((X0 ; X1 ) ! (Y0 ; Y1 )). The solution adopted
is to exploit semantic insights and assert the type identity
(X0 ; X1) ! (Y0 ; Y1 ) = (X0 ! Y0 ; X1 ! Y1 ):
Arities (m; n)
A ` ::: m A ` ::: n
a in A
A ` ::: a A ` ::: 0 A ` ::: 1 A ` ::: (m; n)
Functor contexts()
A` ` ::: m ` ::: n
n is not a pair, X 62 dom()
A; ` ; X :: m ! n `
Functors and types (F; G; T )
` ` F :: m ! n ` G :: m ! n
(X ) = m ! n
` X :: m ! n ` F ! G :: 0 ! n
` ::: m ` ::: m
` P :: m ! 1 ` C :: m ! 1
` ::: m ` F :: p ! m ` G :: p ! n
` K :: m ! 0 ` (F; G) :: p ! (m; n)
` ::: m ` ::: n ` ::: m ` ::: n
` L :: (m; n) ! m ` R :: (m; n) ! n
` F :: m ! n ` G :: n ! p ` F :: (m; n) ! n
` GF :: m ! p ` F :: m ! n
Type schemes (S )
` T :: 0 ! 1 A; ` A; m; ` S ; F :: m ! n ` S
`T A; ` 8m:S ` 8F :: m ! n:S
4 Terms
The raw terms are given by
t ::= x j t t j x:t j let x = t in t j x(x:t) j c j under c apply t else t:
x is a variable. The application, -abstraction and let-construct all take their stan-
dard meanings. Let g:f be notation for x:g(f x). The xpoint construct supports
xpoints with respect to a type scheme instead of a type, i.e. polymorphic recursion.
We shall often use explicit recursion to represent xpoints. For example, a declara-
tion of the form f x = t where f is free in t stands for x (f:x:t). A term of the
form c t0 : : : tn 1 where c is a constructor taking n arguments is constructed by c.
The lambda-abstractions, extensions and partially applied constants are collectively
known as explicit functions.
A term context is a nite sequence of term variables with assigned type
schemes. A context A; ; consists of a functor context A; and a term context
whose type schemes are all well-formed with respect to A; . The set of free functor
(respectively, arity) variables of is given by the union of the sets of free functor
(respectively arity) variables in the type schemes assigned to the term variables in
.
The closure closure( ; T ) of a type T with respect to a term context is given
by quantifying T with respect to those of its free arity and functor variables which
are not free in (the order of variables will not prove to be signicant).
We have the following judgement forms concerning term contexts and terms.
; ` asserts that ; is a well-formed context. ; ` t : T asserts that t is a
term of type T in the context ; . The type derivation rules for terms are given
in Figure 4. Let us consider them now in turn.
Term Contexts ( )
` ; ` ` S
x 62 dom( )
; ` ; ; x : S `
Terms (t)
; ` (x) = 80 :T ` c : 8c :T : !
; ` x : T : 0 ! ; ` c : T c
; ` t : T1 ! T2 ; ` t1 : T1 ; ; x : T1 ` t : T2
; ` t t1 : T2 ; ` x:t : T1 ! T2
; ` ; 1 ; ` t1 : T1 ; ; x : 81 :T1 ` t2 : T2
; ` let x = t1 in t2 : T2
; ` ; 1 ; ; x : 81 :T ` t : T
: 1 !
; ` x(x:t) : T
` c : 8c:T0 ! : : : ! Tn 0 0; ` t2 : T ! T 0
= U (Tn ; T ) : c ! ; ` t1 : (T0 ! : : : Tn 1 ! T 0 )
; ` under c apply t1 else t2 : T ! T 0
Fig. 4. Terms of the constructor calculus
5 Evaluation
The basic reduction rules are represented by the relation > in Figure 5. A reduction
t ! t0 is given by the application of a basic reduction to a sub-term. All of the
reduction rules are standard except those for extensions. Reduction of extensions
amounts to deciding whether to specialise or not. A term t cannot be constructed
by a constructor c if it is an explicit function, or is constructed by some constructor
other than c.
6 Exceptions
Any account of data types must address the issue of missing data. For example,
taking the head of an empty list. In imperative languages this often results in a
void pointer. In pointer-free languages like ML or Java such problems are addressed
by introducing exceptions. These
ag problems during evaluation and may change
the
ow of control in ways that are dicult to specify and understand. Exceptions
may be caught and handled using any arguments to the exception as parameters.
Exceptions arise here when an extension is applied to an argument of the wrong
form, either constructed by the wrong constructor or an explicit function. The
solution is to add one more constructor
exn : 8X; Y:X ! Y
which represents an exception that carries an argument. As exn is a constructor it
can be handled using the extension mechanism without additional machinery. The
only issue is that the exception may produce a function, rather than data. This
would be bad programming style but can be handled by introducing one additional
evaluation rule
exn s t > exn s
Rewriting is still con
uent because the left-hand side is not a term constructed by
exn as it is applied to two arguments, not one.
7 Examples
Let us begin with a generic equality relation. We can use C (P; P ) to represent a
type bool of booleans with true = inl intrU and false = inr intrU and let && be
an inx form of conjunction, dened by nested extensions. In the generic test for
equality it is not necessary that the arguments have the same type, though this will
be the case if they are indeed equal. So the type for equality is X ! Y ! bool.
The program is given in Figure 6. Each pattern in the program corresponds to one
extension. For example,
j intrE x ! under intrE apply equal x else y:false
represents under intrE apply x:under intrE apply equal x else y:false else while the
nal pattern of the form j ! t represents the default function x:t. Obviously,
the algorithm is quite independent of the nature of the individual constructors, one
of the reasons that equality can sometimes be treated by ad hoc methods.
(equal : X ! Y ! bool) z =
match z with
intrU ! under intrU apply true else y:false
j intrE x ! under intrE apply equal x else y:false
j intrF x0 x1 ! under intrF apply y0 ; y1 :(equal x0 y0 ) && (equal x1 y1 ) else
y:false
j intrC x ! under intrC apply equal x else y:false
j intrA x ! under intrA apply equal x else y:false
j intrB x ! under intrB apply equal x else y:false
j intrK x ! under intrK apply equal x else y:false
j intrL x ! under intrL apply equal x else y:false
j intrR x ! under intrR apply equal x else y:false
j intrG x ! under intrG apply equal x else y:false
j intrH x ! under intrH apply equal x else y:false
j intrI x ! under intrI apply equal x else y:false
j ! x; y:false
Fig. 6. Dening equality by extension
Before tackling more complex examples like mapping, we shall require a little
more infrastructure. In particular, we shall require eliminators corresponding to the
constructors. Happily, these can be dened by extensions. For example,
elimE = under intrE apply x:x else exn intrE : PX ! X
is the eliminator corresponding to intrE. If applied to some intrE t then it returns t.
Otherwise an exception results.
In general each constructor has eliminators corresponding to the number of its
arguments. intrU has no eliminator. Those for intrF are given by
elimF0 = under intrF apply x; y:x else exn (intrF; 0) : P (X; Y ) ! PX
elimF1 = under intrF apply x; y:y else exn (intrF; 1) : P (X; Y ) ! PY
We can dene the usual projections for pairs by fst = elimE:elimF0 and snd =
elimE:elimF1. If intrZ : T ! T 0 is any other constructor then its eliminator is
elimZ = under intrZ apply x:x else exn intrZ : T 0 ! T:
The algorithm for mapping is fairly complex. Recall that the type for mapping
is
map : 8m:8F :: m ! 1; X :: m; Y :: m:P (X ! Y ) ! FX ! FY
If m is 1 and f : X ! Y is an ordinary function then intrE f : P (X ! Y ) is the
corresponding one-tuple and map (intrE f ) has type FX ! FY . When applied to
intrE x then semantically the expected result is
map (intrE f ) (intrE x) = intrE (f x)
as x is of the type to which f is to be applied, and then intrE is applied to create a
one-tuple, having the same structure as the original argument.
If m is (m0 ; m1 ) then we need a pair of functions fi : PXi ! PYi for i = 0; 1.
When applied to a term of the form intrF x0 x1 then xi : PXi and we get
map (intrF f0 f1 ) (intrF x0 x1 ) = intrF (map f0 x0 ) (map f1 x1 ):
Note that it is necessary to map f0 and f1 across x0 and x1 . Putting these two rules
together for pairs yields
map (f0 ; f1) (x0 ; x1 ) = (f0 x0 ; f1 x1 ):
The program in Figure 7 represents such semantic equations within extensions,
but replaces the explicit structures given to the functions by the appropriate elim-
inators for tuples. Note how exceptions carry both the mapping function and the
argument as a pair. In particular, if z has evaluated to an exception then that is
nested within the exception generated by the mapping. This gives detailed account
of where the error has occurred which can be handled in sophisticated ways. We
can customise map for any particular arity as in
map1 f = map (intrE f ) : (X ! Y ) ! FX ! FY
map2 f g = map (f; g) : (X0 ! Y0 ) ! (X1 ! Y1 ) ! F (X0 ; X1 ) ! F (Y0 ; Y1 ):
(map : P (X ! Y ) ! F X ! F Y ) f z =
match z with
intrE x ! intrE (elimE f x)
j intrF x y ! intrF (map (elimF0 f ) x) (map (elimF1 f ) y)
j intrC x ! intrC (elimE f x)
j intrA x ! intrA (elimF0 f x)
j intrB y ! intrB (elimF1 f y)
j intrK x ! intrK x
j intrL x ! intrL (map (elimF0 f ) x)
j intrR y ! intrR (map (elimF1 f ) y)
j intrG x ! intrG (map (intrE (map f )) x)
j intrH x ! intrH(map (map f; map f ) x)
j intrI x ! intrI (map (f; map f ) x)
j ! exn (map f; z)
Fig. 7. Denition of map
8 Type inference
The use of polymorphic recursion and extension mean that not every term has a
principal type. The issues for polymorphic recursion are already well explored, e.g.
[Hen93]. When inferring a type for x(x:t) there are many choices of type scheme
which could produce the necessary type. A similar problem arises with extensions.
Nevertheless, the constructor calculus supports a powerful type inference mech-
anism. For example, it is able to type all of the generic functions in Section 7. It
suces to specify the overall type of a generic function or polymorphic recursion
when it is dened, but not when it is used. Given the complexity of generic functions,
it is a good idea to supply explicit types at the point of denition.
Type inference for an extension under c apply t1 else t2 is complicated by several
factors. Inferring a type for t2 produces a substitution 2 . Unication of the result
type for c and the argument type for t2 produces a substitution . Then inferring
a type for t1 (in the context determined by 2 ) produces a third substitution 1 .
The nal result should be 1 2 without including but this will only make sense
if 1 does not act on any variables introduced by . The solution is to treat such
variables as additional constant functors during the type inference for t1 so that 1
does not act on them.
The type inference algorithm W takes a context ; and a term t and a type
T whose free variables are in the context (the type may be initialised to be a fresh
variable). If successful it returns a functor substitution : ! 0 such that
0 ; ` t : T . The case of extensions is handled here. Details of the algorithm
may be found in the technical report [Jay00].
Let t be under c apply t1 else t2 . Let 8c:T0 ! : : : ! Tn be the given type
scheme for c. Let 2 : ! 2 be W (; ; t2 ; T ). If 2 T is not a function type then
the algorithm fails, so assume that it is some T 0 ! T 00. Let : 2 ; c ! 3 be
U (Tn ; T 0). Now treat the type and arity variables introduced by (i.e. appearing in
(T 0) but not in T 0 itself) as if they are constants that cannot be substituted while
computing 1 : 3 ! 1 = W (3 ; 2 ; t1 ; 2 (T0 ! : : : ! Tn 1 ! T 00)). The
result is 1 2 .
Theorem 5. Type inference is correct: if W (; ; t; T ) = : ! 0 then 0 ; `
t : T .
Proof. The proof is by induction on the structure of t and relies heavily on Lemma 2.
Again, we will only consider extensions here.
Let t be under c apply t1 else t2 . Let = 1 2 . The derivation is:
` c : 8c:T0 ! : : : ! Tn
1 ; ` t2 : (T 0 ! T 00)
0 = U (Tn ; T 0 ) : c ; 1 ! 4
4 ; 0 ` t1 : 0 (T0 ! : : : ! Tn 1 ! T 00 )
2 ; ` under c apply t1 else t2 : T
That 0 exists follows because 1 is a unier for Tn and T 0 because
1 T 0 = 1 1 T 0 (2 does not act on T 0 )
= 1 T 0 (1 does not act on variables introduced by )
= 1 Tn ( is the most general unier)
The last premise holds as 0 unies Tn and T 0 and so factors through .
9 Abstract datatypes
This section addresses the creation of types by users, such as the tree functor dened
in Figure 1. It is easy to introduce new functors and constants { the challenge is
to support them in existing generic programs. For example, if we introduce new
constants leaf and node for the user-dened trees then the generic mapping algorithm
in Figure 7 will not be able to handle them. Of course, we could write new patterns
for the new constants but this defeats the purpose of genericity.
The solution begins with the observation that the user-dened functors are al-
ways isomorphic to existing \concrete" functors, e.g. tree is isomorphic to treec .
The point of creating a new functor is to create a new, separate class of data struc-
tures distinguished from the others by their names. So we shall introduce a single
new constructor called intrT whose arguments will be a name and a data structure,
each of which can be handled in a uniform way. For example, let leaf c and nodec
be the concrete versions of leaf and node. Then tree name : treec ! tree is used to
name trees so that we can dene
leaf = intrT tree name leaf c :
intrT has type 8F :: m ! 1; G :: m ! 1; X :: m: (F ! G) ! (FX ! GX ). That is,
it takes a natural transformation r : F ! G (the name) to form a tag intrT r which
when applied to a term t : FX produces a tagged term of type GX .
Now all tags can be treated in a uniform way. For example, mapping over tagged
terms is given by
map f (intrT r t) = intrT r (map f t):
10 Datum types
Introduce some primitive datum types (meta-variable primD) such as primint and
prim
oat to represent unstructured data, equipped with a suite of primitive datum
values and operations, e.g. primplusint and primplus
oat. Now dene the correspond-
ing data types by
datatype int = int primint
datatype
oat =
oat prim
oat:
These can now be used in generic functions, like plus, dened in Figure 9.
(plus : X ! X ! X ) x y =
match (x; y) with
(int x0 ; int y0 ) ! int (plusprimint x0 y0 )
j (
oat x0 ;
oat y0 ) !
oat (plusprim
oat x0 y0 )
j (intrU; intrU) ! intrU
j (intrE x0 ; intrE y0 ) ! intrE:(plus x0 y0 )
j (intrF x0 x1 ; intrF y0 y1 ) ! intrF (plus x0 y0 ) (plus x1 y1 )
j :::
11 Conclusions
The constructor calculus with its functorial type system is able to dene and type
a wide range of generic functions. The type system is based on a class of functors
which is similar to that of fml, but is supported by a system of polymorphic kinds
that eliminates most of the need for explicit arities. Program extension is the truly
novel contribution of the paper. It shows how generic programs can be incorporated
within the typed lambda-calculus by giving a type derivation rule for extensions.
Evaluation does not require type information, so there is no need for programmers
to supply it except to aid the type inference mechanism, when dening complex
functions.
All of the ideas in this paper have been tested during development of the pro-
gramming language FISh2. In particular, all of the generic programs in the paper
have been created, type-checked and evaluated therein. The ability to create such
new and powerful programs shows the expressive power of this addition to the typed
lambda-calculus.
The focus of this paper has been to demonstrate the expressive power of this
approach in a purely functional setting. The successor to this paper will show how
to add imperative features to the calculus so that we may dene generic programs
such as
assign : 8X:loc X ! X ! comm
where loc X represents a location for a value of type X and comm is a type of
commands. In the process we will gain some insights into the optimisation techniques
for reducing the execution overhead of generic programs, again based on our new
understanding of constructors.
Acknowledgements The Universite de Paris VII and the University of Edinburgh
each hosted me for a month while working on this material, the latter by EPSRC vis-
iting fellowship No. GR/M36694. I would like to thank my hosts Pierre-Louis Curien
and Don Sannella for making these visits so pleasant and productive. This work
has also proted from discussions with many other colleagues, especially Manuel
Chakravarty, Peter Dybyer, Martin Hofmann, Gabi Keller, Hai Yan Lu, Eugenio
Moggi, Jens Palsberg, Gilles Peskine and Bob Tennent.
References
[Aa97] L Augustsson and all. Report on the functional programming language Haskell:
version 1.4. Technical report, University of Glasgow, 1997.
[ACPR95] M. Abadi, L. Cardelli, B.C. Pierce, and D. Remy. Dynamic typing in polymor-
phic languages. Journal of Functional Programming, 5(1):111{130, 1995.
[BS98] R. Backhouse and T. Sheard, editors. Workshop on Generic Programming:
Marstrand, Sweden, 18th June, 1998. Chalmers University of Technology, 1998.
[CF92] J.R.B. Cockett and T. Fukushima. About Charity. Technical Report 92/480/18,
University of Calgary, 1992.
[Coc90] J.R.B. Cockett. List-arithmetic distributive categories: locoi. Journal of Pure
and Applied Algebra, 66:1{29, 1990.
[Ga77] J.A. Goguen and all. Initial algebra semantics and continuous algebras. Journal
of the Association for Computing Machinery, 24:68{95, 1977.
[GLT89] J-Y. Girard, Y. Lafont, and P. Taylor. Proofs and Types. Tracts in Theoretical
Computer Science. Cambridge University Press, 1989.
[Hen93] F. Henglein. Type inference with polymorphic recursion. ACM Trans. on Progr.
Lang. and Sys., 15:253{289, 1993.
[Hin00] R. Hinze. A new approach to generic functional programming. In Proceed-
ings of the 27th Annual ACM SIGPLAN-SIGACT Symposium on Principles of
Programming Languages, Boston, Massachusetts, January 19-21, 2000, 2000.
[Hyl82] J.M.E. Hyland. The eective topos. In A.S. Troelstra and D. van Dalen, editors,
The L.E.J. Brouwer Centenary Symposium. North Holland, 1982.
[Jan00] P. Jansson. Functional Polytypic Programming. PhD thesis, Chalmers Univer-
sity, 2000.
[Jay95a] C.B. Jay. Polynomial polymorphism. In R. Kotagiri, editor, Proceedings of the
Eighteenth Australasian Computer Science Conference: Glenelg, South Australia
1{3 February, 1995, volume 17, pages 237{243. A.C.S. Communications, 1995.
[Jay95b] C.B. Jay. A semantics for shape. Science of Computer Programming, 25:251{
283, 1995.
[Jay96] C.B. Jay. Data categories. In M.E. Houle and P. Eades, editors, Computing:
The Australasian Theory Symposium Proceedings, Melbourne, Australia, 29{30
January, 1996, volume 18, pages 21{28. Australian Computer Science Commu-
nications, 1996. ISSN 0157{3055.
[Jay00] C.B. Jay. Distinguishing data structures and functions: the construc-
tor calculus and functorial types. http://www-staff.it.uts.edu.au/
~cbj/Publications/constructors.ps, 2000.
[JBM98] C.B. Jay, G. Belle, and E. Moggi. Functorial ML. Journal of Functional Pro-
gramming, 8(6):573{619, 1998.
[Jeu00] J. Jeuring, editor. Proceedings: Workshop on Generic Programming
(WGP2000): July 6, 2000, Ponte de Lima, Portugal. Utrecht University, UU-
CS-2000-19, 2000.
[JJ97] P. Jansson and J. Jeuring. PolyP - a polytypic programming language extension.
In POPL '97: The 24th ACM SIGPLAN-SIGACT Symposium on Principles of
Programming Languages, pages 470{482. ACM Press, 1997.
[Klo80] J.W. Klop. Combinatory Reduction Systems. PhD thesis, Mathematical Center
Amsterdam, 1980. Tracts 129.
[MFP91] E. Meijer, M. Fokkinga, and R. Paterson. Functional programming with ba-
nanas, lenses, envelopes and barbed wire. In J. Hughes, editor, Procceding of
the 5th ACM Conference on Functional Programming and Computer Architec-
ture, volume 523 of Lecture Notes in Computer Science, pages 124{44. Springer
Verlag, 1991.
[Mil78] R. Milner. A theory of type polymorphism in programming. JCSS, 17, 1978.
[MT91] R. Milner and M. Tofte. Commentary on Standard ML. MIT Press, 1991.
[Rey85] J. Reynolds. Types, abstraction, and parametric polymorphism. In R.E.A.
Mason, editor, Information Processing '83. North Holland, 1985.