An Exploration of Relationships between Reflective Theories
Anurag Mendhekar and Daniel P. Friedman
In Proceedings of Reflection 96, San Francisco 1996.
The goal of this paper is to attempt to come up with a notion of reflection [0] that makes it easy for programmers using the language to reason about their programs. I.e., we attempt to answer the question: What must be the right notion of reflection so that we can get a good theory for it? Moreover, how useful would this notion be? In this paper, we look to notions of reflection in logic and computer science for which a theoretical basis has been established. Working backwards from these notions, we propose a form of reflection and an accompanying theory that has many of the properties we desire.
The intuition behind all these notions of reflection is the broad notion of access to a "meta-level." For example, the proof of Gödel's Incompleteness theorem vitally depends on the notion of "truth" predicates, which provide access at the object language level to the deduction power of the meta-theory. Logicians have been attempting to make sense of "truth" and its meaning for a long time. There is a large body of work that deals with the study of truth. Gupta and Belnap [1], for example, present an excellent analysis of the meaning of truth. For the sake of uniformity in this paper, let us call this field of study logical reflection.
More recently, and from a theoretical perspective, monads [3] have been proposed as a way of organizing the meta-level so that the semantics of diverse "computational effects" (such as side-effects and continuations) can be expressed in a uniform manner by appropriately adjusting a very small part of the meta-level organization. This form of reflection is known as monad reflection.
We begin with logical reflection and how truth predicates are understood. Using constructive principles, we relate truth predicates in logic to a language with reflective operators. Based on this analysis, we establish certain criteria that will help in reasoning about reflection. Using these criteria, we evaluate other notions of reflection in programming languages: static reflection, monadic reflection and computational reflection. We show that monadic reflection has many desirable properties, but in many cases, it lacks the utility of computational reflection. Based on some of this understanding, we show how this utility of computational reflection can be incorporated into monadic reflection.
1. Reflection in Logic: Truth PredicatesTruth predicates are well known. These are predicates introduced in a theory to reason about propositions in the theory itself. Truth predicates are defined over representations of propositions and they hold for all those representations for which the corresponding propositions are true. Tarski [2] was the first to formalize this definition of truth under his famous "Convention T," also known as the Tarski biconditionals:

For simplicity, we assume a positive (no negation), implicational logic with a single predicate T. T-introduction incorporates the fact that if
A holds, then the predicate T holds for the name of A. T-elimination affirms that the reverse also holds: if T holds for the name of some sentence A, then A holds.While extremely simple, the T-biconditionals incorporate our basic intuitions about a meta-level:
T is about the base language, and its behavior reflects that of the theory. 2. The Constructive ApproachOne of the standard ways of relating logical concepts with computational ones is through the use of the Curry-Howard isomorphism [4]. This isomorphism allows formulae in a logic to be interpreted as types in a suitable computational domain. Proofs of the propositions correspond to programs of that type. We define reductions on proofs so that execution of the program yields a result that is considered to be a witness of the proposition being proved.
We take typed
lv-calculus terms as proofs and use standard reductions to extract witnesses. Examples of term construction are: Ù -introduction corresponds to pairing; Ù -elimination to projecting out components of a pair; Ú -introduction to disjoint unions; Ú -elimination to dispatch based on the union; Þ -introduction to procedures; and modus ponens to application and so on. The rules of the logic can be considered to be typing rules of the corresponding operators.How can we interpret truth predicates this way? We must first introduce operators into the
lv-calculus that correspond to truth predicates. Let us define operators Ý (reify) and ß (reflect) to correspond to (that is, with typing rules) T-introduction and T-elimination. Let us call these operators reflective operators. These operators must be given a computational interpretation consistent with the constructive interpretation of truth predicates. What is the constructive interpretation of truth predicates? Let us look at T-introduction. If we are given a proof p for a sentence A, then Ý p must give a proof of the assertion that a proof for A exists. We can simply take this proof to be p. The proof of T(Let us take an example. It is often attractive to optimize functions at run time based on information available when all the data to a program are available. No widely used programming language actually supports this. What we would like to write is a function
f that takes in the representation of a function g and returns a new representation of g that is optimized based on run-time information. The new g can now be incorporated in the program. Suppose that we have written f. How can we think about a program that uses f ? Let us consider a function R, say of type A Þ A. A run-time optimization of this can be written as follows: ß (f (Ý R)). We take a representation of R using Ý , process it through f, and incorporate the optimized function using ß . Let us try to look at this program as a proof. The way to read the following is that the proposition to the right of the ':' is a formula and the term to its left is a proof of that formula. The horizontal lines represent application of inference rules, and the rule used is written to the right of the line.
This also can be read in another way: as an inference of the type of
ß(f(ÝR)). The formula to the right of the ':' is the type of the term to the left of ':'. The horizontal lines are applications of type-inference rules. 3. Establishing the properties of Ý and ß .Since we are interpreting proofs as programs, our ability to reason about these programs is determined by what programs we consider to be equivalent to one another. For
lv-calculus terms, this equality is given by the calculus itself. With the introduction of reflective operators, our calculus must be augmented with rules about how programs containing reflective operators behave. In this section, we state some desirable properties of these operators, without actually giving a real operational semantics to these operators. In later sections, we instantiate the semantics of these operators with existing ones to evaluate them in the context of these properties.Our first observation is that the rules
T-introduction and T-elimination are symmetric. This suggests that the operators Ý and ß ought to be inverses of each other. Indeed, going back and forth between a proof and its representation does not change either the proof itself or its representation. We propose the following equalities. If p proves A, p=Ý ß p and p=ß Ý p. Let us call these properties Ý ß -inverse and ß Ý -inverse, respectively.Our second observation is that if we decide that the representations of two proofs are equal, then the proofs must be equal. This gives rise to the following rule: If
M and N are equal and give rise to a representation of p, ß M =ß N. Let us call this rule the reflection property. Similarly, we would like to have: if M and N are equal Ý M = Ý N. This is because of the following. Let M and N prove A. The truth of A, T(Why are these properties desirable? If we are to think about other forms of reflection in terms similar to the well-understood truth predicate, then it is desirable that the observations we glean from truth predicates be guides for evaluating the others. Access to meta-level is the fundamental commonality in the forms of reflection that we have talked about above. Truth predicates incorporate this commonality without committing to specific operational semantics. This leads us to believe that reflective properties of truth predicates are properties about a fundamental intuition about reflection that we have. As we see later, this belief is borne out by the fact that those languages that violate even one of these properties have more complicated theories (that is, they are, in some sense, harder to think about).
The four properties elucidated above incorporate certain basic notions of how we want to think about reflective operators. The inverse properties assert that a term and its representation, while differentiated, correspond intimately with each other. This is important because when programming reflectively, we think of manipulating representations. The awareness that this representation corresponds to the actual term is important in helping us reason about programs. The other two properties are necessary for referential transparency to hold, which is, in general, a useful property for reasoning mechanisms to have.
5. Reflection in Programming Languages
We have now set up a general framework in which to evaluate various notions of reflection in programming languages. We have deliberately stayed away from concrete operational semantics and mainly set up some general principles that we think are important for reasoning about reflective programs. We have been talking about "representations" in very abstract terms, but have made no commitments as to what this representation should really look like.
In the next few sections, we instantiate our abstract concepts with very concrete ones in order to examine how they stand with respect to this framework.
6. Static reflection: quote and eval
Let us start with the simple operators
quote and eval present in a language such as Lisp. Lists are used as representations of programs and quote is a mechanism for reifying programs. An added restriction is that quote is not affected by variable bindings; (lambda (x) '(x)) will always return the list (x), regardless of what argument is actually passed to the procedure. Eval takes a quoted program, evaluates it, and returns the result.Let us see how these operators behave with respect to our rules. For
Ý ß -inverse, '(eval exp) is clearly not equal to exp. This is because we use a list-representation for programs. For ß Ý -inverse, (eval 'exp) =exp holds, provided exp is a closed term. It is easy to see that the reflection property also holds for this operational semantics. The reification property, however, fails. This can be seen by taking two equal terms: ((lx. x) 3) and 3, but '((lx. x) 3) is not equal to '3.Muller [5] develops a theory for these operators and demonstrates that adding these operators affects equational reasoning
¾ some things that we think ought to be true turn out not to be so. Clearly, from a reasoning point of view, this operational semantics is unsatisfactory.Let us now examine another kind of reflective operational semantics, based on monads. Monads are a category theoretic concept that can be used to express a wide variety of computational effects. We do not get into the category theoretic details of this, but instead refer the reader to Moggi [3].
Ý
and ß are defined in terms of two operators: h and _*, called inclusion and extension, respectively, and these operators must satisfy the following laws:
Intuitively,
h maps a value into a representation of the value and _* transforms a function that takes a value into a function that takes the representation of that value. By defining h and _* suitably in the meta-language, the user can control representations of the value. The three laws above guarantee some degree of consistency between a value and its representation. The first law implies that extracting a value from a representation and then mapping it back into the representation is an identity operation. The second law is the opposite of this: Mapping a value and extracting it is an identity operation. The third law says that functions constructed compositionally can be extended compositionally. This allows us to inductively define the extension of inductively constructed functions.Moggi gives a semantics for the operators
Ý and ß through a translation using h and _*. This translation will be presented in Section 14. Filinski [6] shows that this can be done instead by using the control operators shift and reset. This version is somewhat easier to understand and we present it first.While a theoretical definition of the operators
shift and reset is rather involved, it is easy to explain them in informal terms, using the concept of execution stacks. The operator reset places a marker on the execution stack. The operator shift takes a procedure as an argument. It takes the continuation up to (but not including) the marker closest to the top of the execution stack, converts it to a procedure, and passes it to its argument. The continuation is non-aborting. shift, however, is aborting and pops the execution stack up to (and including) the marker placed on the stack. Operationally, this can be defined as follows:![]()
Filinski defines the operators
Ý and ß as follows:
We can thus see that when the representation of a value is reflected into a context, that portion of the context that needs to use the value (that is, up to the closest dynamically enclosing reify) is used as a function that is extended to take a representation instead of a value. This, then is the base-code. The enclosing
Ý then presents the representation of the processed value. The meta-code here is the part of the context that receives the value of this reification.In the case of monads, there is no explicit reification of machine state. What is done, however, is that those parts of the machine state that are interesting are encoded into the representation of values. This depends on the specific monad. For example, if we are concerned with the state of a certain global variable, this state is encoded into the representation of values. Thus, whenever the representation of a value is sought, we also get the state of the global variable. When we construct a representation of a value, care must be taken that the state of the global variable is also included in the representation.
When this model is used, reification is a much more light-weight process. One simply invokes the function
h on the value. By describing reflective operators using shift and reset, one obtains the additional benefit of adding continuations to representation of values.Let us now examine what properties these operators satisfy.
We see that from a perspective of reasoning mechanisms, monadic reflection has desirable properties.
We have earlier presented a theory for a certain notion of computational reflection [16]. The idea is to make available operators that provide access to the complete state of the computation.
To briefly summarize this theory, it is built on the assumption that the implementation is a rewrite system and maintains two pieces of state: the term it is rewriting and the sub-term that is the next redex. Reification returns some representation of these two pieces of state. Reflection takes such a representation and installs it as the state of the rewrite system. The representation is chosen so that it differentiates syntactically between terms. It is, in fact, a desirable property that reflective programming differentiates between two seemingly equivalent programs. This is what provides programmers with control over how their programs get implemented. If we only have representations that are based on equivalence classes of terms, reflective properties will be largely useless.
A consequence of this representation decision is that the theory is not a conservative extension of the lambda calculus
¾ things that we think ought to be true turn out not to be so. This follows from the fact that the reification property is not satisfied. Many obvious equivalences between programs are not provable in this theory. The other three properties are satisfied by this theory.9. Violating the Reification Property
Reflective programming derives its appeal from allowing programmers to manipulate representations in order to achieve gains in performance and maintainability. One implication of this is that there is an ontological assumption that representations can be changed without changing the meaning of the program. This ontological assumption is in direct contradiction with the reification property. In this sense, the violation of the reification property is vital to reflection in programming languages being useful. On the other hand, this violation leads to a loss of reasoning power. How can we strike a balance between these two? How can we make reflection in programming languages easy to reason about and useful? We examine the answer to this question in the rest of this paper.
Our first observation is that reflection is mostly used in a principled way: we usually define abstractions that use reflective operators, and these abstractions are used everywhere within the program. This is referred to as separation of concerns. If we assume this to be the norm, then we would like our reasoning about the program to be supported in a similar manner. We would like to reason separately about reflective and non-reflective parts of the program and be assured that interaction between them is unproblematic.
As a trivial example, consider the case where we have a
lv-calculus-based language with reflective operators, but in all our programs, we never use a reflective operator. We can surely use the lv-calculus to reason about this program. Another example is a restricted use of reflective capabilities wherein we only look at the representation procedures to determine how many variables they need. This might be necessary, for example, when we want to use reflection to build tracing into our procedures. A way to do it would be to take the procedure P in question, determine how many arguments it takes, and construct a wrapper procedure that prints these arguments before passing them on to P. This is a very limited use of reflection. Moreover, only part of the representation information is being used, so we can use (informal) lv-calculus reasoning mechanisms to reason about programs that use tracing procedures (ignoring the fact that tracing causes characters to be printed on the output stream).Our second observation is that the framework of monadic reflection has the properties we desire. Can it be augmented so that it still maintains its properties but allows a higher degree of reflection?
In this section we examine a solution to the question raised above. We use separation of concerns and partition a program into its reflective and non-reflective components. By "reflective" we mean those parts that explicitly manipulate representations of values. We cast these restrictions in the framework of monads, at the same time relaxing some of the assumptions that are typically used when dealing with monads. We call the resulting structure a reflective monad. At the outset, we mention that while monads have categorical underpinnings, in this section we are not concerned with the underlying category theory.
Let us clarify the framework in which we develop reflective monads. In the following, we assume that the underlying calculus is Muller’s M-Lisp calculus [5]
lM. This calculus is the lv-calculus with added operators for getting the representation of terms and evaluation of terms. We assume the typing rules of the typed lv-calculus. Representations of terms of type A have the type T(The monad structure enables two important properties of reflective languages: separation and regularity. The structural distinction given to _
* allows provides a separation between base code and meta-code. Monad laws, on the other hand, ensure a semantic regularity that allows us to conclude that h and _* do not affect the base code semantically. This monad structure, however, cannot be used directly in a reflective context where equal terms need not have equal representations. Monad laws could easily fail to hold if the representation chosen is too restrictive.In order to address this problem, we introduce the concept of a reflective monad that is similar to that of the monad but has the following restrictions. The code for _* is written
LM. This is the meta-code of the program. Furthermore, this is the only code that is allowed to manipulate representations. All other code is referred to as the base code. The base code is written using LR. The base code is not allowed to construct term representations, except by using Ý. These restrictions are such that they can be enforced syntactically on a program.The separation of concerns we desire is achieved through these restrictions. By disallowing the base code to manipulate term representations, we have encapsulated all reflective representation manipulations into the code for _*. This separation of concerns allows us to use separate reasoning mechanisms for base-code and meta-code. An important consequence of this is that the reasoning mechanism for base-code can use all four of our desirable properties.
We must also address the question of the nature of representations. The actual representation we choose depends upon the reflective capabilities we want the language to have. Some languages might decide to provide a full representation of the term and its context (as the theory described in Section 8). Others might simply choose to provide a simple interface. An example is
call/cc in Scheme, where manipulating the continuation explicitly is not allowed, but a procedure-like interface is available. The assumption we make in the following is that the full representation of a reified context is rarely of any use. It is more important to have detailed representations of what goes into a context.A reflective monad is a monad where
h and _* have the following behavior. (h M ) returnsIn order to formalize this idea, we introduce a second notion of equivalence
» +. Under this equivalence, we unify those terms that evaluate to values that might be different representations of equal terms. Since a key assumption here is that there are no contexts that disinguish representations in LR we restrict the construction of this equivalence to terms that are only built using the basic lambda calculus syntax and calls to the monad operators h an _*. Let us denote the set of such terms by Lh,*. The subset of closed terms is denoted by L0h,*. Contexts built from these terms are denoted Ch,*[ ]. Define
to be the relation:
![]()
This relation captures the notion of evaluation of a term to a value. Here
is the notion of reduction in
![]()
The idea is that terms are distinguished by contexts only when there exists context which converges with one term and diverges with another. The above definition incorporates the intuition that we are interested only in well-behaved reflective contexts
Ch,*[ ]. Let us extend » + to the relation » that is defined on open terms to be the following. If, for all substitutions s ranging over L0h,* , sM» +sN, then M» N.Reflective monads differ from regular monads in subtle but important ways. The most important implication is that the following rule, called the representation rule:
![]()
does not hold a priori. This is not true in regular monads, where
h is assumed to be a function that satisfies the axioms of the lv-calculus. Note, however, that the following holds by definition of » :![]()
Another difference is that regular monads allow values of the monad type to be constructed explicitly (that is, without invoking
Ý). In fact, they sometimes require it. We disallow this in the reflective monad. This is because we want to have a clear idea of where representations are being constructed.We can now state the reflective monad laws:

The relation
» is reflexive, symmetric and transitive. Moreover, » commutes with =M. This follows from the definition of » . Under the monad laws, it has other interesting properties that we will examine in the following sections.12. Preserving the Reflection Property
If the meta-code were free to manipulate representations in an arbitrary manner, the reflection property would break down. This is because the meta-code could become sensitive to spurious differences in representation. We have, however, the second reflective monad axiom that
. The implication of this is that even if the meta-code makes distinctions between intensional aspects of terms, what it finally does must be faithful to what the terms mean. This might disallow many valid reflective programs, but it makes reasoning for a large number of reflective programs much easier.
Let us reconsider the run-time optimization example again. Suppose that we bind this run-time optimized procedure to a variable and use it in our code. We can extend the example in Section 2 in the obvious way:
(let ((g (reflect (f (reify R))))) (g args ...))
This, however, violates our restrictions since we are now using
f in base code. In the reflective monad, we write this code a little differently:Base code:
(let ((g (reflect (reify R)))) (g args ...))
The code for
_* is as follows:
(define star
(lambda (k x)
(if (run-time-optimizable? x)
(k (down-arrow (f x)))
(k (down-arrow x)))))
Here we are assuming the existence of a function that recognizes run-time optimizable functions. This could be done, for instance, by checking for membership in a list of predeclared optimizable functions. Also, note the use of
down-arrow to convert from representations to terms.The way this example works is that when
reflect passes on the reified value to its context (denoted in the meta-code by k), it processes it through star. In this example, the context is the binding of the value inside the let form. The meta-code gets a chance to manipulate the reified value when it is processed through star. In our case, this is when the run-time optimization happens. The monad laws, of course, guarantee that this reified value is not changed semantically¾ only representationally.Let us examine whether
star satisfies the monad laws. Assume that the representation property holds. The interesting case is the first law: (h*As an example of reasoning using the inverse property, in the base code, we can use
Ý ß -inverse to conclude that this program is semantically equivalent to (R args ...). This is because we know that star satisfies our restrictions.14. Theoretical Results About Reflective Monads
We have seen how reflective monads are useful in separating out reflective and non-reflective parts of a program. In this section, we develop theoretical results about reflective monads. We are interested in showing that the four desirable properties hold within the framework of reflective monads. We do this by developing a
l-theory for it and by showing that this theory is a conservative extension of the (typed) lv-calculus.This section takes into account the restrictions placed on reflective programs as given above and forces a rule in the theory that would make all the properties hold for the theory. We show that this is sound by proving that if the restrictions above are satisfied for all reflective programs, the theory is consistent. It is further shown that the theory is a conservative extension of the typed
lv-calculus. The implication of this is that conventional reasoning mechanisms need not be discarded to incorporate reflective capabilities in a language.Let us define the syntax for our (base) language. The syntax is basically that of the
lv-calculus but also allows (ß term) and (Ý term) as terms, where ß and Ý are as defined for monadic reflection. This set of terms is denoted LR. The theory lR is lv extended to include the four properties discussed earlier.Define a translation from
LR to Lh,* as follows. This translation "implements" the base language in a meta-language. This is essentially the operational semantics of our base language. (Note that this is identical to Moggi’s translation [3]).
There is an implementational issue with this translation. The translation itself introduces many instances of the invocation of the meta-code. While this should not matter semantically (as we will see), it becomes important with respect to actual implementations done in this style. Using Filinski’s representational techniques [6], however, this shortcoming can be addressed.
Let us illustrate this translation on the base-code in the example in Section 13. By the first translation we get:
(let ((g (reflect (reify R)))) (g args ...))
By expanding the
let, and applying the above translation for a couple of steps, we get:(star (lambda (f) (star f (star id (h [R])))) (h (lambda (g) [(g args …)]))
The key aspect of the theory
lR is that it reflects the behavior of » . We have the following.![]()
Proof
: (By induction on the structure of P.)Basis:
Then, ![]()
Induction: We illustrate with the case for![]()

We can now prove the following theorem:
l
R |- M=N Þ [M]» [N]Proof
: (By induction on the derivation of lR |- M=N.)Basis: We have two cases:
Case 1: The right-hand side is arrived upon using the
bv-axiom. Then we have the following. Let
![]()
![]()
![]()

Case 2: In the second case, the right-hand side is arrived upon using the inverse properties. The left-hand side then follows from the reflective monad laws.
Induction: The inductive cases are straightforward. The key observation is that in each of the inductive cases, a reflective context
Ch,*[ ] remains a reflective context when filled with [M].What this theorem verifies is that our interpretation of the base language
lR in the meta-language lM induces an equational theory that is safe to use provided certain properties about the interpretation are satisfied.We now define a correspondence between the calculi
lR and lv. We begin by defining an erasure function
There is an analogous erasure on types:

Note that erasure of terms preserves typing under the above type erasure. An important point to note is that erasure commutes with the substitution operation. We call this the substitution-erasure lemma. The proof is a straightforward induction on the structure of terms.
![]()
l
R |- M=N Þ lv |-This follows immediately from the definition of derivability in
lR and type erasure and substitution-erasure properties. The most interesting case is the base case, where lR |- M=N using the axiom b. The substitution lemma then applies.This result is not surprising. In our base language, the operators ß and Ý don’t really do anything. In implementation terms, they are merely "pragmas" to the runtime environment to invoke some special semantically valid processing. We then have the following corollary.
l
R is consistent.Proof: The proof of consistency is by contradiction. Take
I º (lx.x) and K º (lxy.x) as terms. It is well known that these two terms are not equal in the call-by-value lambda calculus. From the definition ofl
R is a conservative extension of lv.Proof
: One direction is given by the correspondence theorem. The other direction follows from the fact that the rules and axioms of lR contain those of lv.From the above theorems, we have established that the addition of the representation rule to the theory is not problematic logically. This also means that the reification property holds for reflective monads. Furthermore, we have established that we can continue to use reasoning mechanisms of the
lv-calculus, because the theory is a conservative extension of the lv-calculus. Both Ý ß -inverse and ß Ý -inverse hold because reflective monads obey monad laws. The reflection property also holds because of monad laws.Having worked out the theory, let us apply it to an example. One application where reflection has been used effectively is in building persistent systems. These systems preserve their objects in long-term storage so that the state of these objects can be retrieved in subsequent invocations of the program. The usual way to do this is to associate a handle with each object and use this handle as a key to store and retrieve the object within a database. This handle is then passed around as the representation of the object.
We could try to do this just using regular monads. We could have
store the object in a database and return its key, and we could use
to unpack this representation. Unfortunately, this does not work because the monad laws do not hold under this implementation. They do, however, hold under reflective monad laws. This can basically be seen by the fact that repeatedly inserting a value in the database will produce different database handles for the value, but each of the handles will be related under
This paper draws from a large body of literature in constructive logic,
lv-calculus and category theory. It was inspired by Lambek [7] and Scott [8]. Both explore interesting relationships between logic, computation, and category theory, using the Curry-Howard isomorphism as a basis. Constructive theories have been used as theories for program verification and construction for some time now. Martin-Löf's theory of types [9] is one of the most popular of such theories. A good introduction to the Curry-Howard isomorphism is given by Howard [4].Danvy and Filinski [6] have studied the control operators
shift and reset. Plotkin [10] introduces the lv-calculus. Moggi [3] introduces the notion of monads. Filinski [6] shows how monadic reflection can be achieved with control operators shift and reset. Other work on building calculi for control operators includes that by Felleisen [11] and Sitaram [12]. Talcott [13] presents an intensional theory of function and control abstractions. Wand and Friedman [14] and Danvy and Malmkjaer [15] have presented formal semantics of reflection.18 Conclusion
This paper explores connections between computational, logical, and monadic reflection. Using constructive principles, we present a design for reflective facilities in a programming language. One goal of reflection in programming languages is to enhance the power of a programmer to control how a program is implemented. Such a goal loses its appeal if a side-effect of achievGN="J7rmond,l"HELe. An i
of a shift and reset. Otsonisacime ntationed to in progbol">lue to usbol">l
18 Conclusion
6C="Image52.gif" WIDd,Times New R"Image52.gif" WIDd#9; Work 612"Biblihaniph>6-Löf's ,on w To H.B asward: Essay(denoClebinREF=ryL, usinott da-Cspired by LaFond no">4:pp 479-490, HiNT Fy J. R.
. Tal
clud J. P.,
Eds., 1980.)
We co
Tprog en wLispe. TaltACE=
the Curry-Hvol. 1 :pp 11-38, 1988.)
. TaMbol">ol"> Arnd teitutim, Wase mets aD.C.,
Octo. 1993. )