The Elf Programming Language: in Fairy Land
The Elf Programming Language: in Fairy Land
83
84 CHAPTER 4. THE ELF PROGRAMMING LANGUAGE
a type inference procedure for the language? Another natural question concerns
the operational semantics: once specified as a deductive system, how can we take
advantage of this specification to obtain an interpreter for the language? In both
of these cases we are in a situation where algorithms are known and need to be
implemented. The problem of proof search can also be phrased in these terms:
given a logical system, implement algorithms for proof search that are appropriate
to the system at hand.
Our approach to the implementation of algorithms is inspired by logic program-
ming: specifications and programs are written in the same language. In traditional
logic programming, the common basis for specifications and implementations has
been the logic of Horn clauses; here, the common basis will be the logical framework
LF. We would like to emphasize that specifications and programs are generally not
the same: many specifications are not useful if interpreted as programs, and many
programs would not normally be considered specifications. In the logic program-
ming paradigm, execution is the search for a derivation of some instance of a query.
The operational semantics of the logic programming language specifies precisely
how this search will be performed, given a list of inference rules that constitute
the program. Thus, if one understands this operational reading of inference rules,
the programmer can obtain the desired execution behavior by defining judgments
appropriately. We explain this in more detail in Section ?? and investigate it more
formally in Chapter ??.
Elf is a strongly typed language, since it is directly based on LF. The Elf
interpreter must thus perform type reconstruction on programs and queries before
executing them. Because of the complex type system of LF, this is a non-trivial task.
In fact, it has been shown by Dowek [Dow93] that the general type inference problem
for LF is undecidable, and thus not all types may be omitted from Elf programs.
The algorithm for type reconstruction which is used in the implementation [Pfe91a,
Pfe94] is based on the same constraint solving algorithm employed during execution.
The current implementation of Elf is within the Twelf system [PS99]. The reader
should consult an up-to-date version of the User’s Guide for further information
regarding the language, its implementation, and its use. Sources, binaries for various
architectures, examples, and other materials are available from the Twelf home
page [Twe98].
overloading in the concrete syntax for Elf and refer to expressions from any of the
three levels collectively as terms. A signature is given as a sequence of declarations.
We describe here only the core language which corresponds very closely to LF.
The main addition is a form of declaration id : term 1 = term 2 that introduces an
abbreviation id for term 2 .
The terminal id stands either for a bound variable, a free variable, or a constant
at the level of families or objects. Bound variables and constants in Elf can be
arbitrary identifiers, but free variables in a declaration or query must begin with an
uppercase letter (a free, undeclared lowercase identifier is flagged as an undeclared
constant). An uppercase identifier is one which begins with an underscore _ or
a letter in the range A through Z; all others are considered lowercase, including
numerals. Identifiers may contain all characters except (){}[]:.% and whitespace.
In particular, A->B would be a single identifier, while A -> B denotes a function
type. The left-pointing arrow as in B <- A is a syntactic variant and parsed into the
same representation as A -> B. It improves the readability of some Elf programs.
Recall that A -> B is just an abbreviation for {x:A} B where x does not occur in
B.
The right-pointing arrow -> is right associative, while the left-pointing arrow <-
is left associative. Juxtaposition binds tighter than the arrows and is left associative.
The scope of quantifications {x : A} and abstractions [x : A] extends to the next
closing parenthesis, bracket, brace or to the end of the term. Term reconstruction
fills in the omitted types in quantifications {x} and abstractions [x] and omitted
types or objects indicated by an underscore _ (see Section 4.2). In case of essential
ambiguity a warning or error message results.
Single-line comments begin with % and extend through the end of the line. A
delimited comment begins with %{ and ends with the matching }%, that is, delimited
comments may be properly nested. The parser for Elf also supports infix, prefix,
and postfix declarations.
86 CHAPTER 4. THE ELF PROGRAMMING LANGUAGE
z : exp.
s : exp -> exp.
case : exp -> exp -> (exp -> exp) -> exp.
pair : exp -> exp -> exp.
fst : exp -> exp.
snd : exp -> exp.
lam : (exp -> exp) -> exp.
app : exp -> exp -> exp.
letv : exp -> (exp -> exp) -> exp.
letn : exp -> (exp -> exp) -> exp.
fix : (exp -> exp) -> exp.
The declaration %name exp E x. indicates to Elf that fresh variables of type
exp which are created during type reconstruction or search should be named E, E1,
E2, etc.
Next, we turn to the signature defining evaluations. Here are three declarations
as they appear on page 56.
is represented in Elf as
ev_case_z z (s z) ([x:exp] z) (s z) ev_z (ev_s z z ev_z).
The Elf implementation performs type checking and reconstruction; later we will
see how the user can also initiate search. In order to check that the object above
represents a derivation of case z of z ⇒ s z | s x ⇒ z, we construct an anonymous
definition
_ = ev_case_z z (s z) ([x:exp] z) (s z) ev_z (ev_s z z ev_z)
: eval (case z (s z) ([x:exp] z)) (s z).
The interpreter re-prints the declaration, which indicates that the given judgment
holds, that is, the object to the left of the colon has type type to the right of the
colon in the current signature. The current signature is embodied in the state of
the Twelf system and comprises all loaded files. Please see the Twelf User’s Guide
for details.
We now reconsider the declaration of ev_case_z. The types of E1, E2, E3, and
V are unambiguously determined by the kind of eval and the type of case. For
example, E1 must have type exp, since the first argument of eval must have type
exp. This means, the declaration of ev_case_z could be replaced by
ev_case_z :
{E1} {E2} {E3} {V}
eval E1 z -> eval E2 V -> eval (case E1 E2 E3) V.
It will frequently be the case that the types of the variables in a declaration can
be determined from the context they appear in. To abbreviate declarations further
we allow the omission of the explicit Π-quantifiers. Consequently, the declaration
above can be given even more succinctly as
ev_case_z : eval E1 z -> eval E2 V -> eval (case E1 E2 E3) V.
This second step introduces a potential problem: the order of the quantifiers is not
determined by the abbreviated declaration. Therefore, we do not know which argu-
ment to ev_case_z stands for E1, which for E2, etc. Fortunately, these arguments
(which are objects) can be determined from the context in which ev_case_z occurs.
Let E1, E2, E3, V, E’ and V’ stand for objects yet to be determined and consider
the incomplete object
88 CHAPTER 4. THE ELF PROGRAMMING LANGUAGE
where D1 , D2 , and v are still to be filled in. Thus computation in Elf corresponds to
bottom-up search for a derivation of a judgment. We solve the currently unsolved
subgoals going through the partial deduction in a depth-first, left-to-right manner.
So the next step would be to solve
?- D1 : eval z z.
We see that only one inference rule can apply, namely ev z, instantiating D1 to
ev_z. Now the subgoal D2 can be matched against the type of ev_s, leading to the
further subgoal
?- D3 : eval z V1.
D3
z ,→ v1
ev z ev s
z ,→ z s z ,→ s v1
ev case z.
(case z of z ⇒ s z | s x ⇒ z) ,→ s v1
D = ev_case_z D1 D2
D2 = ev_s D3,
V = s V1,
D3 = ev_z,
V1 = z,
D1 = ev_z.
Eliminating the intermediate variables we obtain the same answer that Elf would
return.
V = s z,
D = ev_case_z ev_z (ev_s ev_z).
One can see that the matching process which is required for this search procedure
must allow instantiation of the query as well as the declarations. The problem of
finding a common instance of two terms is called unification. A unification algorithm
for terms in first-order logic was first sketched by Herbrand [Her30]. The first full
description of an efficient algorithm for unification was given by Robinson [Rob65],
4.3. A MINI-ML INTERPRETER IN ELF 91
which has henceforth been a central building block for automated theorem prov-
ing procedures and logic programming languages. In Elf, Robinson’s algorithm is
not directly applicable because of the presence of types and λ-abstraction. Huet
showed that unification in the simply-typed λ-calculus is undecidable [Hue73], a
result later sharpened by Goldfarb [Gol81]. The main difficulty stems from the
notion of definitional equality, which can be taken as β or βη-convertibility. Of
course, the simply-typed λ-calculus is a subsystem of the LF type theory, and thus
unifiability is undecidable for LF as well. A practical semi-decision procedure for
unifiability in the simply-typed λ-calculus has been proposed by Huet [Hue75] and
used in a number of implementations of theorem provers and logic programming
languages [AINP88, Pfe91a, Pau94]. However, the procedure has the drawback
that it may not only diverge but also branch, which is difficult to control in logic
programming. Thus, in Elf, we have adopted the approach of constraint logic pro-
gramming languages first proposed by Jaffar and Lassez [JL87], whereby difficult
unification problems are postponed and carried along as constraints during execu-
tion. We will say more about the exact nature of the constraint solving algorithm
employed in Elf in Section ??. In this chapter, all unification problems encountered
will be essentially first-order.
We have not payed close attention to the order of various operations during
computation. In the first approximation, the operational semantics of Elf can be
described as follows. Assume we are given a list of goals A1 , . . . , An with some
free variables. Each type of an object-level constant c in a signature has the form
Πy1 :B1 . . . Πym :Bm . C1 → · · · → Ck → C, where C is an atomic type. We call C
the target type of c. Also, in analogy to logic programming, we call c a clause, C the
head of the clause c. Recall, that some of these quantifiers may remain implicit in
Elf. We instantiate y1 , . . . , ym with fresh variables Y1 , . . . , Ym and unify the resulting
instance of C 0 with A1 , trying each constant in the signature in turn until unification
succeeds. Unification may instantiate C1 , . . . , Ck to C10 , . . . , Ck0 . We now set these
up as subgoals, that is, we obtain the new list of goals Ck0 , . . . , C10 , A2 , . . . , An . The
object we were looking for will be c Y1 . . . Yn M1 . . . Mk , where M1 , . . . , Mk are the
objects of type C10 , . . . , Ck0 , respectively, yet to be determined. We say that the goal
A1 has been resolved with the clause c and refer to the process as back-chaining.
Note that the subgoals will be solved “from the inside out,” that is, Ck0 is the first
one to be considered. If unification should fail and no further constants are available
in the signature, we backtrack, that is, we return to the most recent point where a
goal unified with a clause head (that is, a target type of a constant declaration in
a signature) and further choices were available. If there are no such choice points,
the overall goal fails.
Logic programming tradition suggests writing the (atomic) target type C first
in a declaration, since it makes is visually much easier to read a program. We
follow the same convention here, although the reader should keep in mind that
A -> B and B <- A are parsed to the same representation: the direction of the arrow
92 CHAPTER 4. THE ELF PROGRAMMING LANGUAGE
ev app : ΠE1 :exp. ΠE2 :exp. ΠE10 :exp → exp. ΠV2 :exp. ΠV :exp.
eval E1 (lam E10 )
→ eval E2 V2
→ eval (E10 V2 ) V
→ eval (app E1 E2 ) V.
As before, we transcribe this (and the trivial rule for evaluating λ-expressions) into
Elf.
ev_lam : eval (lam E) (lam E).
val_z : value z.
val_s : value (s E) <- value E.
val_pair : value (pair E1 E2) <- value E1 <- value E2.
val_lam : value (lam E).
This signature can be used as a program to decide if a given expression is a
value. For example,
?- value (pair z (s z)).
Empty Substitution.
More? n
?- value (fst (pair z (s z))).
no
?- value (lam [x] (fst x)).
Empty Substitution.
More? y
No more solutions
Here we use a special query form that consists only of a type A, rather than a typing
judgment M : A. Such a query is interpreted as X : A for a new free variable X
whose instantiation will not be shown with in the answer substitution. In many
cases this query form is substantially more efficient than the form M : A, since the
interpreter can optimize such queries and does not construct the potentially large
object M .
4.4. AN IMPLEMENTATION OF VALUE SOUNDNESS 97
D P
=⇒
e ,→ v v Value
which relates every D to some P and whose definition is based on the structure
of D. This judgment is then represented in LF as a type family vs, following the
judgments-as-types principle.
Each of the various cases in the induction proof gives rise to one inference rule
for the =⇒ judgment, and each such inference rule is represented by a constant
declaration in LF. We illustrate the Elf implementation with the case where D
ends in the rule ev fst and then present the remainder of the signature in Elf more
tersely.
Case:
D0
0
e ,→ hv1, v2i
D= ev fst.
fst e0 ,→ v1
P1 P2
v1 Value v2 Value
P0 = val pair
hv1 , v2 i Value
P1 P2
D 0 v1 Value v2 Value
=⇒ val pair
e0 ,→ hv1 , v2 i hv1 , v2 i Value
vs fst
D0
e ,→ hv1 , v2 i P1
ev fst =⇒
0 v1 Value
fst e ,→ v1
This may seem unwieldy, but Elf’s type reconstruction comes to our aid. In the
declaration of vs, the quantifiers on E and V can remain implicit:
vs : eval E V -> value V -> type.
The corresponding arguments to vs now also remain implicit. We also repeat the
declarations for the inference rules involved in the deduction above.
ev_fst : eval (fst E) V1 <- eval E (pair V1 V2).
val_pair : value (pair E1 E2) <- value E1 <- value E2.
Here is the declaration of the vs fst constant:
vs_fst : vs (ev_fst D’) P1 <- vs D’ (val_pair P2 P1).
Note that this declaration only has to deal with deductions, not with expressions.
Term reconstruction expands this into
vs_fst :
{E:exp} {E1:exp} {E2:exp} {D’:eval E (pair E1 E2)}
{P2:value E2} {P1:value E1}
vs E (pair E1 E2) D’ (val_pair E2 E1 P2 P1)
-> vs (fst E) E1 (ev_fst E E1 E2 D’) P1.
Disregarding the order of quantifiers and the choice of names, this is the LF dec-
laration given above. We show the complete signature which implements the proof
of value soundness without further comment. The declarations can be derived from
the material and the examples in Sections 2.4 and 3.7.
4.4. AN IMPLEMENTATION OF VALUE SOUNDNESS 99
% Natural Numbers
vs_z : vs (ev_z) (val_z).
vs_s : vs (ev_s D1) (val_s P1)
<- vs D1 P1.
vs_case_z : vs (ev_case_z D2 D1) P2
<- vs D2 P2.
vs_case_s : vs (ev_case_s D3 D1) P3
<- vs D3 P3.
% Pairs
vs_pair : vs (ev_pair D2 D1) (val_pair P2 P1)
<- vs D1 P1
<- vs D2 P2.
vs_fst : vs (ev_fst D’) P1
<- vs D’ (val_pair P2 P1).
vs_snd : vs (ev_snd D’) P2
<- vs D’ (val_pair P2 P1).
% Functions
vs_lam : vs (ev_lam) (val_lam).
vs_app : vs (ev_app D3 D2 D1) P3
<- vs D3 P3.
% Definitions
vs_letv : vs (ev_letv D2 D1) P2
<- vs D2 P2.
vs_letn : vs (ev_letn D2) P2
<- vs D2 P2.
% Recursion
vs_fix : vs (ev_fix D1) P1
<- vs D1 P1.
This signature can be used to transform evaluations into value deductions. For
example, the evaluation of case z of z ⇒ s z | s x ⇒ z considered above is given
by the Elf object
ev_case_z (ev_s ev_z) ev_z
of type
eval (case z (s z) ([x:exp] z)) (s z).
100 CHAPTER 4. THE ELF PROGRAMMING LANGUAGE
We can transform this evaluation into a derivation which shows that s z is a value:
?- vs (ev_case_z (ev_s ev_z) ev_z) P.
P = val_s val_z.
The sequence of subgoals considered is
?- vs (ev_case_z (ev_s ev_z) ev_z) P.
% Resolved with clause vs_case_z
?- vs (ev_s ev_z) P.
% Resolved with clause vs_s [with P = val_s P1]
?- vs ev_z P1.
% Resolved with clause vs_z [with P1 = val_z]
This approach to testing the meta-theory is feasible for this simple example. As
evaluations become more complicated, however, we would like to use the program
for evaluation to generate a appropriate derivations and then transform them. This
form of sequencing of computation can be achieved in Elf by using the declaration
%solve c : A. This will solve the query A obtain the first solution A’ with proof
term M and then making the definition c : A’ = M. Later queries can then refer to
c. For example,
%solve d0 : eval (case z (s z) ([x:exp] z) (s z)).
%query 1 * vs d0 P.
will construct d0 and then transform it to a value derivation using the higher-level
judgment vs that implements value soundness.
for an atomic type C, introduces logic variables X1 , . . . , Xn , then finds most general
common instance between the goal G and C (possibly instantiating X1 , . . . , Xn and
then solves Bm , . . . , B1 as subgoals, in that order. Note that in practical programs,
the quantifiers on x1 , . . . , xn are often implicit.
When writing a program it is important to kept this interpretation in mind. In
order to illustrate it, we write some simple programs.
First, the declaration of natural numbers. We declare s, the successor function,
as a prefix operator so we can write 2, for example, as s s 0 without additional
parentheses. Note that without the prefix declaration this term would be associated
to the left and parsed incorrectly as ((s s) 0).
nat : type. %name nat N.
0 : nat.
s : nat -> nat. %prefix 20 s.
The prefix declaration has the general form %prefix prec id 1 . . . id n and gives the
constants id 1 , . . . , id n precedence prec. The second declaration introduces lists of
natural numbers. We declare “;” as a right-associative infix constructor for lists.
list : type. %name list L.
nil : list.
; : nat -> list -> list. %infix right 10 ;.
For example, (0 ; s 0 ; s s 0 ; nil) denotes the list of the first three natural
numbers; (0 ; ((s 0) ; ((s (s 0)) ; nil))) is its fully parenthesized version,
and (; 0 (; (s 0) (; (s (s 0)) nil))) is the prefix form which would have to
be used if no infix declarations had been supplied.
The definition of the append program is straightforward. It is implemented
as a type family indexed by three lists, where the third list must be the result of
appending the first two. This can easily be written as a judgment (see Exercise 4.6).
append : list -> list -> list -> type.
%mode append +L +K -M.
specifies that, for the operational reading, we should consider the first two argument
L and K as given input, while the third argument M is to be constructed by the
program as output. To make this more precise, we define that a term is ground if it
does not contain any logic variables. With the above mode declaration we specify
that first two arguments l and k to append should always be ground when a goal of
the form append l k m is to be solved. Secondly, it expresses that upon success, the
third argument m should always be ground. The Elf compiler verifies this property
as each declaration is read and issues an appropriate error message if it is violated.
This mode verification proceeds as follows. We first consider
We may assume that the first two arguments are ground, that is, nil and K will be
ground when append is invoked. Therefore, if this rule succeeds, the third argument
K will indeed be ground.
Next we consider the second clause.
ap_cons : append (X ; L) K (X ; M)
<- append L K M.
We may assume that the first two arguments are ground when append is invoked.
Hence X, L, and K may be assumed to be ground. This is necessary to know that the
recursive call append L K M is well-moded: L and K are indeed ground. Inductively,
we may now assume that M is ground if this subgoal succeeds, since the third argu-
ment to M was designated as an output argument. Since we also already know that
X is ground, we thus conclude that (X ; M) is ground. Therefore the declaration is
well-moded.
This program exhibits the expected behavior when given ground lists as the first
two arguments. It can also be used to split a list when the third argument is given
the first two are variables. For example,
M = 0 ; s 0 ; s s 0 ; nil.
We can also use the same implementation to split a list into two parts by posing
a query of the form append L K m for a given list m. This query constitutes a use
of append in the mode
The reader may wish to analyze append to see why append also satisfies this mode
declaration. We can now use the %query construct to verify that the actual and
expected number of solutions coincide.
%query 4 *
append L K (0 ; s 0 ; s s 0 ; nil).
---------- Solution 1 ----------
K = 0 ; s 0 ; s s 0 ; nil;
L = nil.
---------- Solution 2 ----------
K = s 0 ; s s 0 ; nil;
L = 0 ; nil.
---------- Solution 3 ----------
K = s s 0 ; nil;
L = 0 ; s 0 ; nil.
---------- Solution 4 ----------
K = nil;
L = 0 ; s 0 ; s s 0 ; nil.
Mode-checking is a valuable tool for the programmer to check the correct def-
inition and use of predicate. Incorrect use often leads to non-termination. For
example, consider the following definition of the even numbers.
The mode declaration indicates that it should be used only to verify if a given
(ground) natural numbers is even. Indeed, the query
?- even N.
will fail to terminate without giving a single answer. It is not mode-correct, since N
is a logic variable in an input position. To see why this fails to terminate, we step
through the execution:
?- even N.
Solving...
% Goal 1:
even N.
% Resolved with clause even_ss
N = s s N1.
% Solving subgoal (1) of clause even_ss
104 CHAPTER 4. THE ELF PROGRAMMING LANGUAGE
% Goal 2:
even N1.
% Resolved with clause even_ss
N1 = s s N2.
% Solving subgoal (1) of clause even_ss
% Goal 3:
even N2.
...
If definition of even was intended to enumerate all even numbers instead, we would
exchange the order of the two declarations even_0 and even_ss. We call this new
family even*.
even*_0 : even* 0.
even*_ss : even* (s (s N)) <- even* N.
The mode declaration now indicates that the argument of even* is no longer an
input, but an output. Since the declarations are tried in order, execution now
succeeds infinitely many times, starting with 0 as the first answer. The query
%query * 10 even* N.
4.6 Exercises
Exercise 4.1 Show the sequence of subgoals generated by the query which at-
tempts to evaluate the Mini-ML expression (lam x. x x) (lam x. x x). Also show
that this expression is not well-typed in Mini-ML, although its representation is of
course well-typed in LF.
Exercise 4.2 The Elf interpreter for Mini-ML contains some obvious redundancies.
For example, while constructing an evaluation of case e1 of z ⇒ e2 | s x ⇒ e3 ,
the expression e1 will be evaluated twice if its value is not zero. Write a program
for evaluation of Mini-ML expressions in Elf that avoids this redundant computa-
tion and prove that the new interpreter and the natural semantics given here are
equivalent. Implement this proof as a higher-level judgment relating derivations.
4.6. EXERCISES 105
Exercise 4.3 Implement the optimized version of evaluation from Exercise 2.12 in
which values that are substituted for variables during evaluation are not evaluated
n
again. Based on the modified interpreter, implement bounded evaluation e ,→ v
with the intended meaning that e evaluates to v in at most n steps (for a natural
number n). You may make the simplifying but unrealistic assumption that every
inference rule represents one step in the evaluation.
Exercise 4.4 Write Elf programs to implement quicksort and insertion sort for
lists of natural numbers, including all necessary auxiliary judgments.
Exercise 4.6 Give the definition of the judgment append as a deductive system.
append l1 l2 l3 should be derivable whenever l3 is the result of appending l1 and l2 .
146 CHAPTER 4. THE ELF PROGRAMMING LANGUAGE
Bibliography
[AINP88] Peter B. Andrews, Sunil Issar, Daniel Nesmith, and Frank Pfenning.
The TPS theorem proving system. In Ewing Lusk and Russ Overbeek,
editors, 9th International Conference on Automated Deduction, pages
760–761, Argonne, Illinois, May 1988. Springer-Verlag LNCS 310. Sys-
tem abstract.
[All75] William Allingham. In Fairy Land. Longmans, Green, and Co., London,
England, 1875.
[CDDK86] Dominique Clément, Joëlle Despeyroux, Thierry Despeyroux, and Gilles
Kahn. A simple applicative language: Mini-ML. In Proceedings of the
1986 Conference on LISP and Functional Programming, pages 13–27.
ACM Press, 1986.
[CF58] H. B. Curry and R. Feys. Combinatory Logic. North-Holland, Amster-
dam, 1958.
[Chu32] A. Church. A set of postulates for the foundation of logic I. Annals of
Mathematics, 33:346–366, 1932.
[Chu33] A. Church. A set of postulates for the foundation of logic II. Annals of
Mathematics, 34:839–864, 1933.
[Chu40] Alonzo Church. A formulation of the simple theory of types. Journal
of Symbolic Logic, 5:56–68, 1940.
[Chu41] Alonzo Church. The Calculi of Lambda-Conversion. Princeton Univer-
sity Press, Princeton, New Jersey, 1941.
[Coq91] Thierry Coquand. An algorithm for testing conversion in type theory.
In Gérard Huet and Gordon Plotkin, editors, Logical Frameworks, pages
255–279. Cambridge University Press, 1991.
[Cur34] H. B. Curry. Functionality in combinatory logic. Proceedings of the
National Academy of Sciences, U.S.A., 20:584–590, 1934.
147
148 BIBLIOGRAPHY
[DFH+ 93] Gilles Dowek, Amy Felty, Hugo Herbelin, Gérard Huet, Chet Murthy,
Catherine Parent, Christine Paulin-Mohring, and Benjamin Werner.
The Coq proof assistant user’s guide. Rapport Techniques 154, INRIA,
Rocquencourt, France, 1993. Version 5.8.
[DM82] Luis Damas and Robin Milner. Principal type schemes for functional
programs. In Conference Record of the 9th ACM Symposium on Princi-
ples of Programming Languages (POPL’82), pages 207–212. ACM Press,
1982.
[FP91] Tim Freeman and Frank Pfenning. Refinement types for ML. In Pro-
ceedings of the SIGPLAN ’91 Symposium on Language Design and Im-
plementation, pages 268–277, Toronto, Ontario, June 1991. ACM Press.
[GS84] Ferenc Gécseg and Magnus Steinby. Tree Automata. Akadémiai Kiadó,
Budapest, 1984.
[HB34] David Hilbert and Paul Bernays. Grundlagen der Mathematik. Springer-
Verlag, Berlin, 1934.
[HHP93] Robert Harper, Furio Honsell, and Gordon Plotkin. A framework for
defining logics. Journal of the Association for Computing Machinery,
40(1):143–184, January 1993.
[HM89] John Hannan and Dale Miller. A meta-logic for functional programming.
In H. Abramson and M. Rogers, editors, Meta-Programming in Logic
Programming, chapter 24, pages 453–476. MIT Press, 1989.
[HP00] Robert Harper and Frank Pfenning. On equivalence and canonical forms
in the LF type theory. Technical Report CMU-CS-00-148, Department
of Computer Science, Carnegie Mellon University, July 2000.
[ML85] Per Martin-Löf. On the meanings of the logical constants and the jus-
tifications of the logical laws. Technical Report 2, Scuola di Specializ-
zazione in Logica Matematica, Dipartimento di Matematica, Università
di Siena, 1985.
[ML96] Per Martin-Löf. On the meanings of the logical constants and the jus-
tifications of the logical laws. Nordic Journal of Philosophical Logic,
1(1):11–60, 1996.
[MNPS91] Dale Miller, Gopalan Nadathur, Frank Pfenning, and Andre Scedrov.
Uniform proofs as a foundation for logic programming. Annals of Pure
and Applied Logic, 51:125–157, 1991.
BIBLIOGRAPHY 151
[MTH90] Robin Milner, Mads Tofte, and Robert Harper. The Definition of Stan-
dard ML. MIT Press, Cambridge, Massachusetts, 1990.
[New65] Allen Newell. Limitations of the current stock of ideas about problem
solving. In A. Kent and O. E. Taulbee, editors, Electronic Information
Handling, pages 195–208, Washington, D.C., 1965. Spartan Books.
[NGdV94] R.P. Nederpelt, J.H. Geuvers, and R.C. de Vrijer, editors. Selected Pa-
pers on Automath, volume 133 of Studies in Logic and the Foundations
of Mathematics. North-Holland, 1994.
[Pfe92] Frank Pfenning, editor. Types in Logic Programming. MIT Press, Cam-
bridge, Massachusetts, 1992.