| Full Abstraction Factory |
Alan Jeffrey | Julian Rathke | |
Introduction to Full AbstractionOperational Models and TestingAn operational model of computation consists of:
For example, for arithmetic expressions we could define:
For example: (1 + 2) + 3 → 3 + 3 → 6 A theory of testing is given by extending an operational model in two ways:
We can then define when a program passes a test as: P may pass T whenever T[P] →* Q for some success state Q For example, for a language of arithmetic expressions:
For example, a possible test is given by the context (let x = 1 + 2 in ⋅). The expression (x + 3) fails this test: let x = 1 + 2 in x + 3 → let x = 3 in x + 3 → 3 + 3 → 6 (an unsuccessful state) The expression (x - 3) passes this test: let x = 1 + 2 in x - 3 → let x = 3 in x - 3 → 3 - 3 → 0 (a successful state) Finally, this allows us to define when one program Q is an operational improvement on another program P: P ≤ Q whenever ∀ T . P may pass T ⇒ Q may pass T This order is called the may testing order, and it is routine to verify that it is a pre-order (that is P ≤ P and if P ≤ Q ≤ R then P ≤ R). For example, let us introduce a new arithmetic expression Ω which never terminates: Ω → Ω Since this expression never reaches a successful state, it is the least program in the may-testing order (so any program is an improvement on Ω): Ω ≤ P Also, since order of arguments to addition does not matter, we can show that addition is commutative: (x + y) ≤ (y + x) As a sanity check, we can verify that x ≠ x + 1 since we have a test which distinguishes them: (x + 1) must fail (let x = 0 in ⋅) The theory of testing in the form described above originated from Morris (1968) and was developed further by Hennessy (1998). It is quite a robust theory, and has been applied to many languages, including models of functional programming such as the λ-calculus (Abramsky, 1989) and PCF (Plotkin, 1977; Abramsky, Jagadeesan and Malacaria, 2000; Hyland and Ong, 2000), concurrent languages such as the π-calculus (Fiore, Moggi and Sangiorgi 1996; Hennessy, 2002), and object-oriented languages such as the σ-calculus (Jeffrey and Rathke, 2002). The aim of the Full Abstraction Factory project is to scale up the techniques used in theories of testing to languages closer to industrial practice such as Java or C#. Denotational Models and Full AbstractionThe theory of may testing is quite elegant, but suffers from a serious problem: it often simple to prove that one program does not improve on another (we find a test which one passes and the other does not) but very hard to prove that one program does improve on another (we need to show that for every test one passes, so does the other). An alternative to operational models are denotational models, which consist of:
(For purists, we are assuming a prime algebraic domain, and identifying a member of the domain with its prime compact approximants.) For example, a denotational model for arithmetic expressions can be given as:
For example, the denotation of (x + y) is given by: [[x + y]] = { (σ, j + k) | (x=j) ∈ σ and (y=k) ∈ σ } and the denotation of Ω is given by: [[Ω]] = Ø Such a denotational semantics gives another definition of improvement: Q is a denotational improvement on P whenever [[P]] ⊆ [[Q]]. The characteristics of denotational models are rather different from operational models. Checking whether one program denotationally improves another is often overkill (it is often just as easy to find a test which distinguishes them). But it is much simpler to prove denotational improvement than operational improvement. For example: [[x + y]] = { (σ, j + k) | (x=j) ∈ σ and (y=k) ∈ σ } = { (σ, j + k) | (y=j) ∈ σ and (x=k) ∈ σ } = [[y + x]] and: [[Ω]] = Ø ⊆ [[P]] We would like to take advantage of the different characteristics of operational and denotational models: we use operational models when we want to show improvement fails (that is, when we are bug hunting) and we use denotational models when we want to show improvement succeeds (that is, when we are verifying software). This is the full abstraction problem (Milner, 1977): A denotational model is fully abstract for an operational model if [[P]] ⊆ [[Q]] precisely when P ≤ Q Full abstraction helps in software verification and validation in two ways:
Proofs of full abstraction often follow a standard outline, and are usually straightforward for simple languages such as integer arithmetic: Proposition (Denotational Precongruence). If [[P]] ⊆ [[Q]] then [[T[P]]] ⊆ [[T[Q]]]. Proposition (Computational Adequacy). There exists an element a such that, for any P, a ∈ [[P]] if and only if P →* Q for some success state Q. Theorem (Soundness of Denotational Model for Operational Model). If [[P]] ⊆ [[Q]] then P ≤ Q. Proof. For any test T, if P may pass T then T[P] →* P′ for some success state P′, so (Computational Adequacy) a ∈ [[T[P]]] and (Denotational Precongruence) a ∈ [[T[Q]]] so (Computational Adequacy) T[Q] →* Q′ for some success state Q′ and so Q may pass T. Proposition (Definability). For any element a, there exists a test T such that, for any P, a ∈ [[P]] if and only if P may pass T. Theorem (Completeness of Denotational Model for Operational Model). If P ≤ Q then [[P]] ⊆ [[Q]]. Proof. For any element a, if a ∈ [[P]] then (Definability) P may pass T and so Q may pass T, and so (Definability) a ∈ [[Q]]. Scaling Up Full AbstractionFull abstraction results are often simple to prove for simple languages such as integer arithmetic, but quickly become complex for languages with richer feature sets. The reason for this is that programming language design is a balancing act between expressivity and testing power. Adding features to a programming language makes the programming language more expressive, and so Definability (and hence Completeness) is simpler to prove. However, it also increases the expressive power of tests, and so Denotational Precongruence (and hence Soundness) is harder to prove. Full abstraction results for languages with powerful features such as first-class functions are often surprisingly difficult to achieve. For example, there is no fully abstract semantics for the language PCF (which contains integers, booleans and first-class functions) for which equivalence of finite programs is decidable. The goal of the Full Abstraction Factory is to design programming languages which include real-world features such as object-orientation and concurrency, but where the balance of features is chosen to make it possible to find tractable fully abstract models. Moreover, we hope to provide a fully abstract treatment of larger-scale computing entities (such as component-based object systems) then has previously been attempted. ReferencesAbramsky, S. The lazy lambda calculus, in Turner, D. (Ed.), Declarative Programming, Addison-Wesley, 1989. Abramsky, S., Jagadeesan, R. and Malacaria, P. Full abstraction for PCF, in Information and Computation 163, pp. 409-470, 2000. Fiore, M., Moggi, E., and Sangiorgi, D. A fully-abstract model for the π-calculus, in Proc. IEEE Conf. Logic in Computer Science, pp. 43-54, 1996. Hennessy, M. Algebraic Theory of Processes, MIT Press, 1988. Hennessy, M. A fully abstract denotational semantics for the π-calculus, in Theoretical Computer Science 278(1), pp. 53-89, 2002. Hyland, J. M. E, and Ong, C.-H. L. On full abstraction for PCF: I, II and III, in Information and Computation 163, pp. 285-408, 2000. Jeffrey, A. S. A., and Rathke, J. A fully abstract may testing semantics for concurrent objects, in Proc. IEEE Logic In Computer Science, pp. 101-112, 2002. Milner, R. Fully abstract semantics of typed λ-calculi, in Theoretical Computer Science 4, pp. 1-22, 1977. Morris, J.-H. Lambda Calculus Models of Programming Languages, Ph.D. Dissertation, M.I.T., 1968. Plotkin, G. LCF considered as a programming langauge, in Theoretical Computer Science 5, pp. 223-256, 1977. |
||
|
|
This material is partly based upon work supported by
the National Science Foundation under Grant No. 0430175, |
|