We use cookies to distinguish you from other users and to provide you with a better experience on our websites. Close this message to accept cookies or find out how to manage your cookie settings.
To save content items to your account,
please confirm that you agree to abide by our usage policies.
If this is the first time you use this feature, you will be asked to authorise Cambridge Core to connect with your account.
Find out more about saving content to .
To save content items to your Kindle, first ensure no-reply@cambridge.org
is added to your Approved Personal Document E-mail List under your Personal Document Settings
on the Manage Your Content and Devices page of your Amazon account. Then enter the ‘name’ part
of your Kindle email address below.
Find out more about saving to your Kindle.
Note you can select to save to either the @free.kindle.com or @kindle.com variations.
‘@free.kindle.com’ emails are free but can only be saved to your device when it is connected to wi-fi.
‘@kindle.com’ emails can be delivered even when you are not connected to wi-fi, but note that service fees apply.
The rationale behind the development of parameterized semantics in Chapter 5 is that it facilitates a multitude of interpretations of the mixed λ-calculus and combinatory logic. We saw examples of ‘standard semantics’ in Chapter 5 and a code generation example in Chapter 6 and in this chapter we shall give examples of static program analyses. We shall follow the approach of abstract interpretation but will only cover a rather small part of the concepts, techniques and tools that have been developed. The Bibliographical Notes will contain pointers to some of those that are not covered; in particular the notions of liveness (as opposed to safety), inducing (a best analysis) and expected forms (for certain operators).
We cover a basic strictness analysis in Section 7.1. It builds on Wadler's four-point domain for lists of base types but generalizes the formulation to lists of arbitrary types. In Section 7.2 we then illustrate the precision obtained by Wadler's notion of case analysis. We then review the tensor product which has been put forward as a way of modelling so-called relational program analyses (as opposed to the independent attribute program analyses). Finally we show that there is a rather intimate connection between these two ideas.
Strictness Analysis
Strictness of a function means that ⊥ is mapped to ⊥. In practical terms this means that if a function is strict it is safe to evaluate the argument to the function before beginning to evaluate the body of the function.
The previous chapter developed the notion of parameterized semantics for the mixed λ-calculus and combinatory logic. This was applied to showing that the run-time part of the language could be equipped with various mixtures of lazy and eager features. In this chapter we shall stick to one of these: the lazy semantics S. The power of parameterized semantics will then be used to specify code that describes how to compute the results specified by the lazy semantics.
The abstract machine and the code generation are both developed in Section 6.1 as it is hard to understand the details of the instructions in the abstract machine without some knowledge of how they are used for code generation and vice versa. The abstract machine is a variant of the categorical abstract machine and its semantics is formulated as a transition system on configurations consisting of a code component and a stack of values. The code generation is specified as an interpretation K in the sense of Chapter 5.
The remainder of the chapter is devoted to demonstrating the correctness of the code generation, K, with respect to the lazy semantics, S. To cut down on the overall length of the proof we shall exclude lists from our consideration. Section 6.2 then begins by showing that the code generation function behaves in a way that admits substitution. Next, Section 6.3 shows that the code generated is ‘well-behaved’ in that it operates in a stack-like manner.
In the previous chapters we have focused on the theoretical development of the language of the mixed λ-calculus and combinatory logic (Chapters 2, 3 and 4) and on the different standard and non-standard semantics of the language (Chapters 5, 6 and 7). There are two immediate application areas for this work, one is in the efficient implementation of functional languages and the other is in denotational semantics.
Optimized Code Generation
Much work in the community of functional languages has been devoted to the development of efficient implementations. This is well documented in [86] which contains a number of techniques that may be used to improve the overall performance of a ‘naive’ implementation. However, the theoretical soundness of all these techniques has not been established (although [52] goes part of the way). We believe that the main reason for this is that it is not well-understood how to structure correctness proofs even for naive code generation schemes. So although we have a firm handle on how to prove the safety of large classes of program analyses it is less clear how to formally prove the correctness of exploiting the analyses to generate ‘optimized’ code.
Before addressing the question on how to improve the code generation of Chapter 6 let us briefly review the techniques we have used. The code generation is specified as an interpretation K (in the sense of Chapter 5) and its correctness is expressed by means of Kripke-logical relations.
We want to interpret the run-time constructs of our language in different ways depending on the task at hand and at the same time we want the meaning of the compile-time constructs to be fixed. To make this possible we shall parameterize the semantics on an interpretation that specifies the meaning of the run-time level. The interpretation will define
the meaning of the run-time function types, and
the meaning of the combinators.
Relative to an interpretation one can then define the semantics of all well-formed 2-level types, of all well-formed 2-level expressions in combinator form and of all well-formed 2-level programs in combinator form. In this chapter we shall provide the detailed development of this framework and illustrate it by definitions of various forms of eager and lazy semantics. In the following chapters we shall use the framework to specify various forms of code generation and abstract interpretation; this will substantiate the claim that the development of parameterized semantics gives the desired flexibility.
In Section 5.1 we concentrate on the 2-level types. This begins with covering the required domain theory, defining the semantics of 2-level types relative to an interpretation and then providing examples of eager and lazy interpretations. In Section 5.2 we perform an analogous development for 2-level expressions in combinator form. We conclude with a treatment of 2-level programs and a discussion of our approach to semantics.
The subject area of this book concerns the implementation of functional languages. The main perspective is that part of the implementation process amounts to
making computer science concepts explicit
in order to facilitate the application, and the development, of general frameworks for program analysis and code generation.
This is illustrated on a specimen functional language patterned after the λ-calculus:
Types are made explicit in Chapter 2 by means of a Hindley/Milner/Damas type analysis.
Binding times are made explicit in Chapter 3 using an approach inspired by the one for type analysis. The binding times of chief interest are compile-time and run-time.
Combinators are made explicit in Chapter 4 but only for run-time computations whereas the compile-time computations retain their λ-calculus syntax.
The advantages of this approach are illustrated in the remainder of the book where the emphasis also shifts from a ‘syntactic perspective’ to a more ‘semantic perspective’:
A notion of parameterized semantics is defined in Chapter 5 and this allows a wide variety of semantics to be given.
It is illustrated for code generation in Chapter 6. Code is generated for a structured abstract machine and the correctness proof exploits Kripke-logical relations and layered predicates.
It is illustrated for abstract interpretation in Chapter 7. We generalize Wadler's strictness analysis to general lists, show the correctness using logical relations, and illustrate the similarity between tensor products and Wadler's case analysis.
Finally, Chapter 8 discusses possible ways of extending the development.
Neither Miranda, Standard ML nor the enriched λ-calculus has an explicit distinction between kinds of binding times. However, for higher-order functions we can distinguish between the parameters that are available and those that are not. In standard compiler terminology this corresponds to the distinction between compile-time and run-time. The idea is now to capture this implicit distinction between binding times and then to annotate the operations of the enriched λ-calculus accordingly.
In this chapter we present such a development for the enriched λ-calculus by defining a so-called 2-level λ-calculus. To be precise, Section 3.1 first presents the syntax of the 2-level λ-calculus and the accompanying explanations indicate how it may carry over to more than two levels or a base language different from the typed λ-calculus. Next we present well-formedness conditions for 2-level λ-expressions and again we sketch the more general setting. Of the many well-formedness definitions that are possible we choose one that interacts well with the approach to combinator introduction to be presented in Chapter 4. Section 3.2 then studies how to transform binding time information (in the form of 2-level types) into an already typed λ-expression. This transformation complements the transformation developed in Section 2.2.
The 2-Level λ-Calculus
We shall use the types of functions to record when their parameters will be available and their results produced.
The functional programming style is closely related to the use of higher-order functions. In particular, it suggests that many function definitions are instances of the same general computational pattern and that this pattern is defined by a higher-order function. The various instances of the pattern are then obtained by supplying the higher-order function with some of its arguments.
One of the benefits of this programming style is the reuse of function definitions and, more importantly, the reuse of properties proved to hold for them: usually a property of a higher-order function carries over to an instance by verifying that the arguments satisfy some simple properties.
One of the disadvantages is that the efficiency is often rather poor. The reason is that when generating code for a higher-order function it is impossible to make any assumptions about its arguments and to optimize the code accordingly. Furthermore, conventional machine architectures make it rather costly to use functions as data.
We shall therefore be interested in transforming instances of higher-order functions into functions that can be implemented more efficiently. The key observation in the approach to be presented here is that an instance of a higher-order function is a function where some of the arguments are known and others are not. To be able to exploit this we shall introduce an explicit distinction between known and unknown values or, using traditional compiler terminology, between compile-time entities and run-time entities.
In this monograph we motivate and provide theoretical foundations for the specification and implementation of systems employing data structures which have come to be known as feature structures. Feature structures provide a record like data structure for representing partial information that can be expressed in terms of features or attributes and their values. Feature structures are inherently associative in nature, with features interpreted as associative connections between domain objects. This leads to natural graph-based representations in which the value of a feature or attribute in a feature structure is either undefined or another feature structure. Under our approach, feature structures are ideally suited for use in unification-based formalisms, in which unification, at its most basic level, is simply an operation that simultaneously determines the consistency of two pieces of partial information and, if they are consistent, combines them into a single result. The standard application of unification is to efficient hierarchical pattern matching, a basic operation which has applications to a broad range of knowledge representation and automated reasoning tasks.
Our notion of feature structures, as well as its implications for processing, should be of interest to anyone interested in the behavior of systems that employ features, roles, attributes, or slots with structured fillers or values. We provide a degree of abstraction away from concrete feature structures by employing a general attribute-value logic. We extend the usual attribute-value logic of Rounds and Kasper (1986, Kasper and Rounds 1986) to allow for path inequations, but do not allow general negation or implication of the sort studied by Johnson (1987, 1988), Smolka (1988), or King (1989).
The first aim of this paper is to attack a problem posed in [1] about uniform families of maps between realizable functors on PER's.
To put this into context, suppose that we are given a category C to serve as our category of types. The authors of [1] observe that the types representable in the second-order lambda; calculus and most extensions thereof can be regarded as being obtained from functors (Cop × C)n → C by diagonalisation of corresponding contra and covariant arguments. Terms in the calculus give rise to dinatural transformations. This suggests a general structure in which parametrised types are interpreted by arbitrary functors (Cop × C)n → C, and their elements by dinatural transformations. Unfortunately as the authors of the original paper point out, this interpretation can not be carried out in general since dinaturals do not necessarily compose.
However, suppose we are in the extraordinary position that all families of maps which are of the correct form to be a dinatural transformation between functors (Cop × C)n → C are in fact dinatural, a situation in which we have, so to speak, the dinaturality for free. In this situation dinaturals compose. The result is a structure for a system in which types can be parametrised by types (second-order lambda calculus without the polymorphic types). Suppose, in addition, the category in question is complete, then we can perform the necessary quantification (which is in fact a simple product), and obtain a model for the second-order lambda calculus.