parameterized programming in lileanna - Tracz Family Home Page

3 downloads 4093 Views 1MB Size Report
Aug 20, 1997 - It was later refined for publication in IEEE Computer in 1986 [Gog86b]. ...... MILs provide manipulation of objects that vary in degree of ...
PARAMETERIZED PROGRAMMING IN LILEANNA

a dissertation submitted to the department of electrical engineering and the committee on graduate studies of stanford university in partial fulfillment of the requirements for the degree of doctor of philosophy

William Joseph Tracz August 1997

c Copyright 1997 by William Joseph Tracz All Rights Reserved

ii

I certify that I have read this dissertation and that in my opinion it is fully adequate, in scope and in quality, as a dissertation for the degree of Doctor of Philosophy.

Dr. David C. Luckham (Principal Adviser)

I certify that I have read this dissertation and that in my opinion it is fully adequate, in scope and in quality, as a dissertation for the degree of Doctor of Philosophy.

Dr. Joseph A. Goguen

I certify that I have read this dissertation and that in my opinion it is fully adequate, in scope and in quality, as a dissertation for the degree of Doctor of Philosophy.

Dr. Gio Wiederhold

Approved for the University Committee on Graduate Studies:

Dean of Graduate Studies

iii

Abstract Software is “soft” because it is “easier” to change than the hardware on which it executes. The relative ease of modification has led to the construction of increasingly complex and sophisticated programs based on existing systems. In the past, this evolutionary process often took place in an ad hoc fashion that usually resulted in: 1. distorting system structure, 2. loosing traceability to the original design, and 3. introducing intractable errors. The goal of this dissertation is to present a mechanism for specifying and applying transformations on existing software systems, modules or components in such a manner that the overall system structure is preserved and control is maintained over the traceability and accountability of change. To meet this goal, a Module Composition Language (MCL) LILEANNA (LIL (Library Interconnect Language) Extended with Anna (Annotated Ada)) has been designed and implemented. This dissertation describes the language, the semantics of the software component modularization, parameterization and composition constructs, and the language’s applicability to the software development process.

iv

Preface The general setting of this research is on software reuse. Software reuse is an intuitively appealing concept with obvious benefits in programmer productivity and quality. Much of the research effort that went into this dissertation was spent separating the non-technical issues of reuse (e.g., intellectual property rights, metrics, cost models, and incentive programs) from the technical ones (e.g., component specification, classification, and integration). The results of this background work appear in tutorial form as a collection of original papers, solicited papers, and reprints in Software Reuse: Emerging Technology published by IEEE Computer Society Press [Tra88a]. This book contains an extensive bibliography of over 500 citations on topics related to developing software with and for reuse. Additional motivational and methodological background material is found in Confessions of a Used Program Salesman — Institutionalizing Software Reuse published by Addison Wesley [Tra95]. Also, portions of this thesis have appeared elsewhere. In particular, portions of Chapter 2 and 4 are found in Parameterized Programming in LILEANNA [Tra93b], which appeared in the proceedings of the ACM Symposium on Applied Computing (SAC’93). Chapter 3 and portions of section 2.2 were derived from joint work done with Dr. Joseph Goguen, as found in the technical report An Implementation-Oriented Semantics for Module Composition [GT95], portions of which appeared in the Proceedings of the Fourth International Workshop on Software Reusability [Gog96b]. Some of the material in Chapter 4 was used as the basis for the paper LILEANNA: A Parameterized Programming Language [Tra93a], which appeared in Advances in Software Reuse — Selected Papers from the Second International Workshop on Software Reusability (IEEE Computer Society Press). The examples found in Chapter 5 have been used as classroom exercises in graduate courses at Syracuse University and the Pennsylvania State University. In addition, some of the material in Appendix B served as an introduction to A Conceptual Model for Megaprogramming published in ACM Software Engineering Notes [Tra91a]. This reusable software component conceptual model (3C Model) has been proposed as a software reuse design standard for the Strategic Defense Initiative Organization (SDIO) [Edw90]. Finally, the research found in this thesis served as the basis of a successful proposal to the US Department of Defense Advanced Research Projects Agency (ARPA) for the Domain-Specific Software Architectures (DSSA) [CTea91] program. The long path to completing this dissertation has been littered with numerous technical and non-technical potholes, as well as several religious crusades. In pursuing software reuse, I have come to several conclusions [Tra88c, Tra88b, Tra92]. The most important insight is that software reuse is a means to an end, and an end in itself. Software reuse is a way to increase programmer productivity and software quality. At the same time software reuse is a result of having software to reuse (i.e., before you can reuse software, there needs to be software to reuse!). Software reuse is a good example of engineering discipline that should start long before the implementation phase. Recent interest in software architecture [GTP95, Gar95] bear this out. As with any profession, a programmer’s ability to cope with the complexities in his or her environment is influenced by the sophistication of the tools and the soundness of the theoretical foundations upon which the technology is based. This dissertation strives to augment the suite of tools that software engineers rely on to ply their profession and to provide insight and formalism to this intuitively attractive technology. In particular, this thesis advances the state of the art in module composition languages.

v

Acknowledgments I would like to thank Dr. Joseph Goguen for his wisdom and encouragement, Dr. David Luckham for his patience and support, Dr. Tom Mitchell for his advice and remote assistance, Doug Bryan for his LaTEX-pertise, Rosemary Brock for taking care of all the little things early on, Dr. Michael Flynn for everything, and Dr. Rodger Fritz and Lockheed Martin/Loral/IBM for their belief in that they were investing their money wisely. I would also like to acknowledge the efforts of my colleagues on the DARPA Domain-Specific Software Architecture Avionics Domain Application Generation Environment (DSSA—ADAGE) Project: Lou Coglianese, Steve Shafer, and Mark Goodwin at Lockheed Martin Federal Systems1 in Owego, New York, Doug Lea at SUNY Oswego, Neel Madhav at Stanford University, and Don Batory at the University of Texas at Austin. In particular, I would especially like to acknowledge the efforts of Lou Coglianese. The avionics example (including some of the drawings) found in Chapter 5 is based on a program initially developed by Lou. Above all, Lou kept me honest. He not only forced me to understand the bounds of LILEANNA, but pushed me to new insights into how it could be extended. For this I am sincerely grateful. Similarly, I would like to acknowledge the efforts of Peter Angeline, Joe Higgens, Larry Holmquist, Keith Werkman, Eric Newton, and Al Valerica, who, under my direction, built additional tools to support parameterized programming with LILEANNA on the DSSA-ADAGE project. Also, I am indebted to Professor Luckham’s staff and graduate assistants (Doug Bryan, Geoff Mendal, Walt Mann, Randy Neff, David Rosenblum, Neel Madhav, and Sriram Sankar), who, under David’s guidance, developed the Anna translator — the technology foundation upon which LILEANNA is built, and whose assistance helped me understand the various aspects of the environment. Finally, I would like to thank my wife, Sharon, for her love and strength, and my children, Matt, Nick, and Meg, for their presence; more presents than they knew they could give.

Trademarks used in this thesis include: • Classic-Ada (Software Productivity Solutions, Inc.), • Eiffel (Interactive Software Engineering, Inc.), • IBM (International Business Machine Company), • InnovAda (Harris Corporation), • Smalltalk (Xerox), and • Unix (AT&T Bell Laboratories). 1 Formerly

the Loral Federal systems, and before that, IBM Federal Systems Company (FSC).

vi

Contents Abstract

iv

Preface

v

Acknowledgments

vi

1 Introduction

1

1.1

Principle Areas of Research . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

2

1.2

Contributions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

2

1.3

Thesis Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

4

2 Setting The Stage 2.1

2.2

2.3

2.4

6

Historical Perspective . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

7

2.1.1

Decomposition, Abstraction and Modularization . . . . . . . . . . . . . . . . . . . . . . . . . .

8

2.1.2

Parameterization Revisited . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

9

2.1.3

Composition, Instantiation and Transformation . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

2.1.4

Module Interconnections and Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

Module Composition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 2.2.1

Module Interconnection Languages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

2.2.2

Formal Foundations of LILEANNA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

Related Work . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 2.3.1

Programming Languages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

2.3.2

Formal Specification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

Selection Rationale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 2.4.1

Ada as a Target Language . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21

2.4.2

Anna as a Specification Language

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22

3 Module Composition Language Semantics 3.1

23

Introduction and Background . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 3.1.1

The Module Graph . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24

3.1.2

The Notation for Module Composition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 vii

3.1.3 3.2

3.3

Mathematical Concepts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 3.2.1

Tuple Sets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26

3.2.2

Partial Signatures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27

3.2.3

Institutions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28

3.2.4

Specification Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

Overload Resolution and Aggregation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 3.3.1

3.4

Laws of Software Composition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25

Implementation Modules

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34

Views and Renaming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 3.4.1

Hiding and Enriching . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37

3.4.2

Aggregation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38

3.5

Parameterization and Instantiation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40

3.6

Module Expressions and Make . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42

3.7

Vertical Composition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 3.7.1

Horizontal vs. Vertical Instantiation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44

4 LILEANNA Language

45

4.1

LILEANNA Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46

4.2

Lexical Elements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46

4.3

4.4

4.5

4.6

4.2.1

Reserved Words . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47

4.2.2

Method of Description and Syntax Notation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47

4.2.3

Predefined Language Environment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47

Theories . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 4.3.1

Attributes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50

4.3.2

Import Mechanism: Horizontal Structure Definition . . . . . . . . . . . . . . . . . . . . . . . . 50

4.3.3

Export Mechanism . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51

4.3.4

Formal Semantics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51

4.3.5

Theory Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54

LILEANNA Packages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 4.4.1

Programming by Difference . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57

4.4.2

Listing a Package’s Needs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57

4.4.3

LA Package Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57

Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 4.5.1

Default Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61

4.5.2

Deferred Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61

4.5.3

Operational Semantics of Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61

4.5.4

Sample Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61

Module Composition and Generation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 4.6.1

Make Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 viii

4.7

4.6.2

Instantiation in the Make Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63

4.6.3

Vertical Composition in the Make Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64

4.6.4

Module Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65

4.6.5

Where to use a Module Expression . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65

4.6.6

Examples of Module Composition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65

Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 4.7.1

Horizontal and Vertical Instantiation of a Stack . . . . . . . . . . . . . . . . . . . . . . . . . . . 71

4.7.2

SAM from Radar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71

4.7.3

Square from Rectangle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75

5 Module Composition Examples 5.1

76

Theater Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76 5.1.1

Application Description/Requirements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76

5.1.2

Horizontal Structure for Theater Problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78

5.1.3

Vertical Structure for Theater Problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83

5.1.4

Making a Theater . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86

5.1.5

Change #1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88

5.1.6

Change #2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90

5.1.7

Change #3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90

5.1.8

Change #4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91

5.1.9

Change #5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91

5.1.10 Change #6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 5.1.11 Change #7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92 5.1.12 Change #8 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92 5.2

5.3

5.4

Avionics Domain Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 5.2.1

ADAGE Avionics System Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93

5.2.2

Avionics System Horizontal and Vertical Structure . . . . . . . . . . . . . . . . . . . . . . . . . 94

Using ADAGE and LILEANNA to Create an Avionics System . . . . . . . . . . . . . . . . . . . . . . 99 5.3.1

LILEANNA from Type Expressions Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102

5.3.2

Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104

Second Version of Avionics Domain Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104 5.4.1

Code Categories . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105

5.4.2

Simple Exemplar Transformations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105

5.4.3

Additional Transformations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106

5.4.4

Wrapper Transformations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107

5.4.5

Sample MEGEN Inputs and Outputs

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108

6 Design and Implementation Issues 6.1

116

Design Considerations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116 6.1.1

Ground Rules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116 ix

6.1.2 6.2

6.3

Differences between LIL and LILEANNA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116

Design Approach . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117 6.2.1

Basic Functional Capabilities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119

6.2.2

Basic Transformations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119

6.2.3

Anna Model for Verifying Requirement Theories . . . . . . . . . . . . . . . . . . . . . . . . . . 120

Implementation Approach . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120 6.3.1

Developing the Grammar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121

6.3.2

Developing the Full Grammar Parser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121

6.3.3

Developing the LILEANNA Translator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121

7 Conclusion 7.1

Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122 7.1.1

7.2

122 Additional Parameterized Programming Experimental Results . . . . . . . . . . . . . . . . . . 124

Future Directions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125

A LILEANNA Grammar

127

B Software Component Conceptual Model

138

B.1 Three Aspects of a Software Component . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139 B.1.1 Concept . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139 B.1.2 Content . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139 B.1.3 Context . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140 B.2 Software Component Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141 B.2.1 Queue Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141 B.3 Parameterization Process Outline . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143 C Module Composition Semantics Paper

145

C.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145 C.1.1 Summary of the Paper . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148 C.1.2 Conventions for Exposition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149 C.2 Foundational Concepts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149 C.2.1 The Module Graph . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149 C.2.2 Signatures and Tuple Sets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150 C.2.3 Institutions, Sentences, and Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153 C.3 Modules and Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156 C.3.1 Specification Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156 C.3.2 Implementation Modules

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163

C.3.3 Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164 C.4 Horizontal Composition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168 C.4.1 Renaming for Specifications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168 x

C.4.2 Hiding and Enriching . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169 C.4.3 Renaming and Transformation for Implementations

. . . . . . . . . . . . . . . . . . . . . . . . 170

C.4.4 Aggregation of Specifications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171 C.4.5 Aggregation of Implementations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176 C.4.6 Parameterization and Instantiation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177 C.4.7 Module Expressions and Make . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182 C.4.8 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185 C.5 Vertical Composition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185 C.6 Results and Lessons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188 D List of Acronyms

189

Bibliography

190

xi

List of Tables 2.1

MIL Comparison: Units of Modularization and Version/Implementation Control . . . . . . . . . . . . 15

2.2

MIL Comparison: Importation/Dependency Relationships . . . . . . . . . . . . . . . . . . . . . . . . . 16

2.3

MIL Comparison: Export/Interface Definition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16

2.4

Functional and Object-Oriented Programming Constructs in FOOPS . . . . . . . . . . . . . . . . . . . 21

4.1

LILEANNA Reserved Words . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47

5.1

List of Objects, Operations and Attributes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77

5.2

Comparison of Theater, Airline, Library, and Inventory Domains . . . . . . . . . . . . . . . . . . . . . 92

5.3

Sample Design Space for Avionics System Components . . . . . . . . . . . . . . . . . . . . . . . . . . . 96

5.4

Sample Design Space for DSOs (Sensors) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96

5.5

Summary of type expression to commented Ada code generated/reused . . . . . . . . . . . . . . . . . 104

B.1 Summary of Conceptual Model of Reusable Software Components . . . . . . . . . . . . . . . . . . . . 139

xii

List of Figures 2.1

Transitive/non-transitive importation example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12

2.2

Idealized software lifecycle with and without module composition . . . . . . . . . . . . . . . . . . . . . 14

3.1

A simple module graph . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25

3.2

Horizontal and vertical relationships for M H [P :: TP ](V :: TV ) . . . . . . . . . . . . . . . . . . . . . . . 25

3.3

A tuple set structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27

3.4

lileanna signature for bounded stack of integers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28

3.5

lileanna theory specifications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31

3.6

lileanna bounded integer stack package specifications . . . . . . . . . . . . . . . . . . . . . . . . . . . 32

3.7

Results of overload resolution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34

3.8

Attributes of a module name . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35

3.9

Theory of partially ordered sets (POSET) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36

3.10 Views of Natural numbers as POSET

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36

3.11 Specifying a SQUARE Package as a Transformation of RECTANGLE . . . . . . . . . . . . . . . . . . . . . . 38 3.12 Module graph for Example 3.8 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 3.13 lileanna generic specification for bounded stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 3.14 View of Integer numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 3.15 Module graph for Example 3.10 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 4.1

Make (-ing) a Short Stack Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46

4.2

Syntax of Theory Definition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49

4.3

Syntax of Parameter List . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49

4.4

Theory of Trivial Type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49

4.5

Subprogram Attribute Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50

4.6

Syntax of Subprogram Attributes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50

4.7

Syntax of Import Clause . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51

4.8

Syntax of Export Clause . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52

4.9

Syntax of Axiom Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52

4.10 Syntax of Type Annotation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 4.11 Type Annotation Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 4.12 Syntax of Exception/Propagation Annotation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 xiii

4.13 Example of Exception/Propagation Annotation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 4.14 Syntax of Subprogram Annotation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 4.15 Subprogram Annotation Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 4.16 Theory of Ada Private Types (Priv) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 4.17 Theory of Partially Ordered Sets (POSet) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 4.18 Generic Sort Theory Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 4.19 Syntax of LILEANNA Package Definition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 4.20 Syntax of LILEANNA Package Body . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 4.21 Syntax of Needs Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 4.22 List LA Package Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 4.23 Sort LA Package Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 4.24 Syntax of View Definition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 4.25 Syntax of Type, Operation and Exception Mappings . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 4.26 Parameterized View Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 4.27 View of Bag as a Set . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 4.28 Syntax of Make Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 4.29 In-Line View Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 4.30 Syntax of Vertical Instantiation Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 4.31 Syntax of Needs Map Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 4.32 Make (-ing) Sorted List of Naturals Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 4.33 Make (-ing) Sorted List of Naturals Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 4.34 Syntax of Module Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 4.35 LA Generic Set and Bag Packages Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 4.36 Bag as Set View Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 4.37 Generic LA Bag as Set Package Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 4.38 Make (-ing) a Set from a Bag Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 4.39 Generated Set Package Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 4.40 Deque and Stack Theory Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 4.41 Deque as Stack View Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 4.42 LA Deque as Stack Package Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 4.43 Make (-ing) a Stack Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 4.44 Generated Stack Package Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 4.45 Make (-ing) a Short Stack Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 4.46 Make (-ing) a Short Stack Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 4.47 LILEANNA Generic Stack Package Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 4.48 List Ada Package Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 4.49 Generic Ada Stack Package Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73 4.50 Generated Stack Package Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73 4.51 Make (-ing) a SAM Radar Site Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74 xiv

4.52 Radar LILEANNA Package Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74 4.53 Generated SAM Site Package Specification Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74 4.54 Make (-ing) a Square from a Rectangle Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 5.1

Seat Interface Theory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79

5.2

Row Parameterized Interface Theory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80

5.3

Section Parameterized Interface Theory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81

5.4

Theater Parameterized Interface Theory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82

5.5

Seat LILEANNA Package . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83

5.6

Row LILEANNA Generic Package . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84

5.7

Section LILEANNA Generic Package . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85

5.8

Theater LILEANNA Generic Package . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86

5.9

Set Generic (Requirement) Theory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87

5.10 Horizontal and Vertical Structure of Theater Example. . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 5.11 Mappings (Views) for Instantiating a Theater . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88 5.12 Creating an Instance of a Theater Via a Make Statement . . . . . . . . . . . . . . . . . . . . . . . . . 88 5.13 Making Performance Parameterized Interface Theory . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 5.14 Show Parameterized Interface Theory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 5.15 Creating an Instance of a Show Via a Make Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . 90 5.16 Using a Package Expression to Modify the Show Package . . . . . . . . . . . . . . . . . . . . . . . . . 90 5.17 Using a Package Expression to Change Cost of Shows . . . . . . . . . . . . . . . . . . . . . . . . . . . 90 5.18 Using a Package Expression to Modify the Row Package . . . . . . . . . . . . . . . . . . . . . . . . . . 91 5.19 Using a Package Expression to Modify the Show Package . . . . . . . . . . . . . . . . . . . . . . . . . 91 5.20 Using a Package Expression to Support an Optimized Concert Ticket Sales Program . . . . . . . . . . 92 5.21 Using a Package Expression to Make an Airline Reservation System . . . . . . . . . . . . . . . . . . . 92 5.22 Horizontal and Vertical Structure of Typical Avionics System . . . . . . . . . . . . . . . . . . . . . . . 94 5.23 LILEANNA Parameterized Programming Environment . . . . . . . . . . . . . . . . . . . . . . . . . . . 100 5.24 Annotated Type Expressions for Example Avionics System . . . . . . . . . . . . . . . . . . . . . . . . 101 6.1

LILEANNA System Block Diagram . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118

7.1

ADAGE Tool Suite . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123

B.1 Queue Concept Formal Description . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141 B.2 Separating Operational and Implementation Context . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142 B.3 Mixing Operational and Implementation Context . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142 B.4 Explicit use of Conceptual Context as Implementation Context . . . . . . . . . . . . . . . . . . . . . . 142 C.1 Idealized Software Lifecycle with and without Module Composition . . . . . . . . . . . . . . . . . . . . 146 C.2 A Simple Module Graph . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150 C.3 A Tuple Set Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151 xv

C.4 lileanna Signature for Bounded Stack of Integers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153 C.5 Some lileanna Theory Specifications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159 C.6 lileanna Bounded Integer Stack Package Specifications . . . . . . . . . . . . . . . . . . . . . . . . . . 160 C.7 Results of Overload Resolution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163 C.8 Attributes of a Module Name . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164 C.9 Theory of Partially Ordered Sets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165 C.10 Views of Natural Numbers as POSETs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165 C.11 Specifying a SQUARE Package as a Transformation of RECTANGLE . . . . . . . . . . . . . . . . . . . . . . 171 C.12 Transforming a single linked list package . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172 C.13 Module Graph for Example 52 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173 C.14 lileanna Generic Specification for Bounded Stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178 C.15 A View of the Integers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178 C.16 Module Graph for Example 69 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179 C.17 Example of Parameterized Module with two Parameter Modules . . . . . . . . . . . . . . . . . . . . . 180 C.18 Two Views of Different Inertial Navigation Sensors (INS) . . . . . . . . . . . . . . . . . . . . . . . . . 180 C.19 View for High-Rate Polling of Sensors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180 C.20 Making a Square from a Rectangle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183 C.21 Making an ADA LOGIC INTERFACE using Aggregation . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184 C.22 Making Parameterized Modules from Parameterized Modules . . . . . . . . . . . . . . . . . . . . . . . 184 C.23 Horizontal and Vertical Relationships for M H [P :: T ](V :: R) . . . . . . . . . . . . . . . . . . . . . . . 187 C.24 LILEANNA Generic Stack Package Example with Vertical Parameterization . . . . . . . . . . . . . . . 187

xvi

Parameterized Programming in LILEANNA William Joseph Tracz August 20, 1997

Chapter 1

Introduction Man cannot retire his experience. He must use it, for experience achieves more with less energy and time. — Bernard Baruch Software developers have long searched for the “Silver Bullet” with which to solve their productivity and quality problems [Bro87]. One often-cited ([McI69, BCG83, Jon84, Ale86, Mat86, Bea87]) step toward solving the perceived software crisis [Boe81] is the modularization and specification of software so that it can be used again. For various technical and non-technical reasons [Tra87c], the state of the practice of software reuse has evolved slowly since its conception in the early 1950’s [Tra88c]. The most widely used reusable software entity then was (and still is) a subroutine or a macro1 . Beyond calling a subroutine, or expanding a macro, the most prevalent method for reusing software involves using an editor to cut and paste portions of previously developed programs and to make global substitutions of variable names. These manual techniques are awkward, error prone, and often rely on the arbitrary expertise (or luck) of the programmer in being aware that such software exists to be salvaged in the first place. Furthermore, this process of small-grain, ad hoc2 reuse takes place during the implementation rather than the design phase of most software development projects. Oftentimes problems occur when a designer specifies (top-down) the high-level architecture of a program that doesn’t match the building blocks that the programmer is trying to manipulate (bottom-up) during implementation. Advances in the state of the art in programming languages and formal methods for software specification, modularization, and development have led to new understandings of the relationships between programming languages and the programming process. This dissertation reports on a systematic study of the issues surrounding the specification, parameterization, structuring, composition and generation of software from existing modules. This dissertation covers the: design, analysis, implementation, and use of the programming language LILEANNA. LILEANNA (LIL Extended with ANNA (Annotated Ada) [LvH84, LvH85, LvHKBO87]) is an implementation of LIL (Library Interconnect Language) [Gog83], a MCL (Module Composition Language) for the programming language Ada3 [Ada83]. LIL is a “parameterized programming” language that supports: • • • •

designing, structuring, composing, and generating software systems.

It is based on the work of Goguen and Burstall on the language Clear [BG77, BG80] and Goguen on OBJ [Gog79]. LIL was first proposed by Joseph Goguen at the Ada Program Libraries Workshop [GL83], November 1-3, 1983 in 1 Though,

arguably, the reuse of objects, patterns of objects [GHJV94], or templates are gaining immense popularity. “odd hack” is a better term to describe the process. 3 The use of the word “Ada” in this thesis refers to the 1983 version of the language Ada (Ada83) and should not be confused with its recent successor, Ada 95 [Ada95]. 2 Perhaps

1

2

CHAPTER 1. INTRODUCTION

Monterey, California. It was later refined for publication in IEEE Computer in 1986 [Gog86b]. Since then it has been the interest of several researchers (e.g., [Gau87], [Har87], [Liu88], and [HW93]). The primary design goals of LIL were: 1. to make it easier to reuse software written in Ada, 2. to facilitate the composition of Ada packages, 3. to support an object-oriented style of design and documentation for Ada, 4. to rapidly prototype new applications by integrating executable specifications with the controlled manipulation of source code, 5. to avoid re-compilation when certain kinds of changes are made to a system, and 6. to support maintenance of Ada programs and families of programs. This dissertation, through the implementation of the language LILEANNA, demonstrates the feasibility of the first three and last design goals, along with part of the fourth. The fifth design goal–avoiding re-compilation–is technically achievable within the current implementation framework, though not demonstrable due to the lack of necessary compiler components (i.e., code generator) for integration. Besides being the first such implementation of LIL, LILEANNA also extends and refines some of the initial concepts in LIL, based on insights gained through working with the implementation models found in this thesis.

1.1

Principle Areas of Research

The work presented in this dissertation focuses on research issues associated with module interconnect languages [PDN86] and their support for a parameterized programming paradigm [Gog84]. While state-of-the-art module interconnect languages provided the capability to specify component interfaces and dependencies, this research set out to determine the set of module composition operations and generation techniques that would support the configuration and integration of existing software modules (in this case Ada package specifications and bodies). In addition, this research strived to characterize the use of inheritance, parameterization, and importation of scope (i.e., layering) from not only a program structuring perspective, but from a application generation perspective.

1.2

Contributions

The following contributions have resulted from the research leading up to this dissertation: The Module Composition Language LILEANNA: • supports the structuring and parameterization of software modules,

• fully defines the composition mechanism proposed in LIL [GL83] by allowing horizontal, vertical, and generic instantiation in module expressions, • illustrates the relationship between inheritance and genericity, and

• provides valuable insight and rationale for designing module interfaces.

1.2. CONTRIBUTIONS

3

An Implementation-oriented Semantics for Module Composition: • distinguishes itself as the first set theory-based approach for specifying the semantics of module composition constructs, • presents a notation that allows analysis of pathological module composition cases, • provides valuable insight into implementation tradeoffs and design strategies, and

• identifies a number of “Laws of Software Composition.”

The Implementation of Parameterization and Composition Language Features:4 • generates Ada packages,

• demonstrates implementation trade-offs, such as the use of parameterization versus inheritance and the use of re-packaging and renaming, • serves as a basis for extension to other programming languages, and

• results in demonstrable improvements in programmer productivity and program quality.

A Conceptual Model for Reusable Software Components:5 • introduces the first comprehensive model for designing reusable software components,

• serves as a basis for developing parameterized modules,

• separates the attributes of concept, content, and context associated with software components, and

• provides a framework for describing the process of designing software systems and components that can readily be composed using LILEANNA.

4 This language was used as the basis for specifying and generating real-time avionics software as part of the Defense Advanced Research Project Agency’s (DARPA) Domain-Specific Software Architectures (DSSA) Avionics Domain Application Generation Environment (ADAGE) Project [CTea91]. 5 This model has been proposed as a software reuse design standard for the Strategic Defense Initiative Organization (SDIO) [Edw90].

4

CHAPTER 1. INTRODUCTION

In summary, the research described in this thesis represents new science in that it proves the feasibility of using a module “composition” language for an imperative programming language. Furthermore, it extends the concept of parameterized programming through the introduction of new composition operations and two new tools that facilitate its applications (MEGEN – Module Expression GENerator and GLUE – Graphical Layout User Environment)6 .

1.3

Thesis Overview

This dissertation reports on a systematic study of the issues surrounding the rigorous specification, parameterization, structuring, composition, and generation of software from existing modules. Chapter 2 reviews previous research related to Module Interconnection Languages (MILs), interface specification, and parameterization. LILEANNA concepts and terminology (i.e., theories, packages, and views) are introduced and placed in perspective with other programming languages and software development methodologies. Chapter 2 also contains a comparison of LILEANNA language features with those found in previous MILs (i.e., MIL75 [DK76], Thomas’s MIL [Tho76], Cooprider’s MIL [Coo79], INTERCOL [Tic80], and PIC [WCW85]). Chapters 3 defines the semantics of LILEANNA module composition constructs. This semantics is set theoretic, using the technical notions of tuple set, partial signature, and institution, and avoiding more difficult mathematics such as abstract algebra and category theory. Language features include information hiding, both vertical and horizontal composition, and views for binding modules to interfaces. Vertical composition refers to the hierarchical structuring of a system into layers, while horizontal composition refers to the structure of a given layer. Modules may involve information hiding, and views may involve behavioral satisfaction of a theory by a module. A number of “Laws of Software Composition” are given, showing how the various module composition operations are related. This chapter introduces these concepts without going into extensive proofs, which are found in Appendix C. Chapter 4 introduces the language features of LILEANNA. Several small examples of the structuring, parameterization, and composition features are presented. Chapter 5 presents two large examples. The first7 example leads the reader through the evolution and generalization of a ticket reservation system. The second example presents the results of using LILEANNA to rapidly prototype, compose, and generate real-time embedded avionics software as part of the DARPA-sponsored DSSA-ADAGE (Domain-Specific Software Architecture [GM92], Avionics Domain Application Generation Environment) Project [CST92]. Chapter 6 contains a summary of the trade-offs and implementation considerations that were evaluated during the development of the language. The transformation and generation algorithms reflect the insights gained and restrictions imposed by targeting the language to Ada. Chapter 7 summarizes the results of this thesis and discusses possible enhancements and future research directions. While this dissertation focuses on a language to support the rapid-prototyping of new software systems, LILEANNA may be viewed as a small part of a larger suite of tools that could define an advanced software development environment. This chapter describes additional tools, developed under the direction of the author (i.e., MEGEN (Module Expression GENerator [TSV94] and GLUE (Graphical Layout User Environment) [HHT94], that enhance parameterized programming in LILEANNA. In addition, this chapter provides metrics data gathered from using the LILEANNA tools on the ADAGE project, including a controlled experiment using and instrumented version of the tool set [HTS96]. The appendices provide additional language details and background information. Appendix A contains the formal syntactical definition of the language. Appendix B describes a conceptual model for designing reusable software components (i.e., the 3C model [Tra90, Tra91a]). The relationship between the concept (or abstraction) that a software component defines is contrasted with the context that it is defined and used under. Implementation issues are distinguished from specification concerns by separating the content of software component (one of possibly 6 These tools were not developed by the author, but their development was coordinated by the author as part of the DSSA-ADAGE project, where the author, as an ARPA PI wrote the specifications for each tool. 7 This example has been used as classroom exercises in graduate courses at Syracuse University and Penn State.

1.3. THESIS OVERVIEW

5

several implementations) from the concept it defines (i.e., its interface) and further delineating contextual parameters associated with the implementation from those unique to the concept. The final Appendix, C, contains the full paper, “ An Implementation-Oriented Semantics for Module Composition,” jointly developed by the author and Professor Joseph A. Goguen. This paper provides additional mathematical details on the semantics of the module composition constructs in LILEANNA.

Chapter 2

Setting The Stage One can appreciate the importance of glue by an analogy with carpentry. A chair can be made quite easily by making the parts — seat legs, back and so on — and sticking them together in the right way. But this depends on the ability to make joints and wood-glue. Lacking that ability, the only way to make a chair is to carve it in one piece out of a solid block of wood. — John Hughes [Hug89] The purpose of this chapter is to provide the reader with sufficient background information to understand the terminology and significance of the material in the chapters that follow. This chapter is divided into three sections: 1. Historical Perspective – summarizes the evolution of software development methodology and tools in order to establish the relevance of this dissertation, 2. Related Work – identifies other research activities that relate to this dissertation, and 3. Selection Rationale – analyzes the target and implementation languages chosen for this dissertation. The first section deals with modularization, parameterization, and composition features of programming languages. In particular, the units of modularization, version and implementation control, importation/dependency relationships, and export/interface definition mechanisms found in existing Module Interconnection Languages (MILs) are analyzed and contrasted with the features in LILEANNA. This is followed by a discussion of the formal foundations of LILEANNA.

6

2.1. HISTORICAL PERSPECTIVE

7

The second section focuses on previous research activities related to programming languages and formal specifications. This section discusses existing object-oriented programming languages and their features. The last section presents the rationale for selection of Ada and Anna as baseline technologies for this research. For a more detailed treatment of this background material, the reader is encouraged to read Software Reuse: Emerging Technology [Tra88b], Software Reuse [Kru92], Abstraction Techniques in Modern Programming Languages [Sha84], or Module Interconnection Languages [PDN86].

2.1

Historical Perspective

If one reflects on the evolution of the programming process, one can easily assert that in the past, software advances have played a secondary role to hardware advances. The computer industry long fostered a Von Neumann mainframe mentality and had little motivation to improve software. This neglect was based on sound economic justification. Why should computer manufactures spend any effort improving bundled software? Slow, bulky tools indirectly resulted in the desire for faster processors and larger storage devices. Hardware costs outweighed software costs and the commercial marketplace was not experienced, educated, nor organized enough to make their needs known. Not until the introduction of the personal computer in the early 1980’s did the marketplace for consumer software emerge. Before then, advances in programming languages, algorithms, and programming environments experienced limited distribution beyond a small academic community or research labs. Furthermore, these advances were often motivated by a set of constraints that are no longer as important: the cost and performance of computational resources. Today, with diminishing hardware costs and surplus processor power, the programming community has elevated software quality and productivity to the forefront of interest. Research in programming languages and formal methods of software specification and development have led to new understandings of the relationship between programming languages and the programming process. The following five sections briefly trace advances in programming languages and software development methodologies leading to the functional capabilities of LILEANNA. The first section deals with creating software components or modules. The second section investigates forms of parameterization.

8

CHAPTER 2. SETTING THE STAGE

The next section focuses on composing new systems from parameterized modules. This is followed by a section analyzing the features of several Module Interconnection Languages (MILs). The last section gives a brief overview of the formalism upon which LILEANNA is based.

2.1.1

Decomposition, Abstraction and Modularization

Abstraction is a useful mechanism for dealing with complexity. Divide-and-conquer is a well known problem-solving technique long recognized by cognitive psychologists. It is little wonder that parallel paradigms exist in computer programming. Abstraction is the process of hiding needless detail and capturing the essence of a problem or object. Modularization is the result of decomposing a larger problem into meaningful smaller problems or modules. The programming community has recognized certain desirable qualities of modularization. Stevens, Myers and Constantine [SMC74] found that modules should be “loosely coupled and highly cohesive,” i.e., modules should consist of related (cohesive) functions and procedures that perhaps share one (or more) common type (or types) of interest [Gut75] as a parameter, while dependency (coupling) on external functions or data should be minimized. Parnas [Par72c], using a technique known as data encapsulation, suggested that all state data should be accessible only through operations exported by the module in which the data is located. By following such a convention, the representation of the data can then be “hidden” from the user (information hiding). This leads to the separation of a module’s interface from its implementation and facilitates the creation of families of implementations [Par76]. All of these techniques in some way have led to the concept of user-defined or abstract data types as a conceptual unit of modularization. A data type defines the values of objects of that type and the operations that may be performed on them. Coercion and overloading are other language features that aid in defining an interface and creating program families. Overloading is the ability to reuse a name of a module, procedure, or function with different type parameters (e.g., overloading the “+” operator to handle the user-defined COMPLEX data type). Overloading is a form of polymorphism [CW85] in that the same operation appears to work on several different operand types. Similarly, objects may be implicitly or explicitly coerced into objects of different types in order to take advantage of predefined or user-defined operations1 . The above mentioned techniques (data encapsulation, information hiding, abstract data types, overloading, polymorphism, and subtyping) manifest themselves in object-oriented programming paradigms. This methodology, mind set, and associated tool set also rely on the concept of inheritance for module implementation and structuring (discussed in section 2.1.4). Units of Modularization A module is a unit of abstraction. This abstraction may take the form of a procedural abstraction, such as a subroutine, function, procedure or macro. It can also be an object abstraction, i.e., a collection of procedural abstractions and/or data, for example a package (Ada), module (Modulo-2), cluster (Clu), class (C++, Simula, etc.), or common data area (FORTRAN). Seidewitz [SS86] proposed the following spectrum of abstractions with four points on the scale of “goodness”: 1. Entity Abstraction — an object abstraction (e.g., a stack). 2. Action Abstraction — a “generalized set” of related operations (e.g., a collection of sorting algorithms). 3. Virtual Machine Abstraction — a collection of operations that provide the procedural abstractions that other (higher-level) modules can use to implement their operational functionality [Par72b] (e.g., the International Organization for Standardization’s Open System Interconnection (ISO/OSI) model layers in a communication protocol [ISO83]). 1 Implicit type coercion often results in the loss of information, which may have undesirable side effects. As a result, some languages (e.g., Ada) have been designed to only allow explicit type coercion.

2.1. HISTORICAL PERSPECTIVE

9

4. Coincidental Abstraction — a completely unrelated set of operations brought together to form a module. This type of module is the opposite of a highly cohesive module cited by Stevens, Myers and Constantine [SMC74]. Because LILEANNA is a design language as well as a rapid-prototyping language, it provides modularization constructs beyond those found in traditional imperative languages. As originally described for LIL by Goguen in [Gog86b], LILEANNA provides three modularization constructs: 1. the theory, 2. the LILEANNA package, and 3. the view. These units of modularization help differentiate between Seidewitz’s forms of abstraction. A theory is a unit of modularization that is used to describe the semantics and syntax of an entity/object. The set of operations, types, exceptions and data objects exported by a virtual machine is referred to in LILEANNA as an interface theory. The set of parameters necessary to instantiate a parameterized/generic module are encapsulated into a requirement theory. A LILEANNA package plays a role similar to that of an entity abstraction and virtual machine abstraction. It is a higher-level module than an Ada package2 and serves as a mechanism for version and implementation control. A view is a theory morphism. It can describe the relationships between two modules or be used as a mapping mechanism as part of the instantiation of parameterized modules. It shows how a module’s requirement theory (parameter list) can be satisfied by mapping its elements (e.g., subprograms and types) into those found in another module or modules. Theories act as package types [Gog91b]. They do not have implementations, although they may be associated with implementations through a view; they serve to document interfaces and express relationships to other modules through subtyping (inheritance). LILEANNA packages, on the other hand, are program design units and, as such, may have one or more implementations (Ada package bodies) sharing the same package interface (Ada package specification).

2.1.2

Parameterization Revisited

Webster [Mis87] defines parameter as both 1. “an independent variable used to express the coordinates of a variable point,” and 2. “an arbitrary constant whose value characterizes a member of a system.” The first definition follows the mathematical notion of function arguments. A parameter is a mechanism for having modules communicate with each other by passing data through a predefined interface. Note that parameters are a syntactical convenience, in some respects, because the same effect could be carried out by assigning the value of a parameter to a global variable3 . As indicated by the second definition, parameterization also plays a role in the process of specialization. In order to increase a module’s domain of applicability [PD87] (or configurability), it can be generalized by adding parameters to facilitate the selection or specification of additional functionality or processing capability. The use of parameters to “generalize” a module has caused the term “generic module” to become synonymous with “parameterized module.” 2 The most obvious difference between an Ada package and a LILEANNA package is that LILEANNA packages have no private section. 3 In fact this was the modus operandi for parameter passing in early versions of the programming languages COBOL (COmmon Business-Oriented Language) and BASIC (Beginners All-purpose Symbolic Instruction Code). This technique works for non-recursive functions or for parameters that are not passed between separate load modules.

10

CHAPTER 2. SETTING THE STAGE

This dissertation (and the parameterization capability of the programming language LILEANNA) focuses on the latter (generic) form of parameterization. An important distinction should be made about the process of parameterization. In some ways, parameterization is the process of factoring out commonality and isolating change. The common part of the module becomes the template or schema that is shared by all instances. The elements of the module that may change are the parameters (or slots in frame-based approaches). How successful one has been at parameterizing a module is reflected by the level of effort necessary to tailor the module to meet the requirements of a different application. This depends on: 1. the degree to which the designer envisioned the domain of applicability for the module, as reflected by its parameters, and 2. the type of parameterization mechanisms in the language in which the software was written. While most widely used commercial programming languages (e.g., COBOL, Pascal, and PL/I) allow only binding formal parameters to variables or constants, some programming languages allow types or subprograms (e.g., FORTRAN or Ada) to be passed as actual parameters. LIL was designed to provide an orthogonal parameterization mechanism beyond that found in Ada. In particular, besides variables, constants, types and subprograms, LILEANNA supports passing packages and exceptions as parameters to generic modules. The notion of passing an entire package as a parameter4 (i.e., package types as first-class citizens) is an elegant way of controlling the complexity associated with heavily parameterized modules. Formal package parameter types allow the semantics of parameters to be defined. (The passing of packages and exceptions to generic modules, as provided by LILEANNA, was considered as part of the new features for Ada 95 [Ada95]). Finally, one should note that a potentially serious error can occur when the intended actual parameter value passed to a parameterized module does not match the semantics of the expected (formal) parameter data type. To guard against this, tests can be done: • at compile time (e.g., in strongly typed languages), or • at execution time by the language’s run-time system (e.g., Smalltalk or LISP) or by the hardware (e.g., tagged architecture in a LISP machine), or • by the module itself (e.g., user-supplied pre-condition tests). The tradeoff becomes one between flexibility and predictability. Unfortunately, any one of these approaches is limited in its expressibility and rigor. LILEANNA has language mechanisms that allows the user to specify the kinds of parameterization/flexibility needed for the individual applications. Section C.4.6 presents a more detailed discussion of parameterization.

2.1.3

Composition, Instantiation and Transformation

Once software has been modularized and parameterized, a programmer needs some mechanism for incorporating it into subsequent implementations. There are two widely recognized approaches to system composition from predesigned components [BP84]: 1. Constructive — a bottom-up building block approach where software components are incorporated as pieces (black boxes) of a new application (i.e., glued together) and 2. Generative — a top-down transformational approach where patterns or templates are instantiated (i.e., holes in the glue are filled in). 4 Passing parameters “bundled” in a package is not different, in effect, from passing parameters “unbundled” to an Ada generic unit, if one has a mechanism to “bundle” the elements by composing a new package on-the-fly.

2.1. HISTORICAL PERSPECTIVE

11

The constructive approach can be as simple as declaring an object of a data type, or invoking a subroutine or function. An example of a generative approach is expanding a macro. Hybrid approaches are also quite common, as is the case of parameterized types that must be instantiated before they are elaborated (assigned storage). Instantiation can be compared to macro expansion or call by name. The actual parameters are textually substituted for the formal parameters and the resulting code compiled into an executable module (or interpreted depending on the language)5 . The trade-off being made is module size and flexibility for execution speed. The reader should note that the effects of dynamic binding have not been factored into the discussion so far. This topic will be introduced in the next section. The above mentioned techniques are effective but fairly low-level methods of software composition, yet only the simplest form of generative mechanism (the macro) is available in most programming languages (e.g., the C preprocessor). LILEANNA provides an extensive capability for generating new Ada packages through fully or partially instantiating parameterized modules, specifying the composition of new systems through the mechanisms of inheritance and importation of scope, and transforming Ada code through module expressions. These features are described in more detail in the section that follows.

2.1.4

Module Interconnections and Structure

During the high-level design phase of the software life-cycle, the overall structure or architectural framework of the system is defined. This process requires the identification of inter-module dependencies and the assignment of functionality through interface definition. A Module Interconnection Language (MIL) provides some of the descriptive capability to detail inter-module dependencies.

Importation Importing, the mechanism of providing visibility into the scope of another module (establishing context), may be transitive or non-transitive. For example, assume there exists a module B that imports visibility module A6 (see figure 2.1). By the definition of importation of scope, module B may refer to any of the operations and types that are exported by (in the interface of) Module A. To highlight the difference between transitive and non-transitive importation of scope, assume there exists a new module, C, that imports module B. In the transitive importation case, module C may not only refer to the operations and types exported by module B, but all operations exported by the modules that B imports, which is module A. In the non-transitive importation case, module C may only refer to the operations and types visible in module B. Code inheritance is a form of transitive importation of scope. It can be used to avoid redundant source code by factoring out common procedural and data abstractions to form module hierarchies. The issue of overload resolution, when the local operation has the same signature or format as an operation in an imported module, presents an interesting problem. In some programming languages (e.g., Smalltalk and Eiffel), overload resolution is performed dynamically, at run-time, through a mechanism called dynamic binding. Dynamic binding deals with postponing the assignment of the location of data or procedure/function entry points to their references until run time (as opposed to assigning them at compile time or load module build (e.g., link edit) time). One advantage of dynamic binding is the ability to change or compose a software system, without re-compiling and re-link editing (binding) the entire systems. Special operators (i.e., “self ” and “super”) have been introduced to aid in establishing the search path for dynamic binding. The “self” operation implies that the search for an operation should begin from the scope of the calling module. The “super” operation implies that the search should start in the imported scope of the called module. Continuing with the example of transitive importation of scope (i.e., B inherits A and C inherits B) shown in figure 2.1, assume that all three modules export operation Y. If module B contains an operation, X, whose implementation has a call to the “self” of operation Y, then if module B invokes operation X, the operation B.Y will be subsequently invoked, but if Module C invokes operation X (in B), then C.Y will be subsequently invoked (B.Y is ignored). 5 Note: While most generative approaches take place at compile time, the same effect results if the parameters are passed to a parameterized subroutine at execution time. 6 The term “import” is being used here in the same manner as the “with” clause in Ada.

12

CHAPTER 2. SETTING THE STAGE

operation Y A

A

non-transitive

transitive

?

?

B: sees A

B: sees A

non-transitive

transitive

?

?

operation Y operation X calls “self/super” Y

operation Y C: sees B but not A

C: sees B and A

Figure 2.1: Transitive/non-transitive importation example If the operation X in B contained a reference to the “super” of Y instead of the “self” of Y, then if module B invokes operation X, then A.Y will be invoked (B.Y is ignored), but if module C invokes operation X (in B), then B.Y will be subsequently invoked (C.Y is ignored). Horizontal and Vertical Structure of Modules Goguen and Burstall in [BG77] introduced two mechanisms for specifying the relationships between modules: 1. vertical (“uses a”) structure — defining the implementation dependencies of a module by describing the operations it imports from other modules — its implementation context, and 2. horizontal structure — defining the interface of a module by building it up from other module interfaces (through inheritance7 (“is a”) and parameterization (“configured by”)). The choice of terms reflects the orthogonality of these two mechanisms rather than the way that structure diagrams are drawn, e.g., should the flow be left to right or top to bottom? Vertical structure, layering, or stratification, may be viewed as the result of design by top-down module decomposition. The module(s) at one level of abstraction are implemented using the operations provided by a (set of) module(s) at a lower, more detailed level of abstraction. This set of operations and types at the lower level form an “abstract machine” [Par72b] upon which the operations at the higher level are implemented. The syntactic and semantic requirements of these operations can be specified using an interface theory in LILEANNA. In Ada, the mechanism for indicating this kind of contextual dependence is the “with” clause in a package specification or body (see Ada Language Reference Manual (ALRM) section 10.1.1). In LILEANNA, the mechanism is a “needs” clause in a LILEANNA package (see section 4.4.2). 7 This use of inheritance defines the conceptual relationship between modules (conceptual context). Code inheritance is an implementation decision that may or may not follow the same horizontal (conceptual) inheritance hierarchy used to define the module’s interface. The difference between code and type inheritance will be an important point of discussion in section B.1.3.

2.2. MODULE COMPOSITION

13

Ideally, horizontal structure is the result of object-oriented domain analysis (e.g., [Tra91b] and [TC92]) rather than a structured/data flow analysis. Common features are “factored” out to form an inheritance hierarchy of modules/objects. In Smalltalk [GR83] this hierarchy is expressed using the superclass construct. In C++ [Str86] the inheritance construct is the public attribute. In LILEANNA three forms of inheritance exist: inherit, protect, and extend (see section 4.3.2). Inheritance may be emulated, somewhat, in Ada using derived types (see ALRM section 3.4) with additional support with “tagged types” in Ada 95 [Bar93]. Rajlich [Raj85] proposed a slightly different view of these two orthogonal hierarchies: 1. seniority structure, and 2. parent-child structure. The seniority hierarchy is the same as the vertical structure hierarchy in that it describes a system architecture in layers. The parent-child hierarchy refers to the aggregation of objects within the limited framework of the Ada “separate” mechanism (ALRM section 10.1). Ada provides a weak form of aggregation as opposed to multiple inheritance in LILEANNA. Ada limits the “separate” modules/packages to be included in only one hierarchy, where with multiple inheritance/importation, a module can be incorporated into several hierarchies8 . The concept of “abstract classes” in Smalltalk, “virtual functions” or “abstract base classes” in C++, and “deferred classes” in Eiffel9 [Mey88] are also supported in LILEANNA through the use of formal semantics and generics. An “abstract class” consists of a set of operations, where the “higher-level” operations are implemented through references (e.g., “self ” in Smalltalk) to “lower-level” or “basic” operations, which themselves are not implemented. The class is abstract in the sense that it does not represent a complete implementation. Subclasses of the class must implement the “basic” operations while inheriting the “higher-level” operations that rely on these implementations.

2.2

Module Composition

The approach to module composition described in this thesis can be used for many different programming languages by providing a simple module connection language (MCL) with “module expression operators” that say how to manipulate and connect modules in that programming language. This general approach, called “parameterized programming” [Gog89], involves a module specification capability; however, programmers can specify as little of a module as they like, as long as they declare the syntax. Module composition languages can be used at least two different ways in software development: 1. descriptively, to specify and analyze a given design or architecture (i.e., the interfaces and interconnections of modules); and 2. constructively, to create a new design and application from existing modules, using operations that combine and transform modules. Using MCLs constructively facilitates reuse and supports controlled evolution. As illustrated in Figure 2.2, it can simplify the software lifecycle by eliminating detailed design and coding10 , provided a suitable library of software modules with their specifications and interrelationships is available. Parameterized programming supports this by executing module expressions, and thus goes far beyond merely specifying modules and systems. Analogy with Functional Programming It may be helpful to think of module composition as functional programming with modules (see [Bur85] for a nice exposition of this idea). The analogy is appropriate because there is 8 One

way to overcome this limitation in Ada is to make the module generic and instantiate it in multiple places. 95 provides a similar capability through its abstract subprograms. 10 However, note that this figure is a great oversimplification, since it omits the processes of feedback and reconstruction that occur in real software development projects. Research on the sociology of software development shows that the division of tasks into phases is to a large extent arbitrary, and is also subject to reclassification as the project evolves; see papers in [JG94], especially [BS94] and [Gog94]. 9 Ada

14

CHAPTER 2. SETTING THE STAGE

Specification r

r

r

Detailed Design

r

r r

Module Composition

Code r

6

Test

r

r

Figure 2.2: Idealized software lifecycle with and without module composition

no state at module composition time. Instead, we are evaluating expressions, called module expressions, that say how to put together software components. The result of evaluating a module expression is a value, namely the composed system; it is built by applying module composition operations to subsystems, which are also values. Under this analogy, modules have types that are called theories. A theory is another kind of module that is used to describe both the syntax and semantics of modules that supply code. We write C :: T to indicate that a module C has the “large grain type” T; this involves more than the usual notion of type indicated by the notation C : T, because (what we call) a view must be given from T to C, binding the formal symbols in T to actual symbols in C. Furthermore, the axioms in T should be satisfied by the corresponding symbols in C. Views are also used in instantiating parameterized modules, to say how to bind symbols of a parameterized module’s interface theory to symbols (e.g., types and operators) in the module used as an actual parameter. The axioms in the theory of a module give rise to “proof obligations;” these are mathematical assertions that should be true in order for the program to work as expected. We do not recommend that the proof aspect of this notion of type should be required for normal production programming, but rather that a “hook” should be left, in case rigorous formal methods are required for some critical application. Thus in normal programming, the axioms serve to document properties of interfaces the designers and programmers considered especially important, and believed to be true, perhaps as the result of informal reasoning. The word “type” has many different meanings in computer science. There are the “small grain types” used in specification and programming languages; in our examples, these will be lileanna and Ada types. There are also the “large grain types” described above. There are two kinds of type in lileanna: (1) purely syntactic types that correspond to those in a programming language, and (2) types in roughly the sense of type theory, i.e., programming language types and operations encapsulated together with some axioms, which are used to define semantic subtypes of the programming and specification language types. Hereafter we will in general only use the word “type” for programming language types, and will use the word “theory” for the large grain type used for modules11 . The paradigm for module composition described here is based on parameterized programming and hyperprogramming12 [Gog89, Gog90b]. Because this approach involves modules for both specification and code, both kinds of module must be composed. Specifications are used as “headers” for code, and are combined with simple set theoretic operations. At the code level, composition may be done with intermediate compiled code, which is then sent to the compiler backend after composition has been completed. In the case of lileanna with Ada, this means using diana13 [DIA83].

11 The parameterized programming literature [Gog89] uses word “sort” for small grain types, and we may occasionally use this terminology if extra clarity is needed. 12 The term megaprogramming is used for these ideas within the ARPA community [BS92, WWC92]. 13 This is an acronym for Descriptive Intermediate Attributed Notation for Ada.

2.2. MODULE COMPOSITION

2.2.1

15

Module Interconnection Languages

This section compares the syntactical constructs and features of LIL, as initially envisioned by Goguen and currently implemented in LILEANNA, with those found in the following five Module Interconnection Languages (MILs): 1. 2. 3. 4. 5.

MIL75 [DK76], Thomas’s MIL [Tho76], Cooprider’s MIL [Coo79], INTERCOL [Tic80], and PIC (Precise Interface Control) [Wol85].

The areas analyzed include: • • • •

Units of Modularization — the basic objects being manipulated within the language, Version/Implementation Control — how families of implementations are handled, Importation/Dependency Relationship — how dependency on other resources is specified and distinguished, and Export/Interface Definition — how external interfaces are constructed.

The sections that follow contain a breakdown, by MIL, of the features supported. (Note: not all languages support all the features identified above.)

MIL MIL75

Thomas’s MIL

Cooprider’s MIL INTERCOL

PIC LIL

Modularization Unit — resource — module — system — subsystem — resource — module — node — module — subsystem — system — module — package — package — theory — LIL package — Ada package

Version Control Mechanism

version implementation

view of LIL package

Table 2.1: MIL Comparison: Units of Modularization and Version/Implementation Control

Units of Modularization MILs provide manipulation of objects that vary in degree of granularity or size. Table 2.1 lists the names of the objects being specified by increasing order of granularity. With the exception of LIL, no MIL surveyed provides a level of abstraction higher than a code unit or configurations/combinations (though others provide distinct hierarchical specification mechanisms). No language except LIL facilitates the specification of a class hierarchy.

16

CHAPTER 2. SETTING THE STAGE

Version/Implementation Control Table 2.1 also summaries the language mechanisms for handling version control and the selection of an implementation from a family of implementations. One can observe that only three MILs (Cooprider’s MIL, INTERCOL, and LIL) directly address this dimension of module interconnection.

Importation/Dependency Relationship The terminology used to specify the module elements (e.g., types, variables, operations, etc.) that an implementation of a given module would depend on is shown in Table 2.2. Note that only LIL provides an inheritance mechanism. Inheritance can be used to define interface relationships (conceptual context), or to indicate how a module is to be implemented (code inheritance as an implementation context).

MIL MIL75 Thomas’s MIL Cooprider’s MIL INTERCOL PIC LIL

Importation/Dependency Mechanism — has access to — uses derived/nonderived — must inherit — has successor — requires — external — require — request — needs — inherit — protect — extend

Table 2.2: MIL Comparison: Importation/Dependency Relationships

Export/Interface Definition The terminology for specifying what module elements are visible to modules that import it is shown in Table 2.3. These mechanisms, with the exception of those found in LIL, all reflect a simple statement of content. Because LIL introduces transitive importation of interface information through inheritance, LIL has additional operations to hide and to rename inherited interface data. MIL MIL75 Thomas’s MIL Cooprider’s MIL INTERCOL PIC LIL

Interface Definition Mechanism — consists of — must provide — synthesize — generate-locally — provides — provide — provide — anything in the interface not “hidden”

Table 2.3: MIL Comparison: Export/Interface Definition

2.3. RELATED WORK

17

High-Level Comparison and Analysis of LIL to other MILs Of the six MILs analyzed in this section, only LIL provides inheritance as a structuring mechanism. Furthermore, only LIL supports both the syntactic and semantic specification of a module’s interface. Finally, only LIL has a module parameterization and generation capability. Not only can one describe the system architecture in LIL, but the description can be interpreted resulting in the generation of a new system. The only clear advantage that some of the MILs have over the current LIL implementation (LILEANNA) is that their respective tool sets provide for more static analysis of dependency conflicts (e.g., PIC).

2.2.2

Formal Foundations of LILEANNA

Because LILEANNA is, in principle, an implementation of OBJ [Gog89, KFO87] for Ada, it shares the same formal foundations. OBJ has its formal foundations in category theory and in initial and order-sorted algebras. These concepts form the basis for advances in algebraic specifications and type theory. Many type systems are based on the concept of an algebra. An algebra defines a set of values and the operations on them just as an abstract data type defines data of that type and provides operations on them. Program semantics in LILEANNA are expressed in first order predicate calculus rather than using re-write rules (as in OBJ) as a way of implementing conditional order-sorted equational logic. First-order propositional calculus allows universal and existential quantifiers to express axioms and to define the semantics of the operations. While first-order predicate calculus is a powerful logic system (institution), it is not always easy to manipulate symbolically. Re-write rules are slightly less expressive than first order predicate calculus, but easier to prove things about and with. Technically, LILEANNA’s program structuring mechanisms (horizontal and vertical) are based on two dimensional categories [GB80]. In addition, this approach draws upon the following formalisms [Gog86a]: • • • • •

“colimits to put theories together; diagrams as environments to keep track of shared sub-theories; data constraints to define particular structures (i.e., abstract data types); pushouts to apply generic theories to their “actual” arguments; and theory morphisms to describe the bindings of actuals at interface theories.”

A detailed account of the formalisms in OBJ is found in [Gog89].

2.3

Related Work

This section describes the research activities that focus on some of the same issues addressed by this dissertation. It is broken into two parts. The first subsection presents a summary of programming languages with features similar to some of those found in LILEANNA. These languages were all initially implemented as pre-processors and require a small run-time support environment. Eiffel [Mey88] and C++ [Str86] originally generated C code. InfoAda [SC88] and Classic Ada [Inc88] generate Ada. The second section focuses on some of the work done on formal specifications. Anna [LvH84] at Stanford University, Larch [GHW85, Win87], and the work of Goguen at SRI/Oxford/UCSD: OBJ3 [Gog89], FOOPS [GM87], and finally Clear [BG80] are briefly described.

2.3.1

Programming Languages

Eiffel The work of Bertrand Meyer is supported by an excellent book [Mey88] and a series of articles in the Journal of

18

CHAPTER 2. SETTING THE STAGE

Object-Oriented Programming. Eiffel is an object-oriented programming language14 initially implemented as a pre-processor generating C code. The language comes with a library of reusable classes consisting of data structures and user interface modules. Eiffel language features include: • exceptions — a very clean error recovery mechanism, • dynamic binding — run-time method selection,

• assertions, pre-conditions, post-conditions, and invariants, • multiple inheritance, • classes,

• generic classes — limited to types as parameters, and

• deferred classes — an extension to the inheritance mechanism. Classic Ada Classic Ada is a true object-oriented superset of Ada. A pre-processor generates Ada packages with special calls to a run-time system for message passing and dynamic binding. Classic Ada language features include: • full Ada,

• dynamic binding (Smalltalk self and super constructs),

• message passing,

• multiple inheritance, and • classes.

InnovAda InnovAda is an Ada pre-processor written using the Harris Object-Oriented Programming System HOOPS) [Kov87]. Its design goal was to allow programs to be rapidly prototyped in LISP or Smalltalk, then converted quickly to Ada. InnovAda language features include: • full Ada,

• dynamic binding,

• message passing (Smalltalk self and super constructs), • multiple inheritance, and

• flavors (classes) and methods (subprograms in flavors). C++ C++ is an object-oriented superset of C developed internally at Bell Labs15 , but now having several commercially available implementations. Early implementations were pre-processors that generated C code, with several C++ compilers and support environments now available. Support tools include source-level debuggers and interpreters. C++ language features include: • full C,

• constructs to limit the inherited interface, 14 Some

of the proponents of Eiffel claim it is Ada done right. Koenig at Bell Labs likes to point out that the difference between Ada and C++ is that “programmers don’t have to program in C++” — a reference to DoD’s mandate that all military software be written in Ada. It is the author’s opinion that the object-oriented language features in LILEANNA overcome some of these perceived inhibitors to programming in Ada. 15 Andy

2.3. RELATED WORK

19

• ways for classes to access data representations in other classes (friends), • operator overloading, • multiple inheritance, • classes, • (planned) generic classes [Str88], • virtual functions (similar to deferred operations in Eiffel), and • class initialization.

2.3.2

Formal Specification

Before something can be reused [BR87, Tra88b], a programmer needs to 1. locate it, 2. understand whether it will perform the desired function, 3. understand how to specify the necessary parameters in order to invoke or instantiate it, and 4. have the confidence that it will not cost more time to reuse it than to write it from scratch [EW87]. Formal specifications assist in each of these four areas [Gau87]. According to the participants in the Formal Methods for Reuse Working Group at the UK Workshop on Software Reuse, May 1-2, 1986, sponsored by Alvey Directorate and held at University College of Wales, Aberystwyth, formal specifications: 1. help capture a design, 2. do not help in the retrieval of a component16 , 3. help evaluate the suitability of a component, 4. aid in maintenance, 5. aid in the acceptance/trust level, 6. help against misuse, and 7. serve as valuable documentation. There are many forms of specifications [GHW82]. Functional specifications state what the software does, not how it does it. They capture the essence of the abstraction and are most often associated with the Hoare pre- and postconditions and axioms [Hoa69]. Behavioral specifications deal with operational characteristics (e.g., sequencing of events). LIL (and LILEANNA) emphasizes the former rather than the latter, although the underlying specification language (ANNA [LvHKBO87]) could be extended to address these aspects17 . Finally, specifications do not always need to be machine processable. Often graphical or textual documentation is sufficient to describe the software and assist in the retrieval process. 16 There is currently some debate over this “result” in academic circles as witnessed by the results found in [Tra93c]. The initial results were based on the rather pragmatic observation that formal specifications are often harder to write than an actual implementation, consequently would not be a practical query mechanism. Subsequent research efforts [ZW93] and [JC95] have demonstrated some success in this method. 17 Rapide [LVB+ 92] and TSL (Task Sequencing Language) [HL85], which originated from the same research group as ANNA, do provide capabilities to specify operational behavior. Since they were developed using similar technology and support Ada, combining the module composition capabilities of LILEANNA with them would be reasonable to implement.

20

CHAPTER 2. SETTING THE STAGE

Anna Anna (Annotated Ada) [LvH84, LvH85, LvHKBO87] is a specification language for annotating programs written with stylized comments in Ada. It uses a form of first order predicate calculus to describe program semantics. Anna provides for assertions on types, objects, exceptions, subprograms (pre- and post conditions) and packages (axioms). The Anna translator generates run-time assertion tests that are embedded in the target program to provide some degree of verification and additional debug capability. For more information on the language and its application see [LvHKBO87] and [Luc90]. Larch Larch was designed to support a two-tiered approach [GHW85, Win87] to module specification: Larch shared language and Larch interface language. This approach separates programming language issues from fundamental abstractions. Larch uses theories, not initial algebras, to allow side effects and error handling procedures. For a detailed presentation on the Larch methodology, including both tutorials and advanced material along with numerous examples, see [GH93]. OBJ3 OBJ3 [Gog89] is the current implementation of OBJ [Gog79, FGJM85, KFO87]. It is a functional programming language based on algebraic specifications of program semantics. It uses rewrite rules as its logical institution and has an extensive generation capability using generic objects. OBJ3 allows rapid prototyping of new applications by combining executable specifications with actual implementations (e.g., LISP). The design of LIL evolved out of OBJ3. OBJ, which first appeared in 1976, was the outgrowth of Gougens’s work [Gog77] on designing a language for error algebras that would support error handling and partial functions. The initial language has gone through several evolutions (OBJT [Gog79], OBJ0 [Tar81], OBJ1 [GMP83, CGS87], and OBJ2 [FGJM85, KFO87]). Currently, a Kyoto Common Lisp version of OBJ3 has been developed at SRI by Timothy Winkler, Jose M´eseguer, Claude and H´el`ene Kirchner, and Aristide Megrelis. A detailed account of the formalisms in OBJ as well as the history and implementation of OBJ are found in [Gog89]. FOOPS FOOPS (Functional and Object-Oriented Programming System) [GM87, RS92] is a wide-spectrum programming language that combines the functional and object-oriented programming paradigms by supporting persistence. While OBJ uses equational logic, FOOPS uses reflective equational logic. Table 2.4 shows the functional and object-oriented paradigm concepts supported by FOOPS18 . Clear The specification language Clear [BG77, BG80] grew out of early programming language research in designing languages with better facilities for modularity and structuring as well as providing some capability to verify that implementations are refinements of formal module specifications. Clear clearly distinguished and precisely defined the concepts of “horizontal” and “vertical” program structure (a two-dimensional category), each viewed as a form of parameterization. Vertical structure, or programming by stepwise refinement, at the time (mid-70’s), was found in the languages CLU [LMS+ 79] and ALPHARD [WLS76]. Horizontal structure, or programming by analogy/difference, had been introduced by Dijkstra, Dahl, Hoare, and Wirth. Clear introduced the following basic operations for module construction and interconnection of “theories” (formally specified units of modularization): 1. Combine — put together separate theories, 18 This

table is based on a table found in [GM87].

2.4. SELECTION RATIONALE

General Concept Module

21

FOOPS Notation Function Level

is-a Relation Function Procedure

Functional Concept Function-Level Module abstract data type Set of data items of the Same Sort Data Item (value) Subsort Relation Function —

Axiom

Equation

axiom

Creation Deletion

— —

— —

Collections of the Same Kind of Entities Entity

FOOPS Notation Object Level

sort

Object-Oriented Concept Object-Level Module abstract machine Class of Objects

Reduced Term

Object

< fn —

Subclass Relation Attribute Method Equational Characterization of the Method’s Effect Object Creation Object Deletion

(unique) Identifier < attr method

fmod

omod

class

axiom new remove

Table 2.4: Functional and Object-Oriented Programming Constructs in FOOPS 2. Derive — “abstracts” a subtheory from an existing theory, 3. Enrich — extends a theory by adding new operations and/or types, and 4. Apply — a form of composing and instantiating a parameterized theory.

2.4

Selection Rationale

The following two sections provide insight into the motivation for selecting Ada and Anna as the baseline technologies for this dissertation.

2.4.1

Ada as a Target Language

Ada was selected as the base language for LILEANNA because it encourages the type of modularization, parameterization, and software development methodology that is suitable for developing and using reusable software when building large, complex programming systems (ALRM section 1.3). While Ada does not provide the orthogonal parameterization [Lam87] capabilities available in other languages (e.g., Smalltalk and C++), this is not a severe handicap, since LILEANNA is a design language19 rather than an implementation language. Therefore, LILEANNA is useful for laying out the structure of a software system by specifying the relationships between the modules/packages. Furthermore, tools have been suggested to automate the implementation process by translating LILEANNA modules into executable Ada code [Tra87a]. Note: the Ada term for module is “package.” These terms will be used interchangeably throughout the rest of this document.20 Some of the parameterization capabilities provided by LILEANNA that are not found in Ada include: 19 Actually, LIL, according to its originator [Gog89], is a “hyperprogramming” language. It is interesting to note, from a historical perspective, that DARPA’s software development research thrust into “megaprogramming” [BS92] bears strong resemblance to the Goguen’s hyperprogramming vision [Gog90b]. The author prefers to view the style of programming supported by LILEANNA as “programming with the large,” in reference to the modules that are being manipulated and generated. 20 At this point one would be tempted to call LILEANNA a PIL (Package Interconnect Language), but that would almost be too difficult to swallow.

22

CHAPTER 2. SETTING THE STAGE

• the association of several bodies with a LILEANNA package (e.g., program families or versions),

• the specification of exceptions and packages as generic formal parameters, • the reuse of “separate” objects by more than one package,

• the use of module expressions to specify generic instances,

• the support of several forms of importation including (multiple) inheritance,

• the selective hiding of operations, types and exceptions in an interface definition (different than Ada’s private (see ALRM section 7.4), • the composition of generic units, and

• the partial instantiation of generic units.

2.4.2

Anna as a Specification Language

Anna was an attractive choice as the underlying language for specifying semantics in LILEANNA because it was designed to annotate Ada programs, the target language. In addition, and probably most important, a suite of tools existed to build the implementation upon [LNR87]. Because executable specifications were an orthogonal issue to this research, Anna provided all the expressive capability necessary to complement the composition and generation emphasis of this thesis. Finally, LILEANNA, as will be discussed in section 6.2.2, was implemented so that the language’s module composition capabilities could easily be transferred to any language that used some form of abstract syntax tree as its intermediate internal representation.

Chapter 3

Module Composition Language Semantics This chapter describes the semantics of the module composition constructs supported by the module composition language LILEANNA1 . This material is based on the paper “Implementation-Oriented Semantics for Module Composition” co-developed with Joseph A. Goguen and included in its entirety as Appendix C of this thesis. The module composition language features described here include information hiding, both vertical and horizontal composition, views for binding modules to interfaces and “module expressions,” which are used to build systems out of component modules. Vertical composition refers to the hierarchical structuring of a system into layers, while horizontal composition refers to the structure of a given layer. Modules may involve information hiding, and views may involve behavioral satisfaction of a theory by a module. A number of “Laws of Software Composition” are given, showing how the various module composition operations are related and how understanding of their semantics aids with the implementation of the language. This semantics is set theoretic, using the technical notions of tuple set, partial signature, and institution, and avoiding more difficult mathematics such as abstract algebra and category theory. This chapter is divided into seven sections. The first section provides background information and introduces the basic data structure (i.e., the module graph) and the notation that is used to describe relationships between modules. The second section defines the mathematical concepts of tuple set, partial signature, institution, and specification module. The third section deals with basic issues associated with overload resolution when flattening an inheritance hierarchy and aggregation, which is a form of multiple inheritance. This is followed by a section on views between modules and renaming. Section five explains the semantics of parameterization and instantiation. The last two sections deal with module expressions and vertical composition. Appendix C provides a more detailed expository, with proofs, regarding the formal constructs we use in this chapter to define the semantics of these module composition constructs.

3.1

Introduction and Background

The approach to module composition described here can be used for many different programming languages, by providing a simple module connection language (MCL) with “module expressions” that say how to manipulate and connect modules in the programming language. The general approach, called “parameterized programming” [Gog89], involves a module specification capability; however, programmers can specify as little of a module as they like, as long as they declare the syntax. Our semantics for module expressions uses simple set theoretic manipulations to define module composition operations 1 These module composition constructs are target language independent. Therefore the semantics described here could be applied to composing modules written in other programming languages.

23

24

CHAPTER 3. MODULE COMPOSITION LANGUAGE SEMANTICS

on both module specifications and implementations. This semantics is intended to aid implementors of module composition facilities; hence it is not a denotational or axiomatic semantics in the usual sense. In particular, it does not directly address the semantics of statements or what happens at compile time or at run time; instead, it is concerned with the semantics of modules and their interconnection, i.e., with what happens at module composition time. For this reason, it abstracts away details of the languages used for specification and programming. Although we avoid complex mathematics, such as abstract algebra and category theory, both the module composition facility and its semantics as described in this thesis are largely inspired by work done using such formalisms, including the Clear specification language [BG77, BG80], and the OBJ [GWM+ ar, FGJM85] system. The approach taken here differs from that of those languages in providing a constructive module composition facility for an imperative programming language. This was contemplated for the CAT [GB80] and LIL [Gog85] systems, which are the closest ancestors of this approach; however, details of the semantics of these systems were not fully developed and they were not implemented. The set theoretic implementation-oriented semantics in this thesis is novel. Sannella [San82] gave a pioneering set theoretic semantics for Clear. The elegant work of Wirsing [Wir86] on the ASL kernel specification language also involved a set theoretic approach. Moreover, many papers have used institutions and category theory to achieve generality. However, it has seemed that the generality of institutions was incompatible with the specificity of set theory, and this is the first place to combine these two approaches, as well as the first to use tuple sets and partial signatures. These notions are necessary because the author’s initial use of set theory to define the semantics of the module composition operations, while sufficient to manipulate module interfaces, proved insufficiently powerful enough to handle the composition of the module’s implementations. The work here also differs from most previous work in the algebraic specification tradition in that it is concerned with generating systems, rather than just specifying them, i.e., it is constructive as well as descriptive. Another unusual feature is the use of information hiding in specifications and the resulting behavioral (i.e., “black box”) notion of satisfaction for views. The version of parameterized programming described here provides both horizontal and vertical composition. Vertical composition concerns the structuring of a system into layers, each providing services to higher layers, while horizontal composition concerns the structure of each layer.

3.1.1

The Module Graph

A module graph is the basic data structure for our semantics; its nodes are module names, and its edges indicate relationships between modules (e.g., inheritance). Each module name M has associated to it a specification module Q(M ), and a list I(M ) of implementation modules. Definition: A module graph G consists of a finite set N of nodes, called module names, a finite set E of edges, two functions, d0 , d1 : E → N , which respectively give the source and target node for each edge, an acyclic subgraph H, called the inheritance graph, an acyclic subgraph P, called the parameter graph, and an acyclic subgraph V, called the vertical dependency graph. The edges of H, called inheritance edges, are a subset I of E, and the source and target functions of H agree with d0 and d1 on I. We write N < M if there is a path from N to M in H. We may use the notation e : M → M to indicate that e is an edge with d0 (e) = M and d1 (e) = M .

Intuitively, the edges of the module graph indicate relationships between modules, of which the most basic are those of inheritance, indicated by the edges in the inheritance subgraph2 . N < M means that M inherits from N . This relation is transitive because the composition of two paths is another path. (The idea of module graphs may be found in [Gog91b].) Another kind of relationship between modules, indicated by edges in the module graph, is given by a view, which asserts that the target module satisfies the axioms given in the source module. The module graph G describes the resources available at a given moment for building systems, including both modules and knowledge about their properties; the role of G is analogous to that of an environment for evaluating expressions in a programming language. 2 Instead

of having a subgraph H of G, we could put the label “inherits” on all inheritance edges, as suggested by Figure 3.1.

3.1. INTRODUCTION AND BACKGROUND

25

Example: Assume modules M 1, M 2, M 3 such that module M 3 inherits M 2 and module M 2 inherits M 1, i.e., M 1 < M 2 < M 3. Then the module graph G has three nodes, named M1 , M2 and M3 , and two edges, one from M1 to M2 , and one from M2 to M3 , as shown in Figure 3.1. Both edges are inheritance edges.

#

#

à inherits

M1 "

-

!

#

à inherits

M2 "

!

-

à M3

"

!

Figure 3.1: A simple module graph

3.1.2

The Notation for Module Composition

We use the notation M H [P :: TP ](V :: TV ) for a module inheriting a set H of module names, horizontally parameterized by P with interface theory TP , and vertically parameterized by V with interface theory TV ; this situation is illustrated in Figure 3.2. We write M H [P ](V ) for an application of M to actuals P and V . Vertical composition uses relations of refinement [MG94], rather than relations of ordinary satisfaction.

#

à inherits

H "

!

Ã

# -

M "

¾ parameterized by

P :: TP

!

6 needs (virtual machine layer)

V :: TV

Figure 3.2: Horizontal and vertical relationships for M H [P :: TP ](V :: TV )

3.1.3

Laws of Software Composition

The notation introduced in the previous section facilitates the specification and analysis of module compositions. The following is a partial list of the “Laws of Software Composition” that are presented in this chapter and later fully defined in Appendix C. Some of them make use of the “aggregation” module operation “+” that will be defined in section 3.3.

26

CHAPTER 3. MODULE COMPOSITION LANGUAGE SEMANTICS (Law 5) M + M = M + M , (Law 6) M + (M + M ) = (M + M ) + M , (Law 7) M + M = M , (Law 8) M ⊆ M + N ,

(Law 9) N ⊆ M implies M + N = M ,

(Law 10) M H + M

H

= (M + M )H ,

(Law 11) M N1 ,N2 = M N2 ,N1 = M N1 +N2 , H

(Law 12) M N1 (Law 13) M

3.2

N

, N2H

H

= M (N1 +N2 )

,

N

+N =M .

Mathematical Concepts

There are two main technical ideas that make our goal to specify the semantics of module composition language constructs feasible: (1) tuple sets, which allow simple recursive definitions of basic set theoretic operations like union and inclusion for complex structures like signatures and specifications, and (2) institutions, which axiomatize the intuitive notion of logical system. Tuple sets permit simplified definitions of operations for combining modules, based on set theory. They also permit us to define the notion of partial signature, which again simplifies our definitions. Institutions let us handle both specification and programming languages while avoiding the enormous complexity of their semantic definitions.

3.2.1

Tuple Sets

We will later assume that there is a given fixed class Sign of signatures, which are used for declaring the syntax of modules, such that all signatures are tuple sets, in the sense of definition below. In this recursive definition, (0) is the base case and (1) is the recursive step. It may help the reader to visualize tuple sets as ordered trees that have sets on their leaf nodes. Definition: A tuple set is either (0) a set, or else (1) a tuple of tuple sets. Two tuple sets have the same form if and only if they are both sets, or else they are tuples of the same length such that their corresponding components have the same form. More formally, t ∼ t iff (0) t and t are both sets, or else (1) t and t are tuples of the same length, say n, and ti ∼ ti for i = 1, ..., n. We will use the notation (t1 , ..., tn ) for a tuple having n components, called an n-tuple. The sets that occur as the bottom level components of a tuple set are called its base sets, and their elements are called its symbols. Figure 3.3 shows a typical tuple set structure, where the circles represent the base sets of symbols, and the internal nodes represent tuples. Sometimes we want to use pairs, triples, lists, etc. that are not tuples. For these cases, we will use the notation [t1 , ..., tn ] for a list of length n, and call it a pair when n = 2 and a triple when n = 3. Example 3.1: Any set S is a tuple set. Also, any tuple of sets is a tuple set, e.g., ({a, b}, {1, 2}). Similarly, any tuple of tuple sets is a tuple set, e.g., ({a, b, c}, ({a, b}, {1, 2})). And so on recursively. The tuple set ({a, b, c}, ({a, b}, {1, 2})) has symbols a, b, c, 1, 2. On the other hand, the tuple set {[0, 0], [−, 1]} has as its symbols the lists [0, 0] and [−, 1], rather than 0, 1, and −.

3.2. MATHEMATICAL CONCEPTS

27

r ¡ @ ¡ j@ ¡r @r ¡ @ ¡ ¡ @@ j j

¡r ¡ @ j¡ j@ j

@ r ¡ @ @ j j¡

Figure 3.3: A tuple set structure

Definition: Two tuple sets, t, t are equal, written t = t , iff they have the same form and all their corresponding components are equal, i.e., if and only if t and t have the same number of components, say n, and ti = ti for i = 1, ..., n. We define inclusion similarly: t ⊆ t iff t and t have the same form, with say n components, and ti ⊆ ti for i = 1, ..., n. Finally, the difference (or the union) of two or more tuple sets, all of the same form, is formed by taking the difference (or union) of the tuple sets occurring as their components. Because signatures are tuple sets, the above definition gives us a natural notion of subsignature: Σ is a subsignature of Σ iff Σ ⊆ Σ as tuple sets. For example, we have (H, (T, O, V, E), (TX , OX , VX , EX ), A) ⊆ (H , (T , O , V , E ), (TX , OX , VX , EX ), A ) if and only if H ⊆ H and T ⊆ T and O ⊆ O and so on.

The assumption that signatures are tuple sets also gives us a natural notion of signature map. Here, we define it as a special case of the (also natural) notion of a relation between two tuple sets of the same form, as a tuple set again having the same form, and having pairs of symbols as its own symbols.

Definition: Given tuple sets Σ, Σ of the same form, then a tuple set relation Σ → Σ is a tuple set3 of pairs of symbols (i.e., a subset of Σ × Σ ). A tuple set relation is a tuple set map iff each of its base sets is a relation that is actually a function. A tuple set map is injective or surjective iff each of its base set functions is. Similarly, a tuple set map is an isomorphism iff each of its base set functions is; in this case we may write Σ ≈ Σ .

Given a tuple set Ω and a tuple set relation h : Ω → Σ, let Ω ∗ h be the tuple set with each symbol a in each base set B of Ω replaced by the corresponding symbols a in pairs [a, a ] in the corresponding base set of h, for each symbol a in Ω, i.e., (Ω ∗ h)p = {a | a ∈ Ωp and [a, a ] ∈ hp } ,

where Ωp denotes the base set of Ω at the end of the path p. We call Ω ∗ h the renaming of Ω by h. We may use the same notation when no target signature is given, since a minimal appropriate signature can be recovered from a tuple set of pairs with symbols from Σ.

3.2.2

Partial Signatures

Definition: Given a collection Sign of tuple sets called “signatures,” let Sign − denote the least class of tuple sets that contains Sign and is closed under tuple set difference. The elements of Sign − will be called partial signatures. The union of a partial signature with a signature can be a signature; for example, if Σ and Σ are signatures with Σ ⊆ Σ, then Σ − Σ is a partial signature, and Σ ∪ (Σ − Σ ) = Σ. (We could use a signature inclusion Σ → Σ to represent the difference Σ − Σ . This would be adequate because we only need first order differences, and it would be more elegant, but it is less concrete.) Example: A lileanna signature has the form (T, O, V, E), where 3 Technically,

we should also add the source and target tuple sets to this tuple set of pairs.

28

CHAPTER 3. MODULE COMPOSITION LANGUAGE SEMANTICS

• • • •

T is the set of visible (i.e., exported) type declarations, O is the set of visible (i.e., exported) operator declarations (with their input and output types), V is the set of variable declarations (including types), and E is the set of exception declarations.

The statements in figure 3.4 form a partial signature, because the types Integer and Boolean are used but not declared (they are assumed to be imported). Here T contains Stack, O contains Is_Empty, Is_Full, Push, Pop, and Top, while E contains Stack_Empty plus Stack_Overflow, and V is empty.

type Stack; function Is_Full ( S: Stack ) return Boolean; function Is_Empty ( S: Stack ) return Boolean; exception Stack_Overflow; exception Stack_Empty; function Push ( I: Integer; S: Stack ) return Stack; function Pop ( S: Stack ) return Stack; function Top ( S: Stack ) return Integer; Figure 3.4: lileanna signature for bounded stack of integers

3.2.3

Institutions

This subsection presents an abstract framework that allows a broad range of notions of signature and axiom for use in specifications, as well as of models for use as implementations. For example, specifications can be written in Anna, and implementations in Ada; other pairs of a specification and an implementation language could also be used. We achieve this generality by using an axiomatization of the notion of “logical system” called an institution. In this way, we avoid having to work with a fixed logical system, with a fixed notion of axiom for specification modules, and a fixed notion of model. Instead, we can use any logical system that satisfies the reasonable assumptions given in Definition below. An intuitive explanation of each condition is given in the paragraph following the formal definition; the reader may wish to read this material in parallel with the formal definition. Definition: An institution satisfies the following: 0. There is a class4 Sign of signatures. 1. For each signature Σ, there is a set Sen(Σ) of what we will call (well formed) sentences built using Σ. 2. For each signature Σ, there is a class M od(Σ) of Σ-models, which provide (what we will call) interpretations for the symbols in Σ. 3. For each signature Σ, there is a satisfaction relation between Σ-sentences and Σ-models, written c |=Σ a, where c is a Σ-model and a is a Σ-sentence; we may omit the subscript Σ on |=Σ if it is clear from context. We pronounce c |= a as “c satisfies a”. 4. For any signatures Σ and Σ , there is a set M or(Σ, Σ ) of signature maps from Σ to Σ , where h ∈ M or(Σ, Σ ) may be written h : Σ → Σ . There is a composition defined for signature maps, a function M or(Σ, Σ ) × M or(Σ , Σ ) → M or(Σ, Σ ) for each Σ, Σ , Σ , which is associative and has an identity 1Σ in M or(Σ, Σ) for each Σ. We use the notation h; h for the composition of h in M or(Σ, Σ ) with h in M or(Σ , Σ ). Then we have (h; h ); h = h; (h ; h ) and h; 1ΣI = h and 1Σ ; h = h for suitable h, h , h . 4 This thesis seeks to avoid being distracted by foundational problems, but of course we wish to ensure that everything we do is sound. For this reason, when we encounter collections that may be too large to be sets, we will speak of classes, in the sense of G¨ odel-Bernays set theory.

3.2. MATHEMATICAL CONCEPTS

29

5. Given a signature map h : Σ → Σ and a Σ-sentence a, there is a renaming of a by h, also called a translation of a by h, denoted a ∗ h, which is a Σ -sentence, such that a ∗ 1Σ = a and a ∗ (h; h ) = (a ∗ h) ∗ h , for h : Σ → Σ and h : Σ → Σ . 6. For each signature map h : Σ → Σ and each Σ -model c , there is a renaming of c by h, denoted c ∗ h, a Σ-model such that c ∗ 1Σ = c and c ∗ (h; h ) = (c ∗ h ) ∗ h. 7. Given a Σ-sentence a, a Σ -model c and a signature map h : Σ → Σ , we require that c |=ΣI a ∗ h iff c ∗ h |=Σ a . (This is called the satisfaction condition.) We say that a Σ-model c satisfies a set A of Σ-sentences iff it satisfies each one of them, and in this case we write c |=Σ A.

Condition 0. in the above definition just says that we have signatures for declaring notation to be used in sentences and models. In our examples, signatures have types and operators, private types and private operators, and exceptions. Condition 1. says that there are some sentences. In our examples, Σ-sentences are built using the types, operators and exceptions in Σ as non-logical symbols. Condition 2. says that there are some concrete entities, such as Ada programs, to which sentences can refer (provided the signatures match). In our examples, Σ-models provide concrete data representations for the types in Σ and concrete operators for the operator symbols in Σ. Condition 3. introduces the mechanism through which sentences can refer to models: a model may or may not satisfy a given sentence; then a set of sentences determines the class of all models that satisfy all the given sentences, i.e., that meet the given requirements. Condition 4. introduces “signature maps,” which allow you to change notation. The composition of two signature maps describes the change in notation resulting from first applying one and then the other. It is intuitive that such a composition operation should be associative, and should have an “identity” map that indicates the substitution of each non-logical symbol in the signature for itself, i.e., no change in notation. Condition 5. introduces the renaming operation for sentences, and states two properties that intuition says it should satisfy. Condition 6. gives the corresponding operation and properties for renaming models; it is interesting to notice the reversal of direction in model renaming. Finally, condition 7. gives a property relating sentence and model renaming through satisfaction; this condition essentially says that truth is invariant under changes of notation. More information about institutions can be found in [GB92].

3.2.4

Specification Modules

Parameterized programming has two kinds of specification module: (1) theories and (2) packages. Both have signatures, which give the syntax for the module. Theories define properties of other modules, while packages specify what is to be implemented. A specification module is attached to each module name (in the module graph G), as a “header,” to (partially) describe the implementations (if any) attached to that name; it should at least include declarations for whatever is exported. Specifications are attached to the nodes of the module graph according to the following assumption; the actual form of specification modules is given below. Assumption: For each module name M in the module graph, there is given a specification module Q(M ), with a tag saying whether it is a theory or a package5 . Both the signature and sentences of each module Q(M ) are from a given fixed institution. Note that different module names could have the same specification module; i.e., we can have Q(M ) = Q(M ) with M = M . Note also that specification modules are tuple sets, because they are 4-tuples having two sets and two tuple sets as components (see the specification module Definition below). For convenience, we may write M when we really mean the specification module Q(M ) that is assigned to the module name M . Definition: A specification module is a tuple of the form (H, Σ, Ψ, A), where

5 Technically, this means giving two functions from the node set N of G: one function (namely Q) to the class of specification modules, and the other, let us denote it t, to the set {th, pkg}. We will see later that we can reconstruct the function t from other data associated with G.

30

CHAPTER 3. MODULE COMPOSITION LANGUAGE SEMANTICS

• • • •

H is a set of module names (for its imported modules), Σ is a partial signature (to declare its types, operators, etc.), Ψ is a sub-partial signature of Σ, called the visible signature6 (for its exported types, operators, etc.), and A is a set of (Σ ∪ |H|)-sentences (also called axioms), where  |Q(N )| , |H| = N ∈H

and where |(H, Σ, Ψ, A)| = Ψ ∪ |H| and Σ ∪ |H| are both signatures.

We may call Σ the local signature of M to distinguish it from the visible signature Ψ of M , and we may call |M | the flat visible signature of M . Let us write H(M ) for the H component of M , Σ(M ) for its signature, Ψ(M ) for its visible signature, and A(M ) for its axioms. We call M atomic if H(M ) = ∅; note that7 in this case, |M | = Ψ(M ). Finally, we let ||(H, Σ, Ψ, A)|| = Σ ∪ |H| ,

and call it the working signature of the module; it contains all the non-logical symbols8 that can be used in the axioms of the module. It is required that ||M || be a signature.

For convenience, we may speak of the axioms of module M as a shorthand for the A component of the tuple Q(M ) associated to the name M , i.e., A(Q(M )). Note that |M | includes all the visible symbols that M inherits from other modules. Also note that the two definitions of | | are recursive over the inheritance hierarchy, and co-recursive with each other. Finally note that only visible symbols from inherited modules appear in the working signature, whereas hidden symbols from the signature of the current module appear9 . The definition for specification module implies that inheritance is transitive, in the sense that if M3 imports M2 and M2 imports M1 , then everything visible in M1 is also imported into M3 . We do not require M1 to appear in the import set of M3 , only that M1 appear in the import set of M2 and M2 in that of M3 . (See Example 3.1.) Example 3.2: Given module names M1 , M2 , M3 with associated specification modules Q(Mi ) for i = 1, 2, 3, suppose that H(M1 ) = ∅, H(M2 ) = {M1 }, and H(M3 ) = {M2 }. Then |H1 | = ∅ |H2 | = Ψ1 |H3 | = Ψ1 ∪ Ψ2

|M1 | = Ψ1 |M2 | = Ψ1 ∪ Ψ2 |M3 | = Ψ1 ∪ Ψ2 ∪ Ψ3

||M1 || = Σ1 ||M2 || = Σ1 ∪ Ψ2 ||M3 || = Σ1 ∪ Ψ2 ∪ Ψ3

Proposition: If M is a specification module then |M | ⊆ ||M ||, and if H(M ) = {N }, then |N | ⊆ |M |. More generally, if H(M ) = H, then  (Law 1) |N | ⊆ |M | , N ∈H

and if10 N < M then |N | ⊆ |M |.

If M is a module that inherits a set H of modules, then we may use the notation M H to emphasize the role of H; however, this superscript is not really needed, because H is already given in the associated specification module Q(M ). We may also write M N when the inheritance set for M is {N }, M N1 ,N2 when it is {N1 , N2 }, etc.

Example 3.3: Figure 3.5 shows three lileanna specification modules11 . The first is a theory called ANY TYPE; it has a single type Element, and nothing more; it can be satisfied by any module that has a type. 6 The symbols in Σ but not in Ψ are “local” or “private” types, operators, variables, etc. used in axioms to describe the semantics of operators, exceptions, etc.; they are not exported. This gives us an information hiding capability, in the sense of [Par72a]. 7 This is because of the convention that f (M ) = ∅ for any formula f , i.e., the result of an empty union is empty. M ∈∅ 8 The so called logical symbols are those that are used for combining the non-logical symbols; typical examples are ∨, ∧, ¬ and ∀. 9 The distinction between the visible (exported) operations and the hidden (internal or auxiliary) operations allows us to treat information hiding, but it also makes our theory more complicated. 10 Recall that this means that there is a path from N to M in the inheritance graph. 11 We adopt the convention that module names have all capital letters, and appear in the text in typewriter font. Type and operation names will also appear in this font in the text.

3.2. MATHEMATICAL CONCEPTS

theory ANY TYPE is type Element; end ANY TYPE; theory MONOID is type Element; function ”∗” ( X, Y : Element) return Element; − − |axiom −−| for all X, Y, Z : Element: => −−| (X ∗ Y) ∗ Z = X ∗ (Y ∗ Z) ; −− associative property − − |axiom −−| for all X: Element: => −−| exist I: Element: => −−| ( X ∗ I = X ) and( I ∗ X = X ) ; −− identity property end MONOID; theory EQV is type Element; function Equal ( X, Y : Element ) return Boolean; − − |axiom −−| for all E1, E2, E3 : Element => −−| Equal ( E1, E1 ), −−| (Equal ( E1, E2 ) and −−| Equal ( E2, E3 ) − > Equal ( E1, E3 )), −−| (Equal ( E1, E2 ) − > Equal ( E2, E1 )); end EQV; Figure 3.5: lileanna theory specifications

31

32

CHAPTER 3. MODULE COMPOSITION LANGUAGE SEMANTICS

The second theory, MONOID, contains a single type and one operation that takes two parameters of that type and returns a Boolean value. This theory also contains two axioms. The Anna “annotation” languages uses conventions, stylized Ada comments (following the symbol --|) to assert formal semantics of this lileanna specification module. An Ann annotation consists of variable declarations, quantifiers, and boolean expressions, whose values are asserted to be true using the symbol =>.” The first axiom asserts the associative law for the operation ∗, and the second asserts the identity law. The third theory, EQV, for equivalence relations, is similar to MONOID in having a single type and three assertions for its operation, which are reflexivity, symmetry, and transitivity, given by three Anna assertions, separated by commas; the last two assertions use Anna’s implication symbol “->.”

package INTSTACK is inherit INTEGER; type Stack; Stack_Empty : exception ; Stack_Overflow: exception ; function Is_Empty ( S: Stack ) return Boolean; function Is_Full ( S: Stack ) return Boolean; function Push ( I: Integer; S: Stack ) return Stack; − − | where −−| Is_Full ( S ) => raise Stack_Overflow; function Pop ( S: Stack ) return Stack; − − | where −−| Is_Empty ( S ) => raise Stack_Empty; function Top ( S: Stack ) return Integer; − − | where −−| Is_Empty ( S ) => raise Stack_Empty; − − |axiom −−| for all I: Integer; S: Stack => −−| not Is_Full ( S ) − > −−| (Pop ( Push ( I, S ) ) = S) and −−| (Top ( Push ( I, S ) ) = I ); end INTSTACK; Figure 3.6: lileanna bounded integer stack package specifications

Figure 3.6 shows a package specification called INTSTACK for a bounded stack of integers; it has some relevant exceptions, and it inherits the module INTEGER, which defines the integers with appropriate operations (i.e., H(INTSTACK) = {INTEGER}). The axioms describe how the operations change the state of objects of type Stack. The exception annotations on the operations Push, Pop and Top specify when these exceptions are raised. Theory modules are not intended to be implemented, but instead are used to define interfaces and declare properties. For example, the theory MONOID an the appropriate interface for iterators over certain data structures (see [Gog89] for details). On the other hand, package modules like INTSTACK are intended to be implemented, and this may be done in a variety of ways. The module INTSTACK also illustrates the use of partial specifications, because no axioms are given for Is Full. The package INTSTACK illustrates three of Anna’s formal specification mechanisms: subprogram annotations, exception annotations, and axioms. Anna also has object annotations, type and subtype annotations, propagation

3.3. OVERLOAD RESOLUTION AND AGGREGATION

33

annotations, context annotations, “virtual functions,” and package states in axioms. It is our goal to give a semantics for module composition, taking advantage of the fact that semantics have already been given for Anna and Ada; [LvH85] gives a good overview of Anna with examples. The following law says that all the exported signature symbols and all the visible consequences of axioms from inherited modules are available to an importing module: Proposition: If M N is a specification module with H(M ) = {N }, then Vth(N ) ⊆ Vth(M N ). More generally, if H(M ) = H, then  (Law 2) Vth(N ) ⊆ Vth(M H ) , N ∈H

and if N < M then Vth(N ) ⊆ Vth(M ). The same results hold for T h(M ).

3.3

Overload Resolution and Aggregation

This section deals with basic issues associated with overload resolution when flattening an inheritance hierarchy and aggregation, which is a form of multiple inheritance. We will assume below that each symbol in a module has a “true name” that is tagged with the name of the module where it is declared. Because it is awkward to use such names in practice, we want a parser that can recover the true name from a “nick name” that omits the tag; the process of finding the closest true name corresponds to what is called “dynamic binding” in the object oriented community. Because signatures are tuple sets, the required tagging can be accomplished by replacing each symbol σ by the pair [σ, M ], where M is the specification module involved; we will write this more simply as σ M . Forming a tuple set of such pairs can be seen as a new operation on tuple sets. Definition: Given a signature map h : Ψ → Ω with Ψ ⊆ Σ such that Ω is disjoint from Σ − Ψ, we can extend h to a map h : Σ → Ω = Ω ∪ (Σ − Ψ) by defining h = h ∪ (1Σ−Ψ ).

The symbols in the local and visible signatures of each specification module are tagged with the module name. If we let Q0 (M ) = (H, Σ, Ψ, A) denote the untagged form of the module associated to the name M , then Q(M ) = Q0 (M ) M . In particular, if Σ and Σ are the local signatures of modules with distinct names M and M , respectively, then Σ and Σ are disjoint.

Example 3.4: As in Example 3.1, assume module names M1 , M2 , M3 with associated specification modules Q(Mi ) for i = 1, 2, 3, such that H(M1 ) = ∅, H(M2 ) = {M1 }, and H(M3 ) = {M2 }. Also assume that Σ(M1 ) = {[a, 0], [b, 0], [c, 0]} Ψ(M1 ) = {[a, 0], [c, 0]} Σ(M2 ) = {[a, 0], [d, 0]} Ψ(M2 ) = {[d, 0]} Σ(M3 ) = {[c, 0], [d, 0]} Ψ(M3 ) = {[c, 0]} . Then the “tagged” forms of these signatures are as follows: Σ M1 = {[a, 0] M1 , [b, 0] M1 , [c, 0] M1 } Ψ M1 = {[a, 0] M1 , [c, 0] M1 } Σ M2 = {[a, 0] M2 , [d, 0] M2 } Ψ M2 = {[d, 0] M2 } Σ M3 = {[c, 0] M3 , [d, 0] M3 } Ψ M3 = {[c, 0] M3 } .

We want a parser that will allow us to use the symbols that appear in Q0 (M ) as shorthand for the full notation that appears in Q(M ), i.e., to use the simpler notation σ for a symbol σ M in Q(M ). This parser should disambiguate a symbol σ occurring in M according to the following rules: 1. if the symbol σ is declared in M , then σ is parsed as σ M ; 2. if σ is not declared in M but is declared in some modules inherited by M , then it is parsed as σ N where N is the unique least module12 inherited by M that declares σ, if there is one; 12 In

the sense that the path from N to M is shortest in H having this property.

34

CHAPTER 3. MODULE COMPOSITION LANGUAGE SEMANTICS

3. otherwise, a parse error is produced for σ in M . (In this case, the label σ cannot be disambiguated by the parser, and should have been explicitly written σ N for some module name N .) Example 3.5: Assume modules M1 , M2 , M3 as in Example 3.4 above. Then the overload resolution mechanism would give the results shown in Figure 3.7 below. Module M1

M2

M3

Symbol [a, 0] [b, 0] [c, 0] [a, 0] [b, 0] [c, 0] [d, 0] [a, 0] [b, 0] [c, 0] [d, 0]

Overload Resolution Result [a, 0] M1 [b, 0] M1 [c, 0] M1 [a, 0] M2 parse error [c, 0] M1 [d, 0] M2 [a, 0] M1 parse error [c, 0] M3 [d, 0] M3

Figure 3.7: Results of overload resolution

3.3.1

Implementation Modules

We are now ready to say what is an implementation for a given package specification module M . Note that we do not need to implement what is hidden inside of M . In fact, we take an implementation of a module to be a model of what it exports as well as everything it inherits. This provides a precise correctness criterion for code while avoiding many complications. At a given point in a software development project, there may be zero or more implementation modules associated to a given module name M . These fit into the module graph structure that we are developing as follows: Assumption: There is a partial function I from module names to lists of implementation modules, defined on the names of package specification modules, and not on those of theory specification modules13 . (Note that I(M ) may be the empty list.) Each implementation module in I(M ) defines an implementation for Q(M ), for each module name M in the module graph G. Figure 3.8 may help the reader to visualize some of the structure that is associated with module names in the module graph; recall that the function I is partial, and that the function t can be eliminated. Now that we have defined specification and implementations, we can begin to develop the operations for composing modules, and to prove some of their properties.

3.4

Views and Renaming

The notion of view introduced here differs from previous notions in that it respects information hiding, i.e., it only requires behavioral satisfaction of axioms; it also takes account of imported modules. A view is a signature map used to assert that some theory is satisfied by some other specification module; more technically, a view binds the “formal” symbols in the signature of the source theory to “actual” symbols in the 13 This means that we do not really need the tagging function t, because we can determine the kind of specification module by looking at the domain of definition of I.

3.4. VIEWS AND RENAMING

35

#

à M

² ±

´ +´ Q(M )

´

Q´ ´ ´ ¯ °

Q! Q

´" t

? ² th / pkg ±

Q I Q

¯ °

Q Q Qs ² C1 , C2 , ..., Cn ±

¯ °

Figure 3.8: Attributes of a module name

target module, which may be either a theory or a package, in such a way that the proof obligations arising from the signature map are satisfied. The definition below makes this precise, using the notation of defined as part of signature maps in section 3.3. Definition: A view from a specification module M = (H, Σ, Ψ, A) to a specification module M = (H , Σ , Ψ , A ) is a signature map h : |M | → |M | such that Ψ ∗ h ⊆ |M | and T h(M ) |=||M I || a ∗ h for each a ∈ Vth(M ), i.e., such that T h(M ) |=||M I || Vth(M ) ∗ h. In this case, we may write h : M → M . We may also call a view a specification module map. In practice, we often have H ⊆ H , so that a view h : M → M can be given by just a signature map h : Ψ → Ψ , which then extends to a map h : Ψ∪|H| → Ψ ∪|H |, provided Ψ is disjoint from |H|. In this case, the proof obligations are just that T h(M ) |=||M I || a ∗ h for each a ∈ T h(A), i.e., that the translations of the visible consequences of axioms in A hold for any model of T h(M ). This means that some axioms in M , namely those involving “hidden” types and/or operations, need only “appear” to be consequences of the axioms in M ; i.e., we are dealing with what is called “behavioral satisfaction,” as is appropriate to the “black box” notion of module used in this paper. Techniques for proving this kind of satisfaction have been developed in hidden sorted algebra; see [GM96b, GM96c, GD94]. For example, in the example of a STACK module with type Stack hidden, the equation pop(push(I,S)) = S is not satisfied by the standard implementation using an array and a pointer, but all of its visible consequences are. So a view from M as this STACK specification module to another more concrete specification module M for stacks implemented with an array and a pointer would satisfy the condition found in the definition of a view, since everything in Vth(M ) is satisfied by M . Example 3.6: The lileanna code in Figure 3.9 gives the theory of partially ordered sets; the axioms say that the operation “ Natural ); −− no ops map - take the default end NAT_DEF; Figure 3.10: Views of Natural numbers as POSET

3.4. VIEWS AND RENAMING

37

Note that if h in the above definition is not injective, then some sorts and operations may be “munged” together. Then the resulting single operation will have to satisfy the union of (the translated versions of) all the axioms for the operations that were identified. This could be useful, e.g., if we want a single operation to accomplish a number side effects that were previously done by separate operations.

3.4.1

Hiding and Enriching

We now discuss two operations on modules (or systems, or subsystems) that were not implemented in Clear or OBJ, although similar ideas were proposed for LIL in [Gog85]. These operations allow deleting some of the visible interface of a module, and also adding to it. lileanna syntax for the first operation is M ∗ (hide Φ), and for the second is M ∗ (add Φ, A), where Φ is a partial signature and A is a set of axioms. We can give precise definitions for these operations in our set theoretic semantics as follows: Definition: Given a specification module M = (H, Σ, Ψ, A) and a partial subsignature Φ ⊆ Ψ, we define M ∗ (hide Φ) = (H, Σ, Ψ − Φ, A) , where it is required that ((Ψ − Φ) ∪ |H|) is a signature.

Given a partial subsignature Φ disjoint from ||M || and a set A of (Σ ∪ Φ ∪ |H|)-axioms, let M ∗ (add Φ, A ) = (H, Σ ∪ Φ, Ψ ∪ Φ, A ∪ A ) , where it is required that (Σ ∪ Φ ∪ |H|) and (Ψ ∪ Φ ∪ |H|) are signatures.

The operations for renaming, hiding and adding to implementations are very useful as part of a module connection language, because existing modules often do not do exactly what is wanted. Another useful operation on implementations has a more ad hoc character; simply adding to the signature and code. For example, to add a new enumeration literal to an enumerated type, the lileanna syntax is: C ∗ (add literal to TypeName , Literal ). Transforming Implementations Users of a system like lileanna will in general be more interested in implementations than in specifications. Hence we should investigate transformations of implementations. We first discuss renaming. As with condition 6. in the definition of institution, renaming implementations moves in the opposite direction from that of the signature map involved, whereas for specifications it moves in the same direction. Thus, there is a duality between specifications and implementations, parallel to the duality between sentences and models, as reflected in the following: Proposition: If h : M → M is a view and C is an M -module, then C ∗ h is an M -module. Let [[h]] : [[M ]] → [[M ]] denote the function that sends C to C ∗ h. Copying and renaming of implementations is surprisingly powerful: it allows deleting functionality from an implementation, as well as adding new functionality that is defined in terms of what is already present; it also allows making one or more new copies of items exported by a module. We first consider the case of adding functionality.

Definition: Given a specification module M = (H, Σ, Ψ, A), an implementation C for M , and a partial subsignature Φ ⊆ Ψ, let i denote the inclusion of Ψ − Φ into Ψ. Then C ∗ i is denoted C ∗ (hide Φ).

Example 3.7: Figure 3.11 illustrates14 how a module can be transformed using simple renaming, hiding, and enriching. Here, the operations in an inherited package have been extended by the operation Side. In addition, the type Rectangle has been renamed Square and the two operations (Length and Width) have been hidden (not removed!). Also, an additional axiom has been added. 14 This

example is based on an example found in [Mey88]

38

CHAPTER 3. MODULE COMPOSITION LANGUAGE SEMANTICS

package SQUARE is inherit RECTANGLE ∗ (rename Rectangle => Square) ∗ (add function Side ( S : Square ) return Real) ∗ (hide Width, Length); − − | axiom −−| for all S : Square => −−| Length ( S ) = Width ( S ); end; Figure 3.11: Specifying a SQUARE Package as a Transformation of RECTANGLE

More generally, if h : M → M is not surjective15 , then implementations for symbols in the visible signature of M that are not the image of any symbol in the visible signature of M are dropped (or “sliced”) from C when C ∗ h is formed.

3.4.2

Aggregation

Module aggregation is the “flat” combination of modules. An important issue is sharing common inherited modules; to understand how sharing works under aggregation, recall that all symbols introduced in a module are tagged with the name of that module. By convention, modules that are inherited are always shared : If A and B both inherit M , then the resulting value of the module expression A + B (the aggregation of modules A and B) only has one copy of M ; this is consistent with the way that Ada handles imports. Here is the formalization: Definition: If M = (H, Σ, Ψ, A) and M = (H , Σ , Ψ , A ) are specification modules, then the aggregation or sum of M and M is M + M = (H ∪ H , Σ ∪ Σ , Ψ ∪ Ψ , A ∪ A ) . For convenience, we may also use the notation M ∪ M in some formulae. More generally, given specification modules Mi = (Hi , Σi , Ψi , Ai ) for i = 1, ..., n, let n 3 i=1

Mi = (

n 

i=1

Hi ,

n 

i=1

Σi ,

n 

Ψi ,

i=1

n 

Ai ) .

i=1

We may also use the notation ni=1 Mi . Notice that operators with the same name (say o) introduced in different modules are automatically “named apart” in the aggregation (i.e., they are σ M and σ M , because of the assumption we made about the names of symbols in modules.) Aggregation satisfies some useful properties, which are given below as laws. Example 3.8: Assume module names M1 , M2 , M3 , M4 with associated specification modules Q(Mi ) for i = 1, 2, 3, 4, such that H(M1 ) = {M2 }, H(M2 ) = {M3 }, H(M3 ) = ∅, and H(M4 ) = {M3 }. The module graph is shown in Figure 3.12. Let us also assume that Σ(M4 ) = {[e, 0], [f, 0], [c, 0]} Σ(M3 ) = {[a, 0], [b, 0], [c, 0]} Σ(M2 ) = {[a, 0], [d, 0]} Σ(M1 ) = {[c, 0], [d, 0]}

Ψ(M4 ) = {[e, 0], [c, 0]} Ψ(M3 ) = {[a, 0], [c, 0]} Ψ(M2 ) = {[d, 0]} Ψ(M1 ) = {[c, 0]} .

These signatures would be “tagged” as follows: 15 I.e.,

there may be symbols p such that there is no symbol p such that p = h(p ).

3.4. VIEWS AND RENAMING

39

#

inherits #

à ¡

M3 "

¡

¡

¡

¡

¡

à M4

¡¡µ "

!

#

Ã

inherits -

inherits

M2

!

"

#

!

-

à M1

"

!

Figure 3.12: Module graph for Example 3.8

Σ M4 = {[e, 0] M4 , [f, 0] M4 , [c, 0] M4 } Ψ M4 = {[e, 0] M4 , [c, 0] M4 } Σ M3 = {[a, 0] M3 , [b, 0] M3 , [c, 0] M3 } Ψ M3 = {[a, 0] M3 , [c, 0] M3 } Σ M2 = {[a, 0] M2 , [d, 0] M2 } Ψ M2 = {[d, 0] M2 } Σ M1 = {[c, 0] M1 , [d, 0] M1 } Ψ M1 = {[c, 0] M1 } . Hence the result of the aggregation M1 + M4 would have H(M1 ∪ M4 ) = H(M1 ) ∪ H(M4 ) = {M2 , M3 } , Σ(M1 ∪ M4 ) = {[c, 0] M1 , [d, 0] M1 , [e, 0] M4 , [f, 0] M4 , [c, 0] M4 } , Ψ(M1 ∪ M4 ) = {[c, 0] M1 , [e, 0] M4 , [c, 0] M4 } . Let us further assume that A1 = {c = d} , A2 = {a = 1} , A3 = ∅, A4 = {e = f } . Then A(M1 ∪ M4 ) = {c M1 = d M1 , a M2 = 1, e M4 = f M4 } . (Note that this example is about aliasing, since all the operations are constants.) Proposition: Aggregation is associative, commutative and idempotent, i.e., (Law 5) M + M = M + M , (Law 6) M + (M + M ) = (M + M ) + M , (Law 7) M + M = M , (Law 8) M ⊆ M + N ,

(Law 9) N ⊆ M implies M + N = M . Proof: These follow from the corresponding properties for unions of tuple sets. The idempotent (Law 7) law depends on the fact that the two modules not only have the same content, but also the same name. If Q(M ) = Q(M ) with M = M , then M + M = M does not hold. Now we can easily prove results such as: Proposition: Given specification modules M1 and M2 and a set H of module names, (Law 10) M H + M

H

= (M + M )H ,

(Law 11) M N1 ,N2 = M N2 ,N1 = M N1 +N2 , H

(Law 12) M N1 (Law 13) M

N

, N2H

H

= M (N1 +N2 )

+N =M

N

.

,

40

CHAPTER 3. MODULE COMPOSITION LANGUAGE SEMANTICS

Aggregation of Implementations In lileanna, where diana trees are manipulated to construct implementations, the aggregation C = C1 + C2 is built in several steps. First, a new (null) Ada package is created and called C. Then a copy of the diana tree for C1 is created (fully qualified to avoid potential name conflicts) and inserted into C. Then a fully qualified copy of the diana tree for C2 is created and inserted into C. The procedure of aggregating n implementation modules is similar.

3.5

Parameterization and Instantiation

One effective way to increase the reusability of software modules is to parameterize them. The form of parameterization described here is much more powerful than what is found in most programming languages today because, following the functional programming analogy given in Chapter 2, (multiple) other modules are allowed as parameters. Definition: A parameterized or generic specification module consists of a specification module M together with an injective specification module map P : T → M , where T is a theory. We may use the notation M [P :: T ], and say that M is parameterized by P , that T is the interface theory, and M is the body. Optionally, we may write M H [P :: T ] when H(M ) = H. The notation M [P :: T ] identifies the “formal parameter” P with the injection T → M ; we discuss how “actual parameters” are handled in the definition below.

generic package STACK[Item :: ANY TYPE] is type Stack; Stack_Empty : exception ; Stack_Overflow: exception ; function Is_Empty ( S: Stack ) return Boolean; function Is_Full ( S: Stack ) return Boolean; function Push ( E: Element; S: Stack ) return Stack; − − | where −−| Is_Full ( S ) => raise Stack_Overflow; function Pop(S: Stack) return Stack; − − | where −−| Is_Empty ( S ) => raise Stack_Empty; function Top ( S: Stack ) return Element; − − | where −−| Is_Empty ( S ) => raise Stack_Empty; − − |axiom −−| for all E: Element; S: Stack => −−| not Is_Full ( S ) − > −−| ( Pop ( Push ( E,S ) ) = S ) and; −−| ( Top ( Push ( E,S ) ) = E ); end STACK; Figure 3.13: lileanna generic specification for bounded stack Example 3.9: The interface theory of the generic STACK module in Figure 3.13 is ANY TYPE, which means that it is parameterized by a single type Element. Item :: ANY TYPE => STACK denotes the inclusion. Hence the declaration “type Element” is part of STACK implicitly through this inclusion.

3.5. PARAMETERIZATION AND INSTANTIATION

41

Note that this specification is incomplete, since it does not say when the exception Is Full is true, and hence the exception Stack Overflow is raised. A parameterized module can only realize its potential for reuse when its parameters are instantiated. “Fitting views” are used to apply generics to actual modules; they describe the binding of formal symbols in the signature of the interface theory to actual symbols in the signature of the actual module. In addition, they assert that the actual module satisfies the properties demanded by the interface theory. These properties are needed in order for the instantiation to behave as desired. (In Example 3.9, no such properties are required.) Definition: Given a generic specification module M [P :: T ] with body (H, Σ, Ψ, A) and given a view h : T → N , we let the instantiation of M by h be defined by the formula M [h :: N ] = M ∗ h♦ + N . The view h is called a fitting view. We may also use the notation M [h], or sometimes even M [N ] if h is clear from context. Note that H(M [h]) = H(M ) ∪ H(N ); this says that shared modules are not duplicated. We will see later that when a generic module is instantiated, it is added to the module graph with a link to the generic that it came from. Example 3.10: If we instantiate the generic STACK module shown in Figure 3.13, using the view Int View shown in Figure 3.14, which sends the type Element to the type Integer, then we get the module INTSTACK shown in Figure 3.6. Note that the type Integer is inherited as part of the module STANDARD, and hence is not explicitly declared.

view INT_View :: ANY_TYPE => STANDARD is types ( Element => Integer ); end INT_View ; Figure 3.14: View of Integer numbers

#

Ã

ST AN DARD " ! 6 view to get INTEGER # ANY_TYPE "

à parameterizes !

# -

à instantiation of via INT_View

ST ACK "

!

#

Ã

INT_STACK "

!

Figure 3.15: Module graph for Example 3.10

The power of using modules as parameters allows some perhaps surprising things to be done without great difficulty. For example, in an object-oriented institution (such as that provided for foops [GM87]), we can easily adapt the signature of an implementation to a new context, by parameterizing the module and instantiating it in a new way, noting that references through chains of object valued attributes can be schematic in foops. In [LX93], this kind of flexibility is called “adaptive software” and is supported by exotic language features; but it arises in a natural way in parameterized programming. Adaptivity is further supported by the ability of parameterize systems with interface subtheories which can express constraints on class structure and methods [Gog89].

42

CHAPTER 3. MODULE COMPOSITION LANGUAGE SEMANTICS

Parameterized Implementations Users of a module composition language are more likely to be interested in reusing code then in reusing specifications. However, it does not seem possible to define parameterized implementations in a way that is both general and direct, because the way that implementations are constructed from their parts varies from one institution to another. For example, in lileanna, we only have to substitute values for symbolic variables in diana code. But for equational logic, we have to do free extensions of algebras, which may involve generating an infinite number of new terms; operations are then defined syntactically on (equivalence classes of) these terms. The parameterization and instantiation capabilities of lileanna have clear advantages over those of Ada. In lileanna, one can use an entire Ada package as an actual parameter, instead of having to unpackage its contents. In addition, lileanna allows passing exceptions as parameters. Finally, because instantiation is done by making a copy of the generic module and renaming its formal parameters with the actual parameter values given by the view, the resulting instantiation generally has better performance than a comparable Ada instantiation. This is because no code sharing is attempted by the compiler and better code optimization takes place. (The results vary from compiler to compiler, but a 50% improvement in execution has been found on certain test cases.)

3.6

Module Expressions and Make

We now have a number of operations for modifying and combining modules. Clearly we can use them to form complex expressions, involving many modules and operations. We call such expressions module expressions, and we use them to describe systems and subsystems. But more than that, in parameterized programming, such expressions can be evaluated; when this happens, the (sub)system described by the expression in actually created. Note that evaluating a module expression involves evaluating all its subexpressions. This process agrees with our analogy with functional programming: module expressions are analogous to functional expressions that are combinations of previously defined functions. Of course, in this case we get a software system, instead of just a function, as the result of evaluating an expression; we also get a specification for the system. The lileanna syntax for module expressions is as follows: Mexp

:= | | | | | |

ModuleName ModuleName [ View ] Mexp + Mexp Mexp ∗ (rename MexpMap ) Mexp ∗ (hide Psig ) Mexp ∗ (add Psig , AxS ) Mexp ∗ (replace MexpMap )

where Mexp indicates module expressions, ModuleName indicates modules, MexpMap indicates a tuple set of pairs of elements, Psig indicates a partial signature, AxS indicates a set of axioms, Id indicates an identifier, and View indicates a view, which is defined as follows: View

ViewDef

:= ViewName | ViewDef | ViewName , View | ViewDef , View ::= view Id is MexpMap end Id

The last Mexp production rule requires some explanation, as it may be interpreted by some to violate the principles of parameterized programming. By providing the capability to “replace16 ” existing module elements with new 16 Here “replace” is more than a “hide” operation followed by an “add” operation. The replace operation results in the physical removal of the source code from the implementation and the addition of new statements. Consequently, it is recommended that the “replace” operation always be used in conjunction with a “rename” operation.

3.7. VERTICAL COMPOSITION

43

“implementations,” which this operation provides, the user is able to take advantage of lileanna’s module composition capability to “evolve” existing components and systems in a consistent and traceable manner. This approach has obvious benefits over using an editor to remove existing code and insert the new implementation. lileanna also allows the implicit instantiation of parameterized modules when default views are used (i.e., module expressions of the form ModuleName [ Mexp ]). Similarly, a tuple of views as an argument is regarded as abbreviating the aggregated view. The “make” command takes a new name and a module expression as arguments. The result of executing this command is to evaluate the module expression, for both the specification and the code, allocate storage for the resulting code (this is called “elaboration” in the jargon of Ada) and assign it the given name. Thus, executing a make yields executable code, attached to an appropriate specification. This code can then be used in building other systems by evaluating other module expressions that include the name of the newly made module. Assumption: Module expressions are evaluated bottom up, for both specification and implementation modules. Whenever a subexpression is evaluated, it is given a new name and added to the module graph. Whenever a generic module is instantiated, new links from the new node are added to the module graph, indicating the generic and the actual from which it was constructed. Thus module expression evaluation is eager not lazy; this is appropriate because the issues that lead to lazy evaluation in functional programming do not arise in module expression evaluation. This assumption concerns dynamic updating of the module graph, which thus serves as a database for a development project. The above assumption is stated in an informal way, unlike all our previous assumptions. It would be straightforward, but somewhat tedious, to formalize, using for example, an abstract machine for graphs. However, we have decided that given our goal of helping implementors, this would not be worth the trouble, and indeed, would make it harder for many readers to understand our intention. Summary As will be shown in the next chapter, module expressions can be used for: 1. instantiating a parameterized module using a view to create: (a) a new parameterized package/module, (b) a new unparameterized package/module, (c) implementations for types and operators in the module. 2. Composing/structuring new modules by: (a) combining given modules (taking account of multiple inheritance), e.g., merging two module’s operators and types, (b) adding something to a given module, e.g., an operator, (c) removing something from the interface of an existing module, e.g., hiding an operator, (d) renaming something, i.e., textually changing the names of operators and/or types in an interface, (e) selecting from a family of implementations through vertical composition (see the next section), (f) replacing something in an existing module, i.e., removing and then adding combination, e.g., a type, operator, exception or even an axiom.

3.7

Vertical Composition

Vertical composition allows designs to have multiple layers, where each layer provides services to higher layers. Furthermore, each layer may itself have a complex horizontal structure, involving the composition of multiple modules. The module system provided by parameterized programming is much more powerful than that of Ada, and in fact

44

CHAPTER 3. MODULE COMPOSITION LANGUAGE SEMANTICS

it provides all of the power of higher order programming without the need for higher order functions, as shown in [Gog90a]. Vertical structure concerns the structuring of a software system into a series of “layers,” where each layer calls upon resources provided by layers below it. That is, we are talking about providing “virtual machines” as resources for higher levels. Ada provides vertical composition support through its import clause (i.e., “with” statement) Due to Ada’s single package body per package specification restriction, one can not have multiple implementations associated with a single virtual machine. lileanna gets around this limitation by allowing a module’s vertical structure to be “parameterized” with explicit mapping of implementation occurring at make time.

3.7.1

Horizontal vs. Vertical Instantiation

Our rules for sharing under horizontal parameterization only allow including a module (and its state, if any) once, through a horizontal instantiation; after that, one must use inheritance to share it. By contrast, a module imported vertically is never shared . For example, if A(V :: T ) and B(V :: T ) both import a module M , then A(M ) + B(M ) has only one copy of M ; also, A(V :: T ) + P (V 1 :: B), where P is a module which imports T , will have two distinct copies of M . This gives a crisp methodological guideline for when to use each kind of structuring. The next chapter provides additional examples and details of LILEANNA syntax and module composition constructs. Appendix C further elaborates on the semantics described in this chapter.

Chapter 4

LILEANNA Language LILEANNA is a language that supports the formal specification and generation of Ada packages. This chapter merges the concepts of vertical, horizontal and generic instantiation with the module specification and composition constructs found in LILEANNA. LILEANNA extends Ada by introducing two modularization constructs: theories and views, and extending a third, package specifications. A LILEANNA package represents a design template for actual Ada package specifications (with its semantics specified either formally or informally). It is used as the common parent for families of implementations and for version control. A theory is a higher level abstraction than a package that describes a module’s syntactical and semantic interface. A view is a mapping between types, operations and exceptions. Programs can be structured/composed using two types of hierarchies: • vertical: to form levels of abstraction/stratification, and • horizontal: to form type hierarchies and generic modules via aggregation, inheritance, and parameterization. LILEANNA supports this with two language mechanisms: • needs: import dependencies, and • inherit, protect, or extend: three forms of inheritance. Finally, the language feature, module expression operators, provides a “super make”1 capability for the generation of new packages from existing packages through horizontal, vertical and generic instantiation. LILEANNA goes beyond the Ada instantiation capability in that generic packages can be composed to create new generic packages without themselves being instantiated. Partial instantiations are also possible. A view is used to instantiate a generic package. This chapter is divided into seven parts. The first section introduces LILEANNA language constructs through a small example. The second section discusses the lexical elements of the language. This is followed by a section that describes theories. The next section presents LILEANNA packages followed by a section on views. The sixth section explains the use of module expressions for the generation of new packages. Finally, the last section contains a series of small examples. The complete LILEANNA syntax is found in Appendix A. Note: “LA” is use as an abbreviation for “LILEANNA” throughout this chapter. 1 Make

is a UNIX term for the process of linking compiled outputs to make an executable module.

45

46

4.1

CHAPTER 4. LILEANNA LANGUAGE

LILEANNA Example

The following small example illustrates several LILEANNA language constructs. It makes a new package Integer_Set from a parameterized LILEANNA package LIL_Set. This example is very similar to the instantiation of an Ada generic package except that in Ada, the instantiation process is done at compile time. In LILEANNA, the generic instantiation is done prior to compile time. The result is Ada source code, ready to be compiled, composed or further instantiated. make Integer_Set is LIL_Set[Integer_View] end; Attention should be paid to the view (shown below), Integer_View (from theory Triv to the Ada package Standard (Appendix C, ALRM)), used in the make statement above. There is an explicit mapping between the type Element and the type Integer. Section 4.3, figure 4.4 explains the theory Triv in more detail. The point to be emphasized is that this mapping can be given a name and reused in other instantiations. view Integer_View :: Triv => Standard is types (Element => Integer); end; Alternatively, as shown below, the instantiation could have been expressed with an in-line view (to be discussed in section 4.5): make Integer_Set is LIL_Set[ view Triv => Standard is types (Element => Integer); ] end; In this case, the view does not have a name, but the mapping is explicit to this particular instantiation. make Short_Stack is LIL_Stack −− inherit Stack Package (horizontal composition) needs (List_Theory => List_Array) −− supply array package (vertical composition) end; Figure 4.1: Make (-ing) a Short Stack Example Figure 4.1 contains an example that illustrates the use of horizontal and vertical composition. (This is actually part of a larger example found in section 4.6.) A generic package (Short_Stack) is generated by selecting an array implementation (List_Array) of the list interface theory (List_Theory) needed by the LA package (LIL_Stack).

4.2

Lexical Elements

The character set, identifiers and literals used in LILEANNA follow the same conventions as described in the Ada Language Reference Manual (ALRM) [Ada83]. Additional delimiters beyond those found in Ada are “[” and “]”, which are used to enclose LILEANNA parameters. Additional compound delimiters include: “::”, which is used in the definition of formal LILEANNA parameters, and “−>” (implies) and “” (if and only if), which are used in boolean expressions found in the formal specifications of semantics expressed in Anna [LvHKBO87]. Comments are standard Ada style: “−−” to end of line. Following Anna conventions, two stylized comments (“−−|” and “−−:”) are used to express package semantics and constraints.

4.2. LEXICAL ELEMENTS

4.2.1

47

Reserved Words

Table 4.1 contains identifiers, not found in Ada, that are reserved words in LILEANNA. add as assoc axiom comm exceptions extend hidden hide hide_exceptions hide_ops hide_types id inherit isin make needs ops rename replace protect theory to types view where

(from Anna)

(from Anna)

(from Anna)

(from Anna)

Table 4.1: LILEANNA Reserved Words

4.2.2

Method of Description and Syntax Notation

The grammar of LILEANNA is described using a modified PGEN style notation. PGEN is the Stanford Parser Generator[Cha85], written in Ada and used for language development research at Stanford. Terminal symbols are enclosed by single quotes. Non-terminal symbols begin with a letter. Where appropriate, the same non-terminals that appear in the ALRM are used. Reserved words are boldface lower case. Productions are of the form: non-terminal -> list of terminals or non-terminals Optional non-terminal expressions are enclosed by square brackets “[ ]”. Repeated items (zero or more times) are enclosed in braces “{ }”.

4.2.3

Predefined Language Environment

All LILEANNA entities have implicit visibility to the predefined types BOOLEAN, INTEGER, FLOAT, CHARACTER, ASCII, and STRING; the subtypes NATURAL and POSITIVE; and respective operations declared in the Ada package STANDARD (see ALRM Appendix C).

48

CHAPTER 4. LILEANNA LANGUAGE

4.3

Theories

Theories are syntactic and semantic specifications of abstract objects. They define operations and their parameter types as well as exceptions that may be raised. Theories provide the system designer with the concepts that serve as vehicles for specifying the structure and interrelationship of other abstract modules in forming a software system. These units of modularization also provide the system designer with a vocabulary to compose new systems by manipulating a parameterized set of clearly defined semantic and syntactic concepts. There are two uses for theories in LIL: 1. interface theories and 2. requirement theories. Interface theories (or concepts/entities in a domain ontology) are higher-level abstractions than LA or Ada packages. They form a unit of modularization that may be manipulated much the same as a more concrete package, even though they have no implementations per say. It is often the case that a LA packages or one or more Ada packages exist that comply with the syntax and semantics of a theory. An interface theory can be viewed as a contract between the package that subscribes to its services and the package that provides the implementation context. In the process of top-down system design, interface theories provide a mechanism for creating a vertical hierarchy or stratification via the needs clause (discussed in section 4.4.2). Each theory presents an abstract machine interface whose operations can be used for implementations of higher-level abstract modules. Requirement theories describe the relationship (i.e., syntax and semantics) between parameters in a parameterized or generic theory or package. In principle, Ada generic formal parameters may be viewed as forming the requirement theory for a generic unit — though they are not bundled, as is the case for a requirement theory. Theories may be parameterized and inherit other theories that can be used to define operations or relationships between types. Certain constraints can be placed on what can be imported to and exported from a theory. Similarly, special attributes can be attached to the types and operations associated with a theory. These constraints and attributes are covered in sections 4.3.1 and 4.3.2 respectively. The semantics of theories and packages may be formally described using the axiomatic and annotative capability of Anna. An overview of the syntax of constraint specifications is found in section 4.3.4. The syntax for a theory definition is shown in figure 4.2. The definitions for “import_clause” and “export_clause” are found in sections 4.3.2 and 4.3.3 respectively. Finally, the body of a theory may consist of type, type inclusion, subprogram specification, and exception statements as well as formal and informal axioms. The LILEANNA type statement complies with the format of an Ada incomplete_type_spec (ALRM 3.8.1). An incomplete_type_spec offers the highest type abstraction in that, unlike private types, which eventually become declared in the private part of an Ada package specification, incomplete types provide only minimal information (the type name). Subprogram_specification and exception_declaration statement syntax are found in ALRM sections 6.1 and 11.1 respectively. The generic theory parameter list has a slightly different syntax than found in the formal_part of an Ada subprogram specification (ALRM 6.1). The compound delimiter “::” is used to separate the formal parameters from the parameter types, which in this case may be either: • a theory module expression (see section 4.6.2) including the instantiation of a generic theory, or • a requirement theory (the most common). The format of a parameter list is shown in figure 4.3. Figure 4.4 is an example of a trivial but common theory for a single type that is passed as a parameter to a generic module. There are no formal axioms to be satisfied, therefore any type can satisfy this requirement theory.

4.3. THEORIES

49

theory -> ’theory’ theory_name ’is’ [import_export_list] theory_body ’end’ [identifier] ’;’ -> ’generic’ ’theory’ theory_name ’[’ parm_list ’]’ ’is’ [import_export_list] theory_body ’end’ [identifier] ’;’ theory_name -> identifier import_export_list -> import_clause -> export_clause -> import_clause import_export_list -> export_clause import_export_list Figure 4.2: Syntax of Theory Definition

parm_list -> formal_name ’::’ module_expression { ;’ parm_list} formal_name -> identifier Figure 4.3: Syntax of Parameter List

theory Triv is type Element; −− This theory provides a type (Element) with no constraints −− on the operations associated with it. end Triv; Figure 4.4: Theory of Trivial Type

50

CHAPTER 4. LILEANNA LANGUAGE

type Short; type Long; function ”+”(X: Short; Y: Long) return Long (comm); is equivalent to function ”+”(X: Short; Y: Long ) return Long; function ”+”(X: Long; Y: Short) return Long; Figure 4.5: Subprogram Attribute Example

4.3.1

Attributes

As a way of simplifying interface specifications in LA, attributes may be assigned to subprograms. They are useful in defining a symmetrical relationships between parameters in subprogram declarations2 . LA subprogram attributes are most often used with infix binary operations. Attributes, such as associative, commutative, identity value, and scope restriction, appear after a subprogram specification. For example, figure 4.5 shows a single subprogram declaration that eliminates the necessity of having to overload “+” twice: Operations may be declared “hidden” (similar to “opaque” in Modula-2) if their visibility is to be restricted when the module they are in is inherited. This attribute is an alternate form of the hide_ops statement described in the section 4.3.3. Attributes (associative, commutative, identity value, and scope restriction) appear after a subprogram specification shown in figure 4.6. The definition for “expression”, is found in section 4.4 of the ALRM. The definitions for “designator”, and “formal_part” are found in ALRM section 6.1. The definition of “type_mark” is found in section 4.3.4 of this document. subprogram_specification -> ’procedure’ identifier formal_part [attributes] ’;’ -> ’function’ designator formal_part ’return’ type_mark [attributes] ’;’ attributes -> ’(’ attribute_list ’)’ attribute_list -> [’assoc’] [’comm’] [’id’ ’:’expression] [’hidden’] Figure 4.6: Syntax of Subprogram Attributes Note: The current implementation of LA ignores the “assoc” and “id” attributes along with using mixfix notation for operation specification.

4.3.2

Import Mechanism: Horizontal Structure Definition

Horizontal structuring through inheritance is an important paradigm for expressing the similarities and differences between modules. In Ada, the with clause imports scope/context to a package (similar to the LA needs clause 2 Goguen’s LIL [Gog84] proposed using attributes to formally express properties that may be useful in parsing the mixfix notation found in the re-write rules that could be used to express a module’s semantics.

4.3. THEORIES

51

discussed in section 4.4.2). Any Ada package that imports another package does not gain scope into the packages that the latter package imports. In LA, the various forms of the import clause result in inheriting the entire interface of the imported module; that is, any module that imports it will be unable to distinguish whether the operations are inherited or explicitly specified in that module. The three forms of the import clause are: 1. inherit — is the same as the with clause in Ada except the results are transitive. No commitments to modifying the semantics of the operations or types are made. 2. extend — implies that no additional operations will be added to the imported type(s) that might corrupt the meaning of objects declared of the imported type, but operations involving the type may take place. 3. protect — is the most restrictive. It implies that no operations will be defined in the context of the types declared in the imported package. The format for the import clause is shown in figure 4.7. The definition for “module_expression” is found in section 4.6.4. import_clause -> ’inherit’ module_expression_list ’;’ -> ’protect’ module_expression_list ’;’ -> ’extend’ module_expression_list ’;’ module_expression_list -> module_expression {’,’ module_expression} Figure 4.7: Syntax of Import Clause Having several import clauses in a module implies multiple inheritance. Because of ambiguities that may arise in the resulting multiple paths in an inheritance hierarchy, operations and types may need to be renamed or qualified. LA detects these ambiguities and automatically qualifies them.

4.3.3

Export Mechanism

The union of operations, types, and exceptions in packages that occurs as a result of inheritance can be restricted in LA through the hide_ops, hide_types, and hide_exceptions statements. These, in essence, make the operations, types, and exceptions internal to the module (or private) and not visible to other modules that may import the module. The format for the export statements is found in figure 4.8. The definition for “identifier_list” is found in section 3.2 of the ALRM. The definition for “subprogram_specification” is found in section 6.1 of the ALRM and section 4.3.1 of this document.

4.3.4

Formal Semantics

LA relies on the expressive power of Anna for specifying the formal semantics of modules. While the initially proposed version of LIL [Gog84] relied on axiomatic rewrite rules for expressing semantics, three additional constructs have been added in LA: 1. Type annotations are used to specify constraints on types (see Anna LRM 3.3). 2. Exception/Propagation annotations are used in subprogram annotations to identify the circumstances under which exceptions are raised or not raised (see Anna LRM 11.3 and 11.4). 3. Subprogram annotations are used to state pre-, post-, and error conditions (see Anna LRM 6).

52

CHAPTER 4. LILEANNA LANGUAGE

export_clause -> {hide_types_clause} {hide_ops_clause} {hide_exceptions_clause} hide_types_clause -> ’hide_types’ identifier_list ’;’ hide_ops_clause -> ’hide_ops’ identifier_list ’;’ -> ’hide-ops’ subprogram_specification_list ’;’ hide_exceptions_clause -> ’hide_exceptions’ identifier_list ’;’ subprogram_specification_list -> subprogram_specification {’,’ subprogram_specification} Figure 4.8: Syntax of Export Clause Axioms Axioms may be formal or informal. They describe the semantics of an operation and its relationships with other operations. The format of the axiom statement is shown in figure 4.9. Definitions for “identifier_list”, “subtype_indication” and “range” are found in sections 3.2, 3.3.2 and 3.5 of the ALRM respectively. The definition of “condition” is found in section 5.3 of the ALRM. axiomatic_annotation -> ’axiom’ [quantifier domain {’;’ domain} ’=>’ ] [condition] {’,’ condition} ’;’ quantifier -> [’not’] ’for all’ -> [’not’] ’exist’ domain -> identifier_list ’:’ subtype_indication -> identifier_list ’:’ range Figure 4.9: Syntax of Axiom Statement More information on axiomatic annotations can be found in section 7.8 of the Anna LRM. Type Annotations Type annotations are used to specify constraints on values of objects of this type. The syntax of a type annotation is shown in figure 4.10. The definition of “condition” is found in section 5.3 of the ALRM. type_annotation -> ’where’ [’in out’] identifier ’:’ type_mark ’=>’ condition ’;’ type_mark -> identifier Figure 4.10: Syntax of Type Annotation As an example, consider the formal specification of type Prime shown in figure 4.11. Type annotations must appear directly following the type definition they apply to. More information on type annotations may be found in Chapter 3 of the Anna LRM.

4.3. THEORIES

53

type Prime; − − |where P:Prime => − − | (not exist X,Y:Natural => X>1 and Y>1 and X*Y = P); Figure 4.11: Type Annotation Example Exception/Propagation Annotations Exception annotations are used in subprogram annotations and package axioms to describe the conditions under which an exception is raised (a strong_propagation annotation), or to describe the results of raising an annotation (weak_propagation annotation). The syntax of an exception (propagation) annotation is shown in figure 4.12. The definition of “condition” is found in section 5.3 the ALRM. exception_annotation -> strong_propagation_annotation -> weak_propagation_annotation strong_propagation_annotation -> condition ’=>’ ’raise’ exception_name weak_propagation_annotation -> ’raise’ exception_choice { ’|’ exception_name} [’=>’ condition] exception_choice -> exception_name -> ’others’ exception_name -> identifier Figure 4.12: Syntax of Exception/Propagation Annotation Figure 4.13 shows two examples are taken from a larger example found in section 4.7.1.. They specify when to raise the Stack_Underflow exception and what happens when a Stack_Overflow exception is raised. − − | (Pop(X) and Is_Empty(X)) => raise Stack_Underflow; −− strong propagation annotation − − | raise Stack_Overflow => S[in Stack] = S[out Stack]; −− weak propagation annotation Figure 4.13: Example of Exception/Propagation Annotation More information on propagation annotations may be found in Chapter 11 of the Anna LRM. Subprogram Annotations Subprogram annotations describe the functional semantics of a subprogram. They appear directly after the subprogram_specification. The result_annotation is used for functions. The object_annotation is used to describe the relationship between input and output parameters in procedures. The syntax of subprogram annotations are shown in figure 4.14. The definition of “boolean_primary” is found in section 4.1 of the ALRM. A “boolean_compound_expression” is an Anna compound_expression (Anna LRM 4.4) that resolves to a Boolean value.

54

CHAPTER 4. LILEANNA LANGUAGE

subprogram_annotation -> ’where’ basic_annotation_list basic_annotation_list -> basic_annotation {’,’ basic_annotation} basic_annotation -> object_annotation — Anna LRM 3.2 -> result_annotation — Anna LRM 6.5 -> propagation_annotation object_annotation -> condition -> ’out’ boolean_primary result_annotation -> ’return’ [identifier ’:’ type_mark ’=>’] compound_expression Figure 4.14: Syntax of Subprogram Annotation The subprogram annotation shown in figure 4.15 defines the semantics of the Is_Sorted function. function Is_Sorted (A : An_Array) return Boolean; − − | where return (for all I : Natural range −−| An_Array’First .. An_Array’Last -1 => −−| A(I) package_stmt package_body package_stmt -> incomplete_type_spec −− A.8.1 ALRM -> subprogram_specification −− 6.1 ALRM -> exception_declaration −− 11.1 ALRM -> axiom_annotation −− 7.8 Anna LRM -> package_expression Figure 4.20: Syntax of LILEANNA Package Body

4.4. LILEANNA PACKAGES

4.4.1

57

Programming by Difference

When using inheritance as a mechanism to define new interfaces, it is often the case that a small change needs to be made to the interface or to the implementation that the parent module exports. To support the same-as-except programming paradigm, LA offers a language construct to rename operations, types and exceptions, as well as to replace the implementation of an operation3 through module expression operators (section 4.6.4). These two mapping constructs are similar to the semantics of views (see section 4.5).

4.4.2

Listing a Package’s Needs

One of the most expressive structuring and composition features in LA is the ability to state explicitly the semantic and syntactic contextual requirements of a package implementation. During (top-down or object-oriented) program design, the needs clause provides a mechanism for specifying implementation dependencies in the form of the types, exceptions, and operations necessary for proper construction thereby facilitating vertical (hierarchical) composition or stratification. A needs clause identifies the interface theory that must be satisfied by an imported package at “make” time (see section 4.6). The needs_list identifies implementation dependencies. Figure 4.21 shows the format of the needs statement. The definition of “parm_list” is found in section 4.3.

needs_list -> ’(’ parameter_name ’::’ identifier [needs_parms] ’)’ {’,’ needs_list} needs_parms -> ’[’ parm_list ’]’ parameter_name -> identifier Figure 4.21: Syntax of Needs Statement The following example indicates that a generic linked-list package (ListP) must be supplied that is a version of the LA package List. needs (ListP :: List[ X :: Triv ]) In order to instantiate the package that contains a needs clause, a mapping between the formal parameter (ListP) and a linked list implementation (List_Array) must be specified. needs (ListP => List_Array)

4.4.3

LA Package Examples

The first example, shown in figure 4.22, is a parameterized linked-list package with formal semantic specification. The requirement theory Triv (presented in section 4.3) is indicated as containing the semantic and syntactic requirements of the parameters, which in this case consists of a single type. The second example (see figure 4.23) is a generic sorting package that needs the generic list package and a view (shown in the following section) of the requirements theory POSet (see section 4.3.5, figure 4.17), which itself requires a view (Triv) in order to be instantiated. 3 Types,

exceptions, and operations may be added explicitly to a module’s interface.

58

CHAPTER 4. LILEANNA LANGUAGE

generic package List[ Item :: Triv] is type List; function Join (X,Y: List) return List (assoc, id: null); function Is_Empty (X: List) return Boolean; function Head (X: List) return Element; function Tail (X: List) return List; − − | axiom −−| for all E: Element, L: List => −−| Head(Join (E, L)) = E, −−| Tail(Join (E, L)) = L; end List; Figure 4.22: List LA Package Example

generic package Sort[X :: POSet] needs (ListP :: List[X]) is function Sort(X: List) return List; function Is_Sorted(X: List) return Boolean; − − | axiom −−| for all X: List => −−| Is_Sorted(Sort(X) => TRUE; −− Sort returns a permuted version of the input list where −− items are arranged to the linear order specified in the −− theory POSet. end Sort; Figure 4.23: Sort LA Package Example

4.5. VIEWS

4.5

59

Views

As can be observed from the examples in the previous two sections, there is a strong similarity between theories and LA packages. In particular, the Sort theory (see section 4.3.5, figure 4.18) and the Sort LA package (see section 4.4.3, figure 4.23) are almost identical. In general, a theory is more abstract than a LA package. Theories have no vertical composition dependencies. Mappings exist between theories and LA packages as well as between LA packages and Ada package specifications. Unlike the one-to-one mapping between an Ada package specification and an Ada body, which is based on matching subprogram and type signatures4 , a more powerful mapping mechanism — the view — exists for LA modules. A view describes a binding of types, operations, and exceptions in a theory to those found in another theory or LA (or Ada) package. Views can also express bindings between LA packages and Ada package specifications. Views may be parameterized or non-parameterized. A view body may consist of simple one-to-one, identifier-to-identifier mappings for types, exceptions and operations. A more powerful form of mapping, an identifier to a subprogram expression, highlights the rapid prototyping capability of the language. Operations may map into an expression (in the case of a function). The purpose of a view is to establish how the semantics of one entity are satisfied by another. Views can be defined stand-alone, or in-line as part of the make statement to be covered in section 4.6. A view is necessary to define the mapping between formal and actual parameters in a requirement theory. Views are used to instantiate parameterized modules. The format of a view definition is shown if figure 4.24. The definition for “module_expression” can be found in section 4.6.2 . view -> ’view’ view_name ’::’ module_expression ’=>’ module_expression ’is’ −− package or theory view_body view_body ’end’ [identifier] ’;’ -> ’generic’ ’view’ view_name ’::’ module_expression ’[’ parm_list ’]’ ’=>’ module_expression ’is’ view_body ’end’ [identifier] ’;’ view_name -> identifier Figure 4.24: Syntax of View Definition The following example defines a view Descending between requirement theory POSet (section 4.3.5, figure 4.17) and package Standard (Appendix C, ALRM) for Integers: view Descending :: POSet => Standard is types ( Element => Integer ); Ops ( ” ”>=” ); end This view may be used to instantiate the LA Sort package (see section 4.4.3) to create a function that sorts Integers in descending order (see examples in section 4.6). More examples of views are found at the end of this section. A view body is made up of up to three different types of mappings: 1. type to type, 4 As

mentioned earlier, Ada unbundles generic parameters, while LA provides a modularization mechanism.

60

CHAPTER 4. LILEANNA LANGUAGE

2. exception to exception, and 3. operation to operation (subprogram to subprogram) or operation to subprogram expression. Not all mappings need to be explicit. Two special cases exist: default views and deferred views. As discussed in section 4.5.1, default views may be assumed when obvious mappings exist between modules. A deferred views described in section 4.5.2, is a partial view. Some of the mappings can be intentionally left unbound (using “*”) in order to create a partial instantiation of a parameterized module. LA module expressions may be used to specify (alternative) implementations of operations through expressions and statements consisting of existing operations. These expressions may not only contain references to operations and objects defined within the scope of the package, but may require the definition of local variables5 . The format for the type, operation and exception mappings is shown in figure 4.25. The definitions for “type_mark” and “module_expression” are found in sections 4.3.4 and 4.6.4 respectively. The definition for “subprogram_specification”, “expression” and “statement” are found in sections 6.1, 4.4, and 5.1 of the ALRM.

view_body -> [type_map] [object_declaration] [ops_map] [exception_map] type_map -> ’types’ name_map ’;’ exception_map -> ’exceptions’ name_map ’;’ name_map -> ’(’ formal_parm ’=>’ actual_parm ’)’ -> ’(’ formal_parm ’=>’ actual_parm ’)’ name_map formal_parm -> identifier actual_parm -> identifier -> ’*’ −− deferred view ops_map -> ’ops’ op_map ’;’ op_map -> ’(’ operator_name ’=>’ subprogram_expression ’)’ -> ’(’ operator_name ’=>’ subprogram_expression ’)’ op_map subprogram_expression -> operator_name -> operator_name ’(’ subprogram_expression ’)’ operator_name -> identifier — same signature -> literal -> ’*’ −− valid as deferred operator only

Figure 4.25: Syntax of Type, Operation and Exception Mappings Note: When there is only one type, operation, or exception in each module, or they are the same name, then an explicit mapping does not need to be specified (default view). 5 This

capability is not supported by the current implementation of LILEANNA.

4.5. VIEWS

4.5.1

61

Default Views

Full views do not always need to be explicitly supplied during instantiation. As stated in the previous section, when type, exception, or operation signatures match lexigraphically, then explicit mappings may be omitted in a view. For example, the theory Triv, as described in section 4.3, is automatically assumed to be the default requirement theory to serve as a basis of a view for modules that require actual package parameters of a single type.

4.5.2

Deferred Views

A mechanism needs to be available for specifying partial mappings in order to generate partial instantiations of modules. The “*” symbol can be used to indicate explicitly that a binding is being deferred. A deferred viewed may be used in a module expression only to generate a partially instantiated, parameterized module.

4.5.3

Operational Semantics of Views

There are several uses for a view: 1. to establish a mapping between formal parameters in a requirement theory and actual parameters derived from a module expression so that a LA or Ada package can be instantiated; 2. to informally specify the design of an operation by stating the relationship between existing entities; and, 3. to specify an implementation, using the rapid prototyping capability of LA through module expressions. When a view is used to instantiate a parameterized module, the parameters are substituted for, in the Ada sense. Temporary subprogram units are generated for subprogram expressions if necessary. When a view is used to map an implementation into a theory or LA package, or when the resulting package or theory is used in a module expression in a make statement (see section 4.6), the operation is re-packaged to form the body of a compilable Ada package.

4.5.4

Sample Views

Figure 4.26 is an example of a parameterized view. The requirement theory Triv is used to select the type of elements in the list. generic view ListM :: Monoid => List[ X :: Triv ] is ops ( ”*” => Dot ) ( I => NIL ); end ListM; Figure 4.26: Parameterized View Example The last example in this section, figure 4.27, depicts a view of the abstract data type Bag as a Set. In this example Set and Bag are interface theories or LA packages. To make a set out of a bag, the Add operation needs to be re-implemented. The Set package that would be generated from an instantiation through the make statement using this view would inherit all the operations in the Bag package, with the Add operation being replaced.

62

CHAPTER 4. LILEANNA LANGUAGE

view Bag_2_Set :: Set => Bag *(replace ( Add => Add ) −− Where the new implementation might be as follows: −−begin −− if Bag.Is_In( X, S ) then −− return S; −− else −− return Bag.Add ( X; S ); −− end if; −− end; is types ( Set -> Bag ); ops ( Add -> AddX ); end Bag_2_Set; Figure 4.27: View of Bag as a Set

4.6

Module Composition and Generation

The power of parameterized programming term “hyperprogramming” has been suggested by Goguen as applicable for this paradigm because of the leverage on gets in productivity through the generation capability of the language. The author has suggested using the term “programming-with-the-large” to emphasize the granularity of the objects being manipulated. Recently, Barry Boehm [BS92] has coined the term “megaprogramming” to refer construction software one in LA centers on the ability to compose new packages with module and subprogram expressions via the make statement. Existing packages may be manipulated through module expressions to specify the instantiation, aggregation, renaming, addition, elimination, and/or replacement of operations, types, or exceptions. Horizontal structuring can be combined to satisfy vertical structuring requirements by use of a needs clause. LA supports the structuring and composition of software modules from existing modules, theories and views as follows: 1. Instantiating a parameterized module using a view (or module expression) to create: (a) implementations of operations (through subprogram expressions), (b) a simple package/module, or (c) a parameterized package/module (generic). 2. Composing/structuring modules by: (a) combining other modules (inheritance and multiple inheritance) (e.g., merge two module’s operations and types), (b) adding something to an existing (inherited or instantiated) module (e.g., add an operation), (c) removing something from the interface of an existing module (e.g., hide an operation), (d) renaming something (e.g., purely textual changing the name of operation in an interface), (e) selecting from a family of implementations (e.g., vertical composition), or (f) replacing something in an existing module, i.e., a pure swap — a remove and add combination (e.g., on-the-fly vertical composition = replace an operation). (Where “something” is a type operation, exception or in some cases an axiom.) The result of evaluating a LA module composition statement (i.e., make statement) is an executable Ada package specification and body that either is

4.6. MODULE COMPOSITION AND GENERATION

63

1. a “stand-alone” flat module (nothing imported), or 2. a hierarchy, with selected functionality imported and perhaps re-packaged (i.e., the structure is retained). Note that since there is no inheritance in Ada, composition that uses inheritance will need to either import all modules in the inheritance hierarchy (being careful to rename those that might result in ambiguity), or include all necessary functionality directly in the implementation (package body). In either case, the resulting user interface (package specification) should not be cluttered by such details. This section describes how the horizontal and vertical composition facilities in LA may be used to generate new Ada packages. First, the syntax and semantics of the make statement are presented. Then, vertical composition through the needs statement is explained followed by horizontal composition with module expressions. Finally, numerous small examples are presented. Additional examples are found in the chapter that follows.

4.6.1

Make Statement

The LA language construct for generating and instantiating a new module (Ada package) is the make statement. The make statement provides three composition mechanisms: 1. instantiation of generic modules, 2. horizontal composition/instantiation through inheritance and module expressions, and 3. vertical composition/instantiation through context definition. The format of the make statement is shown in figure 4.28. make_statement -> ’make’ package_name [ ’[’ parm_list ’]’ ] ’is’ module_expression −− horizontal composition [needs_clause] −− vertical composition ’end’ [identifier] ’;’ package_name -> identifier Figure 4.28: Syntax of Make Statement

4.6.2

Instantiation in the Make Statement

LA goes beyond the Ada instantiation capability in that generic packages can be composed to create new generic packages without themselves being instantiated. Partial instantiations are also possible, when given a deferred view (see section 4.5.2). In most instances, a view is used to instantiate a generic package, but default views can be computed if only a package name is supplied. Alternatively, mappings of formal to actual parameters may form an in-line view as part of a module expression. The first example instantiates a set of integers: make Integer_Set is LIL_Set[Integer_View] end; where

64

CHAPTER 4. LILEANNA LANGUAGE

view Integer_View :: Triv => Standard is types (Element => Integer); end; Alternatively, the instantiation could have been expressed with an in-line view as shown in figure 4.29. This form of module expression will be explained in more detail in section 4.6.2.

make Integer_Set is LIL_Set[ view Triv => Standard is types (Element => Integer); ] end; Figure 4.29: In-Line View Example

4.6.3

Vertical Composition in the Make Statement

The needs clause may be viewed as another form of parameterization. It allows families of implementations sharing the same interface (or versions of the same implementation) to be selected (at make time) for integration into a new application. The needs clause is used during package composition to indicate how an Ada package can meet the needs of a LA Package (see section 4.4.2). The format for a needs clause is shown in figure 4.30. The needs_map, as shown if figure 4.31, binds an Ada package to a LA interface theory.

needs_clause -> ’needs’ needs_map Figure 4.30: Syntax of Vertical Instantiation Statement

needs_map -> ’(’ identifier ’=>’ identifier ’)’ -> ’(’ identifier ’=>’ identifier ’)’ needs_map Figure 4.31: Syntax of Needs Map Statement Figure 4.32 shows an example of how a make statement instantiates the generic LA package Sort according to the view Nat_Default, which mapped the Natural numbers and the pre-defined linear order relationship onto the theory of partially ordered sets (POSet). Using the example presented in section 4.1, in order to instantiate the package that contains a needs clause, a mapping between the formal parameter (List_Theory) and a linked-list implementation (List_Array) must be specified. needs (ListP => List_Array)

Figure 4.33 shows an example of a make statement instantiates the generic LA package Sort (see section 4.4.3) according to the view Nat_Default (section 4.5.4), which mapped the Natural numbers and the pre-defined linear order relationship onto the theory of partially ordered sets (POSet), (see section 4.3.5).

4.6. MODULE COMPOSITION AND GENERATION

65

make Sort_Lists_of_Naturals is Sort[Nat_Default] needs (ListP => Linked_List) end; Figure 4.32: Make (-ing) Sorted List of Naturals Example

make Sort_Lists_of_Naturals is Sort[Nat_Default] needs (ListP => Linked_List) end; Figure 4.33: Make (-ing) Sorted List of Naturals Example

4.6.4

Module Expressions

Module expressions manipulate Ada packages and their contents based on their relationships to LA packages, theories and views. The basic operations of importation in the form of inheritance, specialization in the form of instantiation, generalization, and aggregation are supported. The simplest form of module expression is just the name of a module to be imported or instantiated. In the formal definition shown in figure 4.34, the first two forms of modules expression deal with inheritance and multiple inheritance. Since instantiation requires a view, the next two forms of module expressions support predefined views and in-line views respectively. Finally the last format for module expressions allows the contents of modules to be manipulated through module operators to indicate what entities are being added, hidden, renamed, or replaced. The format for a module expression is shown in figure 4.34. The definition for “identifier_list” is found in section 3.2 of the ALRM. The definitions for “view_name”, “name_map”, and “op_map” are found in section 4.5. When new operations are added, or operations are replaced as a result of a module operator, if the implementation is not provided, then by convention, the LA processor will look for an implementation in the module with the same name as the first qualifier on the operation name (e.g., the operation for New_OPS.Add will be found in package New_OPS). If no qualifier exists, then the default package, MAKELIL is assumed.

4.6.5

Where to use a Module Expression

Module expressions may be appear in the following context: • • • •

as to as as

4.6.6

an actual parameter in a parameterized theory, form an instantiation in an inherit clause of a theory or LIL module (module_expression_list), the target of a view (to create the theory or package that is being mapped onto) and, part of a make statement.

Examples of Module Composition

In the following vertical composition example, the LA package Sort was previously defined (section 4.4.3) to need an Ada package that fulfills the interface theory ListP. List_Hack is supplied. make Sort_Nat is Sort[NATD] needs (ListP => List_Hack) end;

66

CHAPTER 4. LILEANNA LANGUAGE

module_expression -> module_or_theory_name -> module_expression ’+’ module_expression -> module_expression ’[’ view_name ’]’ -> module_expression ’[’ in_line_view ’]’ -> module_expression module_operator_list package_or_theory_name -> identifier in_line_view -> ’view’ module_expression ’=>’ module_expression ’is’ view_body module_operator_list -> module_operator -> module_operator_list module_operator module_operator -> ’*’ ’(’ ’rename’ name_map ’)’ -> ’*’ ’(’ ’add’ package_element ’)’ -> ’*’ ’(’ ’hide’ package_element ’)’ -> ’*’ ’(’ ’hide’ identifier_list ’)’ -> ’*’ ’(’ ’replace’ name_map ’)’ -> ’*’ ’(’ ’replace’ op_map ’)’ package_element -> literal -> identifier -> subprogram_specification Figure 4.34: Syntax of Module Expressions

4.6. MODULE COMPOSITION AND GENERATION

67

The next example shows three ways of turning the abstract data type bag into a set. A set is a bag with the Add operation replaced. This relationship can be stated and exploited three ways. Figure 4.35 shows condensed LA packages for Set and Bag. A view (see figure 4.36) could be defined (the same as in section 4.5.4) to express the relationship between the two LA packages. Alternatively, a LA package Bag_as_Set_Package could have been defined to show the same relationship (see figure 4.37). There are three ways to generate the implementation of a Set package: 1. inherit Bag and explicitly apply the rename and replace module operators, and 2. use Bag_as_Set_View as a module operator that contains the mappings. 3. inherit the explicitly modified Bag_as_Set_Package. Figure 4.38 show these three methods. In all cases, the same results will be generated (see figure 4.39). generic package Set[Item :: Triv] is type Set; function Add(E: Element; S: Set) return Set; function Remove(E: Element; S: Set) return Set; function Is_In( E: Element; S: Set) return Boolean; end; generic package Bag[Item :: Triv] is type Bag; function Add(E: Element; S: Bag) return Bag; function Remove(E: Element; S: Bag) return Bag; function Is_In( E: Element; S: Bag) return Boolean; end; Figure 4.35: LA Generic Set and Bag Packages Example

view Bag_2_Set :: Set => Bag *(replace ( Add => Add ) −− Where the replacement operation might be implemented as −− begin −− if Bag.Is_In( X, S ) then −− return S; −− else −− return Bag.Add ( X; S ); −− end if; −− end; ) is types ( Set -> Bag ); ops ( Add -> AddX ); end Bag_2_Set; Figure 4.36: Bag as Set View Example

68

CHAPTER 4. LILEANNA LANGUAGE

generic package Bag_as_Set_Package[Item :: Triv] is inherit Bag*(rename Bag => Set) *(replace Add => Add ) −− where the function could be implemented as follows: −−function Add( X: Element; S : Set) return Set is −− begin −− if Bag.Is_In( X, S ) then −− return S; −− else −− return Bag.Add ( X, S ); −− end if; −− end; ) end Bag_as_Set_Package; Figure 4.37: Generic LA Bag as Set Package Example −− 1) Explicit use of module operators make Set[Item :: Triv] is Bag*(rename Bag => Set) *(replace ( Add => Add) −− Where the new add checks before adding elements ) end Set; −− 2) Implicit use of module operators through a view make Set[Item::Triv] is Bag*[Bag_as_Set_View] end; −− 3) Importation of modified package with implicit mappings make Set[Item::Triv] is Bag_as_Set_Package end; Figure 4.38: Make (-ing) a Set from a Bag Examples

with Bag; use Bag; generic type Element is private; package Set is type Set private; function Add(E: Element; S: Set) return Set; function Remove(E: Element; S: Set) return Set; function Is_In( E: Element; S: Set) return Boolean; private package Bag_as_Set is new Bag(Element); type Set is new Bag; end; Figure 4.39: Generated Set Package Example

4.6. MODULE COMPOSITION AND GENERATION

69

The last example in this section converts a deque into a stack. A deque is a double ended stack. Figure 4.40 shows condensed theories for Stack and Deque. A view (see figure 4.41) Deque_as_Stack_View, could be defined express the relationship between the two theories. Alternatively, a LA package Deque_as_Stack_Package could be defined to show the same relationship (see figure 4.42). As in the previous example, there exists three ways to make a Stack from a Deque, given the modules that are defined, assuming an Ada Deque package existed. Figure 4.43 show these three methods. In all cases, the same results will be generated (see figure 4.44).

generic theory Stack[Item :: Triv] is type Stack; function Push(E: Element; S: Stack) return Stack; function Pop(S: Stack) return Stack; function Top(S: Stack) return Element; end; generic theory Deque[Item :: Triv] is type Deque; function Push_Top(E: Element; D: Deque) return Deque; function Push_Bot(E: Element; D: Deque) return Deque; function Pop_Top(D: Deque) return Deque; function Pop_Bot(D: Deque) return Deque; function Top(D: Deque) return Element; function Bot(D: Deque) return Element; end; Figure 4.40: Deque and Stack Theory Examples

view Deque_as_Stack_View :: Stack => Deque is types ( Stack => Deque); ops ( Push => Push_Top ) ( Pop => Pop_Top ) ( Top => Top ); end Deque_as_Stack_View; Figure 4.41: Deque as Stack View Example

generic package Deque_as_Stack_Package[Item :: Triv] is inherit Deque[Item :: Triv]*(rename Deque => Stack) *(rename (Pop_Top => Pop)) *(rename (Push_Top => Push)) *(hide Push_Bot, Pop_Bot, Bot ) end Deque_as_Stack_Package; Figure 4.42: LA Deque as Stack Package Example

70

CHAPTER 4. LILEANNA LANGUAGE

make Stack[Item :: Triv] is Deque[Item :: Triv]*(rename (Deque => Stack) ) *(rename (Pop_Top => Pop)) *(rename (Push_Top => Push)) *(hide Push_Bot, Pop_Bot, Bot ) end Stack; make Stack[Item::Triv] is Deque*[Deque_as_Stack_View] *(hide Push_Bot, Pop_Bot, Bot ) end; make Stack[Item::Triv] is Deque_as_Stack_Package[Item::Triv] end; Figure 4.43: Make (-ing) a Stack Example

with Deque; use Deque; generic type Element is private; package Stack is type Stack private; function Push(E: Element; S: Stack) return Stack; function Pop(S: Stack) return Stack; function Top(S: Stack) return Element; private package Deque_as_Stack is new Deque(Element); type Stack is new Deque; end; Figure 4.44: Generated Stack Package Example

4.7. EXAMPLES

4.7

71

Examples

The remaining portion of this chapter contains small examples that illustrate the functional capabilities of LILEANNA.

4.7.1

Horizontal and Vertical Instantiation of a Stack

The small example in Figure 4.45 illustrates the use of horizontal and vertical composition. The example generates a generic package (Short_Stack) by selecting an array implementation (List_Array) of a list interface theory (List_Theory) for a LILEANNA package (LIL_Stack). It is assumed that the LILEANNA package (LIL_Stack) has a comparable Ada package (Stack) and that an explicit view may or may not exist between them. Here, LIL_Stack, List_Theory, and List_Array are defined as shown in Figure 4.46, and LIL_Stack, List_Theory, and List_Array are defined in Figure 4.47. In this example a default view exists between the LIL_Stack and Ada Stack packages. The generated result is shown if Figure 4.50. Note that the Join operation is overloaded in the List_Array package in order to fulfill the interface requirements indicated by the assoc attribute, and the type inclusion statement found in List_Theory. make Short_Stack is LIL_Stack −− inherit Stack Package (horizontal composition) needs (List_Theory => List_Array) −− supply array package (vertical composition) end; Figure 4.45: Make (-ing) a Short Stack Example

generic theory List_Theory[ Item :: Triv] is −− LILEANNA Theory type List; includes Element as List; function Join (X,Y: List) return List (assoc, id: null); function Is_Empty (X: List) return Boolean; function Head (X: List) return Element; function Tail (X: List) return List; − − |axiom − − | for all E: Element, L: List => − − | Head(Join (E, L)) = E, − − | Tail(Join (E, L)) = L; end List_Theory; Figure 4.46: Make (-ing) a Short Stack Example

4.7.2

SAM from Radar

The example6 in Figure 4.51 demonstrates how the operation Initialize is redefined but not replaced by first hiding it from being re-exported, then adding a new implementation to the interface. Figure 4.52 shows the interface for the Radar LILEANNA package. Figure 4.53 shows the resulting Ada package. 6 This

example is based on an example found in [Inc88]

72

CHAPTER 4. LILEANNA LANGUAGE

generic package LIL_Stack[ Item :: Triv ] −− LILEANNA Package needs (ListP :: List_Theory[ Item ]) is type Stack; includes Element as Stack; function Is_Empty (S: Stack) return Boolean; function Push (E: Element; S: Stack) return Stack (comm); function Pop (S: Stack) return Stack; function Top (S: Stack) return Element; − − |axiom − − | for all E: Element, S: Stack => − − | Pop(Push(E,S)) = E; − − | (Pop(X) and Is_Empty(X)) => raise Stack_Underflow; −− strong propagation annotation − − | raise Stack_Overflow => S[in Stack] = S[out Stack]; −− weak propagation annotation end LIL_Stack; Figure 4.47: LILEANNA Generic Stack Package Example

generic −− Ada Package type Element is private; package List_Array is type List is private; function Join (E: Element; L: List) return List; function Join (L: List; E: Element) return List; function Join (X,Y: List) return List; function Join (E,F: Element) return List; function Is_Empty (L: List) return Boolean; function Is_Empty (E: Element) return Boolean; function Head (L: List) return Element; function Head (E: Element) return Element; function Tail (L: List) return List; function Tail (E: Element) return List; private type List is array( 1 .. 100 ) of Element; end List_Array; Figure 4.48: List Ada Package Example

4.7. EXAMPLES

73

with List_Package; use List_Package; generic package −− Ada Package type Element is private; package Stack is type Stack is private; function Is_Empty (S: Stack) return Boolean; function Push (E: Element; S: Stack) return Stack; function Pop (S: Stack) return Stack; function Top (S: Stack) return Element; private package Stack_List is new List_Package(Element); type Stack is new Stack_List.List ; end Stack; Figure 4.49: Generic Ada Stack Package Example

with List_Array; use List_Array; generic package type Element is private; package Short_Stack is −− Ada Package type Stack; function Is_Empty (S: Stack) return Boolean; function Push (E: Element; S: Stack) return Stack; function Pop (S: Stack) return Stack; function Top (S: Stack) return Element; private package Stack_List is new List_Array( Element ); type Stack is new Stack_List.List ; end Short_Stack; package body Short_Stack is −− Ada Package function Is_Empty (S: Stack) return Boolean is ... function Push (E: Element; S: Stack) return Stack is ... end Short_Stack; Figure 4.50: Generated Stack Package Example

74

CHAPTER 4. LILEANNA LANGUAGE

make SAM_Site is Radar*(hide Initialize) *(add Initialize) *(add procedure Add Penetrator(A Penetrator: Object ID)) *(add procedure Remove Penetrator(A Penetrator: Object ID)) *(add procedure Expect(A Penetrator: Object ID)) needs (Tactical_Types => Stealth) end SAM_Site; Figure 4.51: Make (-ing) a SAM Radar Site Example

package Radar needs (Tactical_Types :: Tactical_Fighter) is procedure In Range(A Penetrator: Object ID); procedure Out of Range(A Penetrator: Object ID); procedure Transmit(Message : string; Destination: Object ID); procedure Create (New Radar : out Object ID); procedure Initialize; end Radar; Figure 4.52: Radar LILEANNA Package Example

with Stealth; use Stealth; package SAM_Site is −− Ada Package −− Inherited Procedures procedure In Range(A Penetrator: Object ID); procedure Out of Range(A Penetrator: Object ID); procedure Transmit(Message : string; Destination: Object ID); procedure Create (New Radar : out Object ID); −− New procedures procedure Add Penetrator(A Penetrator: Object ID); procedure Remove Penetrator(A Penetrator: Object ID); procedure Expect(A Penetrator: Object ID); procedure Initialize; end SAM_Site; Figure 4.53: Generated SAM Site Package Specification Example

4.7. EXAMPLES

4.7.3

75

Square from Rectangle

Figure 4.54 shows another example7 of how a module can be refined by replacing implementations that are more efficient. Also, notice that some additional axioms have been added to the new module. make Square is Rectangle*(rename Rectangle => Square *(replace ( Area => Area ) ) −− where the new function would be implemented as: −− function Area(S: Square) return Real is −− begin −− return Length(S)**2; −− end; *(replace ( Perimeter => Perimeter ) ) −− function Perimeter(S: Square) return Real is −− begin −− return Length(S)*4; −− end; ) − − | axiom −−| for all S : Square => −−| Length(S) = Width(S); end; Figure 4.54: Make (-ing) a Square from a Rectangle Example

7 This

example is based on an example found in [Mey88]

Chapter 5

Module Composition Examples This chapter contains two examples of how LILEANNA can be used to design, prototype and compose new software systems. The first example illustrates how a seemingly trivial application (a data base for ticket sales for a one-time theatrical performance), once properly structured using the capabilities in LILEANNA, can readily be modified, through package expressions to provide additional capabilities. The second example provides details how LILEANNA is being used to support the specification and manipulation of a Domain-Specific Software Architecture [GM92] (DSSA) on the Lockheed Martin Avionics Domain Application Generation Environment (ADAGE) Project [CST92]. This example is derived from an Ada program written by Lou Coglianese as part of the DSSA-ADAGE project and uses the domain model developed by Lou and Mark Goodwin at Lockheed Martin along with Don Batory at the University of Texas at Austin.

5.1

Theater Example

This section starts off with a brief description of the application’s requirements followed by an analysis of the application domain and the resulting high-level design specified using LILEANNA interface theories. This design represents the horizontal structure of the program. Next, the vertical structure of the application design is specified using LILEANNA packages that correspond to the interface theories. The remaining sections take the reader through a series of plausible change scenarios that illustrate how LILEANNA supports the incorporation of those changes and demonstrating the benefits of adherence to the design principles encouraged by LILEANNA.

5.1.1

Application Description/Requirements

The requirements for the example being explored in this section can be informally stated as follows: “You are in charge of finances for a play that is being performed by your community theatrical group. This is a one time shot, but you thought it would be nice to have a computer program to help the person taking phone and mail orders for tickets. The theater you are using has reserved seats (row, seat#). You are charging $10 for orchestra seats and $7 for seats in the balcony. You would like the program to tell you such things as how many tickets are sold, how many are left, and how much money has been taken it. To help the ticket agent, you also would like a display of the seating arrangement that shows which seats are sold and which are available.”

76

5.1. THEATER EXAMPLE

77

Object

Attributes

Operations

Seat

Name Status (e.g., sold/available)

Is Available Sell a Seat Return a Seat Initialize a Seat

Row

Name

Is Last Seat in Row Number of Available Seats in Row Get Next Seat Get First Seat List Available Seats in Row List Seats in Row Initialize a Row

Section

Name (e.g., orchestra/balcony) Price

Is Last Row in Section Get First Row Get Next Row List Rows in Section List Available Rows in Section Initialize a Section

Theater

Name Total Tickets Sold Total Tickets Unsold Total Sales

Is Last Section in Theater Get First Section Get Next Section List Sections Display Seating Arrangement Initialize a Theater

Table 5.1: List of Objects, Operations and Attributes

78

CHAPTER 5. MODULE COMPOSITION EXAMPLES

Analysis of Theater Problem Using an object-oriented approach to module decomposition and specification, the following objects can readily be identified: 1. 2. 3. 4.

Seat, Row (of seats), Section (of rows), and Theater (of sections).

The operations and attributes of these objects are found in the table 5.1. One could argue that “ticket”, “ticket agent”, “balcony”, “orchestra”, “money”, and “seating arrangement” are also objects. For all intensive purposes, a “seat” and a “ticket” are synonymous. “Ticket agent” is an implicit object in that the program will have an interactive interface. “Balcony” and “orchestra” are section types. Finally “seating arrangement” is synonymous to “section”.

5.1.2

Horizontal Structure for Theater Problem

This section contains the LILEANNA interface theory specifications for the objects described in table 5.1. Theory of Seat A “seat” is the lowest object being manipulated in this design in that it is atomic. A seat has a name and a status (e.g., available/sold). The reader should note that a seat is treated as a physical object in that it does not have a price, or performance associated with it. This is a debatable design tradeoff whose rationale will become evident in the choice of initialization strategies. Figure 5.1 shows the interface theory for seats. Theory of Row A “row” is a sequence of “seats”. Every seat in a row has a unique name1 . The parameterized interface theory in figure 5.2 shows how the interface theory Row is parameterized by the interface theory Seats (figure 5.1). Theory of Section A “section” is a sequence of “rows” (of “seats”). Sections have names and prices. Every row in a section has a unique name. Figure 5.3 shows the generic interface theory for Section. Theory of Theater A “theater” is a set of “sections” (of “rows” of “seats”). Ticket agents who work at theaters sell tickets for seats that are available. Theater managers like to know how many tickets are sold and how much revenue has been gathered. Figure 5.4 shows the generic interface theory for Theater.

1 One could have specified that a “row” was a set of “seats”, in which case the uniqueness of a seat name is guaranteed, but the concept of linear arrangement is missing, hence neither structure, on its own, provides the necessary abstraction.

5.1. THEATER EXAMPLE

79

Theory Seat is type Seat; -- has a name (position) and status (sold/available) -initial value is available. Illegal_Sell : Illegal_Return : Uninitialized_Seat :

exception; exception; exception;

function Is_Available ( S: Seat ) return Boolean; --| where raise Uninitialized_Seat => return false; function Seat_Name ( S: Seat ) return String; --| where raise Uninitialized_Seat => return "unavailable"; procedure Sell_a_Seat ( S: in out Seat ); --| where out not Is_Available (S), --| Is_Available( S ) => raise Illegal_Sell; procedure Return_a_Seat ( S: in out Seat ); --| where out Is_Available(S), --| raise exception Illegal_Return if Is_Available (S); procedure Initialize_a_Seat ( S: in out Seat ); --| where out Is_Available(S out); -- create an object of type seat and assign it a name -and make it available end Seat;

Figure 5.1: Seat Interface Theory

80

CHAPTER 5. MODULE COMPOSITION EXAMPLES

generic theory Row [S :: Seat] is type Row;

-- a sequence of seats with a name

No_More_Seats Duplicate_Seat Seat_not_in_Row Uninitialized_Row

: : : :

exception; exception; exception; exception;

function is_Last_Seat_in_Row ( R: in Row; S: in Seat ) return Boolean; -- where raise No_More_Seats is null row;? function Row_Name ( R: Row) return String; -- where raise Uninitialized_Row; function Number_of_available_Seats_in_Row ( R: Row ) return Natural; procedure Get_First_Seat ( R: in Row; S: out Seat ); -- raise No_More_Seats is null row; procedure Get_Next_Seat ( R: in Row; Current_Seat: in Seat; Next_Seat: out Seat ); --| where is_Last_Seat_In_Row (R, Current_Seat) => raise No_More_Seats procedure List_Seats_in_Row ( R: Row ); procedure List_available_Seats_in_Row ( R: Row ); procedure Initialize_a_Row ( R: in out Row ); -- create an object of type row and assign it a name -- create a sequence of seats and initialize them with unique names -- raise Duplicate Seat if try to put in same seat twice? end Row;

Figure 5.2: Row Parameterized Interface Theory

5.1. THEATER EXAMPLE

81

generic theory Section [ R :: Row ] is type Currency; -- could be dollars, pounds, francs, etc. type Section; -- a sequence of rows with a name -- has a cost of type currency. Uninitialized_Section: exception; No_More_Rows : exception; Duplicate_Row : exception; function Is_Last_Row_in_Section ( S: in Section; R: in Row ) return Boolean; -- raise No_More_Rows is null row;? function Section_Name ( S: Section) return String; -- raise Uninitialized_Section function Price_of ( S: Section) return Currency; procedure Get_First_Row ( S: in Section; R: out Row ); -- raise No_More_Rows is null row; procedure Get_Next_Row ( S: in Section; Current_Row: in Row; Next_Row: out Row ); --| where is_Last_Row_in_Section(S, Current_Row) => raise No_More_Rows; procedure List_Rows_in_Section ( S: Section ); procedure List_Available_Rows_in_Section ( S: Section ); procedure Initialize_a_Section ( S: in out Section ); -- create an object of type section and assign it a name -- create a sequence of rows and initialize them with unique names. end Section;

Figure 5.3: Section Parameterized Interface Theory

82

CHAPTER 5. MODULE COMPOSITION EXAMPLES

generic theory Theater [ S :: Section ] is type Theater; -- a set of sections of rows of seats type Currency; No_More_Sections : Duplicate_Section :

exception; exception;

function Total_Tickets_Sold (T : Theater ) return Natural; function Total_Tickets_Unsold (T : Theater ) return Natural; function Total_Sales (T : Theater ) return Currency; function Theater_Name ( T: Theater ) return String; function Is_Last_Section_in_Theater ( T : Theater; S in Section; ) return Boolean; -- raise No_More_Sections is null section:? procedure Get_First_Section (T: Theater; S: out Section); -- raise No_More_Sections is null Section? procedure Get_Next_Section ( T: Theater; Current_Section: in Section; Next_Section: out Section); -- raise No_More_Section if Current_Section is Last procedure List_Sections (T: Theater ); procedure Display_Seating_Arrangement (T: Theater); procedure Initialize_a_Theater (T: in out Theater); -- create an object of type Theater -- create a set of sections and initialize them with unique names. end Theater;

Figure 5.4: Theater Parameterized Interface Theory

5.1. THEATER EXAMPLE

5.1.3

83

Vertical Structure for Theater Problem

This section contains the LILEANNA package specifications for the objects described in table 5.1, plus additional requirements theory specifications used by the LILEANNA packages. The reader should note that a family of Ada implementations exists for these LILEANNA packages, especially for the requirement theories of List and Set. LILEANNA Package for Seat The LILEANNA “Seat” package shown in figure 5.5 has the same interface as the theory for Seat shown in figure 5.1. package Seat is type Seat; -- has a name (position) and status (sold/available) Illegal_Sell : Illegal_Return : Uninitialized_Seat :

exception; exception; exception;

function Is_Available ( S: Seat ) return Boolean; --| where raise Uninitialized_Seat => return false; function Seat_Name ( S: Seat ) return String; --| where raise Uninitialized_Seat => return "unavailable"; procedure Sell_a_Seat ( S: in out Seat ); --| where out not Is_Available (S), --| in Is_Available( S ) => raise Illegal_Sell; procedure Return_a_Seat ( S: in out Seat ); --| where out Is_Available(S) --| in Is_Available (S) => raise Illegal_Return; procedure Initialize_a_Seat ( S: in out Seat ); --| where out Is_Available(S out); -- create an object of type seat and assign it a name -and make it available end Seat;

Figure 5.5: Seat LILEANNA Package

LILEANNA Package for Row The generic LILEANNA “Row” package shown is figure 5.6 is similar to the theory for Row shown in figure 5.2 except that it uses (“needs”) the requirement theory List Theory shown in figure 4.46, which itself is parameterized by the requirement theory Triv (see figure 4.4). LILEANNA Package for Section The generic LILEANNA “Section” package shown is figure 5.7 is similar to the theory for Section shown in figure 5.3 except, like the LILEANNA Row package, it uses (“needs”) the requirement theory List Theory shown in figure 4.46, which itself is parameterized by the requirement theory Triv (see figure 4.4).

84

CHAPTER 5. MODULE COMPOSITION EXAMPLES

generic package Row [S :: Seat] needs ( SequenceP :: List_Theory [ Item :: Triv ] ) is type Row;

-- a sequence of seats with a name

No_More_Seats Duplicate_Seat Seat_not_in_Row Uninitialized_Row

: : : :

exception; exception; exception; exception;

function is_Last_Seat_in_Row ( R: in Row; S: in Seat ) return Boolean; -- raise No_More_Seats is null row;? function Row_Name ( R: Row) return String; -- raise Uninitialized_Row function Number_of_available_Seats_in_Row ( R: Row ) return Natural; procedure Get_First_Seat ( R: in Row; S: out Seat ); -- raise No_More_Seats is null row; procedure Get_Next_Seat ( R: in Row; Current_Seat: in Seat; Next_Seat: out Seat ); -- raise No_More_Seats if Current_Seat is Last procedure List_Seats_in_Row ( R: Row ); procedure List_available_Seats_in_Row ( R: Row ); procedure Initialize_a_Row ( R: in out Row ); -- create an object of type row and assign it a name -- create a sequence of seats and initialize them with unique names -- raise Duplicate Seat if try to put in same seat twice? -- raise Duplicate Seat if try to put in same seat twice? end Row;

Figure 5.6: Row LILEANNA Generic Package

5.1. THEATER EXAMPLE

85

generic package Section [ R :: Row ] needs ( SequenceP :: List_Theory [ Item :: Triv ] ) is type Currency; -- could be dollars, pounds, francs, etc. type Section; -- a sequence of rows with a name -- has a cost of type currency. Uninitialized_Section: exception; No_More_Rows : exception; Duplicate_Row : exception; function Is_Last_Row_in_Section ( S: in Section; R: in Row ) return Boolean; -- raise No_More_Rows is null row;? function Section_Name ( S: Section) return String; -- raise Uninitialized_Section function Price_of ( S: Section) return Currency; procedure Get_First_Row ( S: in Section; R: out Row ); -- raise No_More_Rows is null row; procedure Get_Next_Row ( S: in Section; Current_Row: in Row; Next_Row: out Row ); --| where is_Last_Row_in_Section(S, Current_Row) => raise No_More_Rows; procedure List_Rows_in_Section ( S: Section ); procedure List_Available_Rows_in_Section ( S: Section ); procedure Initialize_a_Section ( S: in out Section ); -- create an object of type section and assign it a name -- create a sequence of rows and initialize them with unique names. end Section;

Figure 5.7: Section LILEANNA Generic Package

86

CHAPTER 5. MODULE COMPOSITION EXAMPLES

LILEANNA Package for Theater The generic LILEANNA “Theater” package shown is figure 5.8 is similar to the theory for Theater shown in figure 5.4 except that it uses (“needs”) the requirement theory Set Theory shown in figure 5.9, which itself is parameterized by the requirement theory Triv (see figure 4.4). generic package Theater [ S :: Section ] needs ( SetP :: Set_Theory [ Item :: Triv ] ) is type Theater; -- a set of sections of rows of seats type Currency; No_More_Sections : exception; Duplicate_Section : exception; function Total_Tickets_Sold (T : Theater ) return Natural; function Total_Tickets_Unsold (T : Theater ) return Natural; function Total_Sales (T : Theater ) return Currency; function Theater_Name ( T: Theater ) return String; function Is_Last_Section_in_Theater ( T : Theater; S in Section; ) return Boolean; -- raise No_More_Sections is null section:? procedure Get_First_Section (T: Theater; S: out Section); -- raise No_More_Sections is null Section? procedure Get_Next_Section ( T: Theater; Current_Section: in Section; Next_Section: out Section); -- raise No_More_Section if Current_Section is Last procedure List_Sections (T: Theater ); procedure Display_Seating_Arrangement (T: Theater); procedure Initialize_a_Theater (T: in out Theater); -- create an object of type Theater -- create a set of sections and initialize them with unique names. end Theater;

Figure 5.8: Theater LILEANNA Generic Package

5.1.4

Making a Theater

Creating an instance of a theater is a two step process: 1. The horizontal structure needs to be instantiated by creating views to map requirement theories to Ada packages that satisfy them. 2. The vertical structure needs to be instantiated by specifying the desired Ada packages that satisfy the interface theories. (Note that a theory can be used both as an interface theory and a requirement theory.)

5.1. THEATER EXAMPLE

87

generic theory Set_Theory [ Item :: Triv ] is type Set; exception Item_Not_Found; function Is_in ( E: Element: S: Set ) return boolean; function Add ( E: Element; S: Set ) return Set; function Remove ( E: Element; S: Set ) return Set; --| not Is_In( E, S) => raise Item_Not_Found; function Size_of ( S: Set) return integer; -- see "Programming With Specifications" by David Luckham, -pages 306-310 for a formal specification in Anna. end Section;

Figure 5.9: Set Generic (Requirement) Theory

Theater ? Set

- Triv

-

Section ? List - Triv

-

Row

-

Seat

? List - Triv

Figure 5.10: Horizontal and Vertical Structure of Theater Example.

88

CHAPTER 5. MODULE COMPOSITION EXAMPLES

Figure 5.10 shows the horizontal and vertical structure of this design that needs to be instantiated. The sections that follow contain explicit views for the horizontal structure. The last example is the series of “make” statements that uses the views, combined with explicit mappings to Ada Packages that solve the vertical structure of the application. Views The first three views expressed in figure 5.11 reflect simple direct mappings to Ada implementations of the same name with suffix “ A”. The last three views represent “trivial” type mappings that the LILEANNA Translator could accept as default mappings, but are included in this example for illustrative purposes. view Section_View :: Section => Section_A is end; view Row_View :: Row => Row_A is end; view Seat_View :: Seat => Seat_A is end; view Seat_Type :: Triv => Seat_A types ( Element => Seat ) end;

is

view Row_Type :: Triv types ( Element => Row end;

is

=> Row_A )

view Set_Type :: Triv => Section_A is types ( Element => Section ) end;

Figure 5.11: Mappings (Views) for Instantiating a Theater

Make Statement Figure 5.12 reveals how the vertical and horizontal structure of the Theater example can be satisfied. The horizontal structure is indicated by the nesting of interface theories being satisfied by views. The associated “needs” clauses show the vertical structure of the individual packages being provided for. make My_Theater is Theater[ Section_View [ Row_View [ Seat_View] needs ( Sequence_P => Linked_List[ Seat_Type ] ) ] needs ( Sequence_P => Linked_List [ Row_Type ] ) ] needs ( SetP => Small_Set [ Section_Type ] ) end;

Figure 5.12: Creating an Instance of a Theater Via a Make Statement

5.1.5

Change #1

The demand for tickets is so great that you have to add a matinee.

5.1. THEATER EXAMPLE

89

Analysis of Change #1 One flaw in the initial design was the omission of time (and date) of performance. Since the initial requirements were for a one-time show, there was no need for such details. The Theater interface theory can serve as a basis for creating a new LILEANNA package called Performance. A Performance has a name (matinee or evening). One sells tickets to a Performance the same as one sold Theater tickets. Figure 5.13 shows how the LILEANNA Theater packages can be made into LILEANNA Performance package using package expressions.

make Performance_P [S ::Section] is Theater_P *(replace (Theater => Performance) ) *(replace (Initialize_Theater => Initialize_Performance ( P: in Performance ) ) *(add function Performance_Name ( P: Performance ) return String) end;

Figure 5.13: Making Performance Parameterized Interface Theory A similar transformation can be applied to the Ada packages for Theater to derive new packages. In addition to transforming Theater into Performance, a new module must be defined — Show. The interface theory for “Show” is shown in figure 5.14.

generic theory Show [ P :: Performance ] is type Show; -- a set of performances exception No_More_Performances; exception Duplicate_Performance; function Total_Tickets_Sold ( S: Show ) return integer; function Total_Tickets_Unsold ( S: Show) return integer; function Total_Sales (S: Show)return currency; function Number_of_Performance_in_Show ( S: in Show P: Performance) return integer; procedure List_Performances_in_Show ( S: Show ); procedure Initialize_Show; -- create an object of type Show which is a set of Performances. -- create a set of performances and initialize them with unique names. end Show;

Figure 5.14: Show Parameterized Interface Theory Finally, figure 5.15 shows how an instance of the ticket selling program can be generated, using an “inline” view.

90

CHAPTER 5. MODULE COMPOSITION EXAMPLES

make My_Show_C1 is Show[ view Performance => Performance_A [ Section_View [ Row_View [ Seat_View ] needs (Sequence_P => Linked_List[ Elt :: Seat_Type ] ) ] needs ( Sequence_P => Linked_List [ Elt :: Row_Type ] ) ] needs ( SetP => Small_Set [Elt :: Section_Type ] ) ] needs ( SetP => Small_Set [Elt :: Performance_Type ] ) end;

Figure 5.15: Creating an Instance of a Show Via a Make Statement

5.1.6

Change #2

The demand for tickets is so great that you have to add several shows the following week. Analysis of Change #2 As with the last change, when the “time” was added to the type performance, now “day” has to be added and the initialization procedure replaced with one asks for “time” and “day” of performance. This can be done, by modifying the program generated by the make statement shown in figure 5.15 using the package expression shown in figure 5.16 make My_Show_C2 is My_Show_C1*(replace ( Performance => Performance_1 )) *(replace ( Initialize_Performance => Initialize_Performance_1)) end;

Figure 5.16: Using a Package Expression to Modify the Show Package

5.1.7

Change #3

You decide that you should charge $2.00 less for matinee tickets. Analysis of Change #3 This is a simple change in that only one procedure in one package needs to be modified. One should note that because LILEANNA works on the intermediate form of the Ada program, this change is extremely inexpensive and fast. Using straight Ada would require 6 instantiations and 5 recompilations. Using the current implementation of LILEANNA requires one recompilation. Furthermore, the resulting program is smaller and executes faster. make My_Show_C3 is My_Show_C2 *(replace ( Initialize_Section => Initialize_Section_1)) end;

Figure 5.17: Using a Package Expression to Change Cost of Shows

5.1. THEATER EXAMPLE

5.1.8

91

Change #4

The demand for tickets is so great that you have to add another phone line and ticket agent to handle reservations. Analysis of Change #4 This is somewhat easy and hard. It is easy because we can add a simple lock on rows so that before you can access them, you need write access (see figure 5.18). make Exclusive_Row is Row_P *(replace (Row => Locked_Row) *(add procedure Row_Lock (R : Locked_Row)) end;

Figure 5.18: Using a Package Expression to Modify the Row Package What is harder is making the program have two active users. It also points out the absence of certain state-data benchmarking and restoring operations that should have been included in the initial design.

5.1.9

Change #5

The success of your theatrical company is so great that you decide to do another show, this time in a larger theater. Analysis of Change #5 Depending on the implementation of Initialize Section and Initialize Row (hard-coded seating plan or interactive solicitation) either the two routines need to be replaced, or, in the case of the interactive configuration, nothing needs to be done. Figure 5.19 shows this modification for the hard-coded specification. make My_Show_C5 is My_Show_C3 *(replace ( Initialize_Section => Initialize_Section_1)) *(replace ( Initialize_Row => Initialize_Row_1)) end;

Figure 5.19: Using a Package Expression to Modify the Show Package

5.1.10

Change #6

One of the theaters wants to use your software to sell tickets to open seating concert that is being held in the theater. Analysis of Change #6 Two things need to happen in order to bring this change about: 1) all tickets have the same price and 2) the theater becomes one section. In addition, there are implications and optimizations that could be performed on all levels of the design since the concept of sequentiality does not apply to rows and seats.

92

CHAPTER 5. MODULE COMPOSITION EXAMPLES

make My_Show_C6 is My_Show_C3 *(replace ( Initialize_Section => Initialize_Section_2)) end;

Figure 5.20: Using a Package Expression to Support an Optimized Concert Ticket Sales Program

5.1.11

Change #7

A small commuter airline contacts you and asks if you could modify your program to support their ticket sales. Analysis of Change #7 The analogy between selling tickets to a show and for a flight is fairly strong. Table5.2 breaks down this analogy by the objects in each domain. One should note that the pricing arrangement may be an issue if the airline offers different price tickets in first class or coach sections (e.g., 30 day advanced purchase, or travel that includes a stay over Saturday). If this is the case Ticket prices could be calculated by a generic function instead of a being initialized by section. Figure 5.21 shows how the airline reservation program could be derived from the theater program. make Airline is My_Show_C3 *(replace ( Initialize_Section => Initialize_Section_2) *(rename ( Section => Ticket_Category) ( Performance => Flight )) end;

Figure 5.21: Using a Package Expression to Make an Airline Reservation System

Theater Domain

Airline Domain

Library Domain

Seat Row Section Performance Seating Arrangement Tickets Sold Ticket Remaining Price Performance time Performance date Ticket Agent

Seat Row Ticket Category Flight Number Seating Arrangement Tickets Sold Tickets Remaining Price Flight Departure Flight Date Ticket Agent

Book Shelf Section Title Floorplan Books on loan Books available Penalty for lateness n/a Due date Librarian

Inventory Generalization Item Room/Shelf/Bin Aisle or Building Description Warehouse Items sold current inventory Cost/Item n/a Expiration date? Clerk

Table 5.2: Comparison of Theater, Airline, Library, and Inventory Domains

5.1.12

Change #8

The public library contacts you and asks if your program could be used to track their books.

5.2. AVIONICS DOMAIN EXAMPLE

93

Analysis of Change #8 As shown in table 5.2, the book for ticket is also a reasonable analogy because all these applications fall into the general domain of “inventory management”, which is a classic data base problem.

5.2

Avionics Domain Example

One of the deliverables on the Domain-Specific Software Architecture Avionics Domain Application Generation Environment (DSSA ADAGE) project was a description of an avionics architecture written in an AKRL (Avionics Knowledge Representation Language). One of the many dimensions of this language is the ability of describe the interfaces and dependencies between the components that make up the architecture2 . The goal of this section is to: 1. introduce the reader to the avionics components that are the focus of the ADAGE project, 2. impress upon the reader the degree of variability and configurability faced by the avionics engineer in designing new avionics systems, and 3. clearly illustrate how LILEANNA, within the ADAGE environment, can be used to rapidly prototype new avionics systems for evaluation. The material in this section is organized as follows: • The first section presents a high-level overview of the software and system components that make up a typical military integrated avionics application. • Next, the horizontal and vertical ADAGE structure of avionics system components is described and the design space partially enumerated to give the reader a sense of the magnitude of the number of permutations of sensors and modes an avionics system can have. • Finally, the last section contains a sample avionics system configuration as reflected by LILEANNA package expressions, views and make statements. This configuration was created by using additional tools, GLUE (Graphical Layout User Environment) and MEGEN (Module Expression GENerator), independently developed under the authors direction as part of the ADAGE project to aid the avionics system designer in selecting and configuring the components for a target system.

5.2.1

ADAGE Avionics System Components

The portion of the avionics domain that the ADAGE Project is focusing on consists of the areas of: Flight Director, Guidance, and Navigation. This is a classic control loop design, very similar in principle to the cruise control on ones automobile, except the degree of sophistication, accuracy, and fault tolerance (not to mention cost) are significantly higher. The following explanation refers to components found in figure 5.22, found in the following section, to describe the relationships and processing requirements of a typical avionics system. The basic requirement for an integrated avionics system is for the pilot, or autopilot, to keep the plane on a preplanned flight path as detailed in a Mission Profile. To do so, the pilot (or autopilot) is constantly fed “cues” from the Flight Director. The Flight Director understands certain Control Laws about the flight dynamics of the aircraft and calculates a recommended course of action, taking into consideration the current aircraft position and state along with Atmospheric factors and information about the Earth’s geography. The Flight Director receives “error” signals from 2 This dimension has been referred to as the “Architecture Representation Language,” an extension to the “Module Interconnect Formalism” effort investigated by the DARPA ProtoTech Community.

94

CHAPTER 5. MODULE COMPOSITION EXAMPLES

Guidance, who maintains current aircraft state and position (i.e., the State Vector) and compares it with the estimated aircraft state and position given to it by Navigation. Navigation calculates estimated values for the aircraft’s position, velocity and attitude (with respect to one or more coordinate frames) from an assortment of Data Source Objects (DSOs) (sensors). Sensor data must be filtered, fused, averaged, and selected, based on various modes. The selection criteria is determined by the availability and accuracy of sensors, or can be directly specified by the pilot or Flight Director. It is important to point out that today’s modern military aircraft rely on a suite of different sensors, often in redundant configurations for reliability, combined using different filtering algorithms to determine the most accurate aircraft state, when operating in one of several possible modes.

5.2.2

Avionics System Horizontal and Vertical Structure

Figure 5.22: Horizontal and Vertical Structure of Typical Avionics System Figure 5.22 shows a horizontal and vertical structure of a simplified avionics system3 . There are several important points to realize about this diagram: 3 Certain

details of the system have been left out to make the example more manageable.

5.2. AVIONICS DOMAIN EXAMPLE

95

1. This is not a call tree. Even though the vertical structure does portray a client/server relationship. The actual implementations may take on an entirely different topology. In particular, the popular “time-line executive,” would, in essence, sequentially call each component, according to some priority order and periodicity. 2. The horizontal structure in this example takes the form of generic parameters consisting of: • State Vector, which may use different coordinate systems,

• DSO Select, which contains a list of the sensors in the current configuration, • DSO Select 2, which contains a list of aggregate navigation modes, • Filter, which depict different filter algorithms and constants, and

• Global Data.

3. The structure of the topmost three layers of most avionics systems is the same in the sense that the overall system structure and interfaces do not change from system to system, but different components (e.g., Atmospheric Model, Earth Model, or Mission Profile) may be swapped in. 4. The most dynamic layers in any avionics system are in the middle — Sensor Sampling and Sensor Fusion along with the DSO/sensor suite and Navigation Modes. They are dynamic in that they require the most configuration, tradeoffs, and experimentation to determine the most effective set of sensors, modes, filters and filter constants to meet mission specific requirements. 5. The “*” in the components Sensor Fusion and Sensor Sampling indicate that these are more than generic instantiations in LILEANNA. These Ada packages are actually composed and generated using the “make” statement and a form of inheritance. 6. The dashed vertical lines coming out the components Sensor Fusion and Sensor Sampling indicate that the components below have been composed with each module, rather than indicating vertical structure (implementation dependency), even though, in reality they are part of the implementation, they are not part of the design, since it cannot be predicted what ones will be needed to generate the system. An avionics system evolves through several iterations, moving from a prototype on the avionics engineer’s desk, to the flight simulation lab, and eventually to the field. Having a uniform system architecture and a family of implementation that can easily be configured from one operating environment to another is necessary in order to quickly validate new systems. Tables 5.3 and 5.4 present an abbreviated list of the families of components that the avionics engineer has to choose from in configuring a system to meet these environments. For example, an avionics application could be configured to consist of an Everest Earth model, for Fixed Wing Attack aircraft Control Laws, operating in Approach to Hover, Course to Point, Direct to Point, Expanding Square, Hover Hold, ILS, and Moving Point Guidance Modes, and AHRS (Air Heading Reference System), INS (Inertial Navigation System), and GPS/DNS (Global Positioning System/Doppler Navigation System) Navigation Modes, with a Sensor suite consisting of a ASN 145 AHRS Unit, APN 146 DNS Unit, ARN 151 GPS Box, ASN 90 INS Unit, and a HG 9050LO RADALT (Radar Altimeter)4 . The remaining portion of this section consists of the LILEANNA representations for simplified versions of the highlevel components found in figure 5.22. Flight Director LILEANNA Package generic package Flight_Director [ SV :: State_Vector ] needs ( Control_Laws :: Control_Laws [ SV :: State_Vector ] ), ( Guidance :: Guidance [ SV :: State_Vector ] ), ( AM :: Atmospheric_Model ), 4 The sensor names reflect the manufactures names and unit numbers (e.g., HG 9050 is a Honeywell Guidance Model 90 Low Resolution System

96

CHAPTER 5. MODULE COMPOSITION EXAMPLES

Earth Model Airy Bessel 1841 Clarke 1866 Clarke 1880 Everest Hough International WGS 1972 WGS 1994 ...

Control Laws Fixed Wing Transport Fixed Wing Fighter Rotary Wing Attack Rotary Wing Recon ...

Guidance Modes ADF Approach Approach to Hover Baro Alt Select Course to Point Creeping Line Direct to Point Expanding Square Go Around Heading Select Hover Hold ILS ILS Back Course Moving Point NRP PLS Radar Alt Select TACAN Terrain Following ...

Nav Modes ADDR AHRS DNS GPS INS MHS TRN AHRS DNS GPS DNS GPS INS INS DNS ...

Sensors ADC AHRS DMG DNS GPS INS MHS PITOT RADALT FLIR RADAR LFADF LORAN MLS Omega PLS TACAN UVADF VOR LOC ...

Table 5.3: Sample Design Space for Avionics System Components

AHRS A24G 1A AQU 4A ASN 145 Simulate

DNS APN 146 APN 175 APN 217 APM 218 APN 221B ASN 128 ASN 137 ASN 138 Simulate

FLIR AAD 7 AAQ 10 AAQ 15 AAQ 16 AAQ 17 AAQ 18 Simulate

GPS ARN 149v2 ARN 151 ASR 151 Simulate

INS ASN 141 ASN 143 ASN 143RLG ASN 90 LN 15J LTN 51 SKN 2443 Simulate

Table 5.4: Sample Design Space for DSOs (Sensors)

RADALT APN 133 HI APN 171 LO APN 194 LO APN 209 LO APN 22 LO APN 232 LO HG 9050 LO SCR 718 HI Simulate

5.2. AVIONICS DOMAIN EXAMPLE

( EM

:: Earth_Model )

is procedure Initialize; procedure Calculate_Cues; -- Based on Control Laws, calculate the path to get back on target procedure Signal_Autopilot; -- S.Coupled_Mode => true; procedure Run; -end Flight Director;

Control Laws LILEANNA Package generic package Control_Laws [ SV :: State_Vector ] needs ( DC :: Directional_Control_Law ), ( LAC :: Lateral_Control_Law ), ( LOC :: Longitudinal_Control_Law ), ( VC :: Vertical_Control_Law ), ( G :: Guidance [ SV :: State_Vector ] ) is function Command_Petal(E: Guidance.Error_Vector, S: SV.State_Vector) return C.Error; function Command_Roll(E: Guidance.Error_Vector, S: SV.State_Vector) return C.Error; function Command_Pitch(E:Guidance.Error_Vector, S: SV.State_Vector) return C.Error; function Command_Power(E: Guidance.Error_Vector, S: SV.State_Vector) return C.Error; end Control_Laws;

State Vector LILEANNA Package package State_Vector is type Position; type Velocity; type Acceleration; type Altitude; type Altitude_Rate; type State_Vector; -- The State Vector is implemented as a collection of records for -- each type listed above, all rolled together. end State_Vector;

Guidance LILEANNA Package generic package Guidance [ ST :: State_Vector ] needs ( MP :: Mission_Profile [ ST :: State_Vector ] ), ( Nav :: Navigation [ ST :: State_Vector ] ) is type Error_Vector is record Speed_Error : SV.type_x; Altitude_Error : SV.type_x; Heading_Error : SV.type_x;

97

98

CHAPTER 5. MODULE COMPOSITION EXAMPLES

end record; function Calculate_Error_Vector return Boolean; -- Use input from Navigation and current position from State_Vector to calculate Error_Vector. function Set_Mode ( M: SV.Mode ) return Boolean; -- if legal mode then -change to new mode unless illegal and return true -- else -don’t change mode and return false. function Arm_Mode ( M: SV.Mode, C: SV.Condition ) return Boolean; -- if legal mode and legal Condition then -set up triggers to change mode when condition is satisfied -and return true.-- else -- return false. function Current_Mode return SV.Mode; function Legal_Mode ( M: SV.Mode ) return Boolean; -- It is not legal to combine modes that overlap axis end Guidance;

Mission Profile LILEANNA Package generic package Mission_Profile [ ST:: State_Vector ] is type Mode; -- one of Combined, Lateral, Longitudinal, or Vertical_Axis Modes type Combined_Axis_Mode; -- e.g., type Combined_Axis_Mode is DHDV_Go_Around, -NRP_Approach, -... type Lateral_Axis_Mode; -- e.g., type Lateral_Axis_Mode is Creeping_Lines, -Heading, -... type Longitudinal_Axis_Mode; -- e.g. type Longitudinal_Axis_Mode is Air_Speed, -Ground_Speed, -... type Vertical_Axis_Mode; -- e.g. type Vertical_Axis_Mode is Barometric_Altitude, -Radar_Altitude, -... end Mission_Profile;

Navigation LILEANNA Package generic package Navigation [ ST :: State_Model ] needs ( Sample :: Sensor_Sampling [ ST :: State_Model ] ), ( Fusion :: Sensor_Fusion [ ST :: State_Model ] ) is function Calculate_State_Vector return SV.State_Vector -- State_Vector calculations are based on the availability of -- Data Sources end Navigation;

5.3. USING ADAGE AND LILEANNA TO CREATE AN AVIONICS SYSTEM

99

Data Sources LILEANNA Package generic package Data_Sources [ G :: Global_Data; DSP :: DSO_Select ] is type Data_Source; -- e.g. type Data_Source is GPS, Air_Data, -INU, TACAN, -Doppler, VOR, -AHRS, Altimeter, -TRN, -... type Quality_Type; -- e.g., type Quality_Type is Usable, Degraded, Unusable; type Selection_Criteria_Type; -- e.g., type Selection_Criteria_Type is External, Best_Available -one of the Data_Sources type Sensor_Data is record Quality : Quality_Type, Position : C.Position_Type, Velocity : C.Velocity_Type, Attitude : C.Attitude_Type end record; function Calculate_Sensor_Data ( D: DSO.Data_Source ) return Sensor_Data -- Samples data at a certain rate procedure Set_Selection_Criteria ( D: DSO.Data_Source, S: Selection_Criteria_Type ); procedure Initialize ( D: DSO.Data_Source ); procedure Alignment ( D: DSO.Data_Source ); procedure Tune ( D: DSO.Data_Source ); -- only for certain Data_Source Types (e.g., VOR) end Data_Sources;

DSO Select LILEANNA Package package DSO_Select is -- This data structure is built by the MEGEN type Data_Source; -- e.g. type Data_Source is GPS, Air_Data, -INU, TACAN, -Doppler, VOR, -AHRS, Altimeter, -TRN, -... end DSO_Select;

5.3

Using ADAGE and LILEANNA to Create an Avionics System

As one can see from table 5.3 and table 5.4 in the previous section, creating an avionics system requires the avionics engineer to select components from a series of lists, configuring them in various manners, then evaluating the results. In DSSA ADAGE, LILEANNA is being used as a module composition language, to combine and instantiate components resulting in a collection of Ada programs ready to compile and run. While the user could create LILEANNA views and write LILEANNA make statements and package expressions to generate the desired results,

100

CHAPTER 5. MODULE COMPOSITION EXAMPLES

in the ADAGE environment, they are assisted by an independently developed tool5 — GLUE (Graphical Layout User Environment), as shown in figure 5.23, presents the user with various options in the design space and MEGEN (Module Expression GENerator) automatically generates the LILEANNA that will be used to create the Ada packages that comprise the system.

Figure 5.23: LILEANNA Parameterized Programming Environment The GLUE is based on Batory’s concept of “Realms” [BO91, Bat93b] as demonstrated in the Genesis System [Bea86]. Realms have been defined for the avionics domain [SC93, Bat93a] and are fed into a decision support tool — GLUE. The output is a colored decision tree, “type expressions”, which is fed into MEGEN, which produces LILEANNA views, make statements and package expressions. Figure 5.24 shows the type expression output for a sample ADAGE program. There are four groupings: 1. DSO/Sensors, 2. Earth Geometry and Atmospheric Models, 3. Navigation Modes, and 4. Guidance. Each line in the type expressions as been numbered to facilitate explaining the mappings into LILEANNA. The reader should note that there is a prescribed order of instantiation of the system, as reflected by the order of type expressions in figure 5.24. Even though the user may not traverse the decision tree in this order the type expressions are output in a “bottom” up manner. That is, the components at the bottom of figure 5.22 are instantiated first, before the rest of the system is composed with other various options. 5 GLUE and its predecessor, the “Layout Editor” were jointly developed by Eric Newton, Keith Werkman, and Peter Angeline as part of the ADAGE effort.

5.3. USING ADAGE AND LILEANNA TO CREATE AN AVIONICS SYSTEM

smpADC smpDNS smpGPS smpINS

= = = =

ADC_simulate; DNS_simulate; GPS_simulate; INS_simulate;

smpEM smpEG smpAM

= = =

WGS_1984; -- Earth Geometry and Atmosphere Model Realms -- 5 Earth_Geom [ smpINav, { smpINS, smpGPS }, smpEM ]; -- 6 Atmosphere_Model [ smpADC ]; -- 7

smpINav

-- DSO Realm Definitions

101

-----

1 2 3 4

=

Static_Nav_Mode_Ranking -- Navigation and Radio Nav Realms [ { -- 8 ADDR_Nav_Mode [ smpAM ], -- 9 GPS_Nav_Mode [ smpGPS ], --10 INS_Nav_Mode [ smpINS ], --11 GPS_INS_Nav_Mode [ smpGPS, smpINS ], --12 INS_DNS_Nav_Mode [ smpINS, smpDNS, smpEG ] --13 } ]; smpNav_Data = Nav_Data_AM_EG [ smpINav, smpAM, smpEG ]; --14 smpNavigation = Nav [ smpNav_Data ]; --15 smpControl_Laws = Ctl_Laws_Rotary_Wing_Attack; -- Guidance Realm --16 smpGuide = Guid_Mode_Manager [ { Direct_To_Point [ smpNavigation ],--17 Moving_Point [ smpNavigation ] },--18 smpControl_Laws ]; --19 Figure 5.24: Annotated Type Expressions for Example Avionics System

102

5.3.1

CHAPTER 5. MODULE COMPOSITION EXAMPLES

LILEANNA from Type Expressions Example

The following sections contain LILEANNA make statements that generate a set of avionics system Ada packages. The instantiations take place bottom up, similar to the compilation order imposed by Ada compilers. Each make statements uses the LILEANNA packages found in section 5.2.2, which follow the horizontal and vertical structure shown in figure 5.22. In addition, each make statement has comments back to the type expressions listed in figure 5.24. Making ADC DSO make smpADC is Data_Sources [ view G => smp_Global_Data; -- inline view DSA => ADC_View ] -- *** TE 1 *** end;

Making DNS DSO make smpDNS is Data_Sources [ view G => smp_Global_Data; -- inline view DSA => DNS_View ] -- *** TE 2 *** end;

Making GPS DSO make smpGPS is Data_Sources [ view G => smp_Global_Data; -- inline view DSA => GPS_View ] -- *** TE 3 *** end;

Making INS DSO make smpINS is Data_Sources [ view G => smp_Global_Data; -- inline view DSA => INS_View ] -- *** TE 4 *** end;

Making ADDR Nav Mode make smpADDR_Nav_Mode is Nav_Mode [ view G => smp_Global_Data; -- inline view Filter => No_Filter_View; -- *** TE 9 *** DSO_Select => ADDR_View ] -- *** TE 9 *** end;

(default)

Making GPS Nav Mode make smpGPS_Nav_Mode is Nav_Mode [ view G => smp_Global_Data; -- inline view Filter => No_Filter_View; -- *** TE 10 *** DSO_Select => GPS_View ] -- *** TE 10 *** end;

(default)

5.3. USING ADAGE AND LILEANNA TO CREATE AN AVIONICS SYSTEM

Making INS Nav Mode make smpINS_Nav_Mode is Nav_Mode [ view G => smp_Global_Data; -- inline view Filter => No_Filter_View; -- *** TE 11 *** DSO_Select => INS_View ] -- *** TE 11 *** end;

(default)

Making GPS INS Nav Mode make smpGSP_INS_Nav_Mode is Nav_Mode2 [ view G => smp_Global_Data; -- inline view Filter => No_Filter_View; -- *** TE 12 *** DSO_Select2 => GPS_INS_View ] -- *** TE 12 *** end;

(default)

Making INS DNS Nav Mode make smpINS_DNS_Nav_Mode is Nav_Mode2 [ view G => smp_Global_Data; -- inline view Filter => No_Filter_View; -- *** TE 13 *** DSO_Select2 => INS_DNS_View ]-- *** TE 13 *** end;

(default)

Making Sample Sensor Sampling make smp_Sensor_Sampling is Sensor_Sampling [ smp_ST_View ] *(add smpADC) *(add smpDNS) *(add smpINS) *(add smpGPS) *(replace Sample) -- built by layout end;

-- *** -- *** -- *** -- *** editor

TE TE TE TE

1 2 3 4

*** *** *** ***

Making Sample Sensor Fusion make smp_Sensor_Fusion is Sensor_Fusion [ smp_ST_View ] *(add smpADDR_Nav_Mode) -- *** *(add smpGPS_Nav_Mode) -- *** *(add smpINS_Nav_Mode) -- *** *(add smpGPS_INS_Nav_Mode) -- *** *(add smpINS_DNS_Nav_Mode) -- *** *(replace Estimate) -- built by layout end;

TE 9 *** TE 10 *** TE 11 *** TE 12 *** TE 13 *** editor

Making Sample Navigation make smp_Navigation is Static_Navigation_Mode_Ranking [ smpST_View ] needs (Sample => smp_Sensor_Sampling) (Fusion => smp_Sensor_Fusion) end;

-- *** TE 8 ***

103

104

CHAPTER 5. MODULE COMPOSITION EXAMPLES

Making Sample Guidance make smp_Guidance is Guidance[ smpST_View ] *(add Direct_To_Point) *(add Moving_Point) *(replace Guid_Mode_Manager) needs (MP => smp_Mission_Profile) (Nav => smp_Navigation) end;

-----

*** TE 17 *** *** TE 17 *** built by layout editor *** Default ***

Making Sample Flight Director make smp_Flight_Director is Flight_Director [ ST :: ST_View] needs (Control_Laws => CTL_Laws_Laws_Rotary_Wing_Attack ) -- *** (Guidance => smp_Guidance) (AM => smpAM) -- *** TE 7 *** (EM => WGS_1984); -- *** TE 5 *** end;

5.3.2

TE 16 ***

Results

The current avionics “demo” application consists of 24 Ada packages and over 2000 source lines of code. LILEANNA is being used to generate only a small portion of the (around 250 lines) of a three sensor, 3 navigation mode application. (In particular, the Sensor Sampling, Sensor Fusion and Navigation packages are being generated.) Table 5.5 summarizes these results. Type Expression 1 2 3 4 5 6 7 8 14 15 16 17 Totals

# of Module Expressions 2 2 2 2 1 1 1 5 4 1 1 6 28

SLOCS Generated/Reused 6/25 6/25 6/25 6/25 N/A N/A N/A 62/134 N/A N/A N/A N/A 234

Table 5.5: Summary of type expression to commented Ada code generated/reused

5.4

Second Version of Avionics Domain Example

After demonstrating LILEANNA on the avionics domain example described in the previous section, its module composition capability was examined along with its implications on component design. As a result, new composition

5.4. SECOND VERSION OF AVIONICS DOMAIN EXAMPLE

105

constructs were added to LILEANNA and new component design guidelines were established. The code in the second version of the ADAGE avionics example was divided into three categories, which are defined by the method used to include the code in the final DSSA system. These categories are: 1. Realm code – a realm is an interface template or Ada package specification, which has a family of plug compatible implementations (i.e., Ada package bodies), 2. Glue code – the place where realm code is integrated, and 3. Support code – non-changing code modules. The following section describes the code categories. This is followed by the new module composition operations and an example of their use.

5.4.1

Code Categories

Realm Code All code modules in the Realm category directly correspond to realm components from the type expressions manipulated by GLUE. That is, if a realm component is included in the type expression system configuration, then the design record for that component “points to” a file containing Realm code. In some cases, however, there is no existing code for an indicated realm component. For example, ADAGE contains code for only one sensor driver. When more than one sensor is in the system configuration, LILEANNA must create the code to deal with the other sensors. This is done through the use of exemplars. These files are used both as part of a system under construction and as a foundation from which other files will be built. For example, if the system configuration contains more than one sensor (which should almost always be the case), LILEANNA is instructed by MEGEN to make a copy of the sensor exemplar file, and then to modify certain statements in the file to make it unique for the appropriate sensor. Glue Code Making a list of all the Realm code in a given system configuration, however, does not produce all the code that would be needed for a complete, compilable system. This is where the other two categories of code come into play. Glue code can be thought of as the code that binds the Realm code together. In other words, in a control flow diagram, Glue code “calls” the Realm code. The Glue code also determines the execution model of the system. Thus, the system can follow a tasking model, or an executive model, etc. depending on the Glue code used. The appropriate set of Glue code to include in the system is determined by the coding “style” annotation in the type expression that is output from GLUE. Exemplars are not used in Glue code, as all execution models are different enough from each other to warrant completely different sets of code to implement them. However, a method of “daisy-chaining” is used to add new procedure calls or case alternatives to some files. See the Wrapper Transformations and Daisy Chaining sections for an explanation of this process. Support Code Support code is the final category of code. This type of code “supports” the Glue and Realm code in that they need the Support code to compile. This code includes type definition packages, global data (abstract data type) packages, some reuse code, and so on. For the most part, Support code does not need to be modified by LILEANNA, except perhaps to change a constant value in a package specification.

5.4.2

Simple Exemplar Transformations

The following subsections

106

CHAPTER 5. MODULE COMPOSITION EXAMPLES

Changing Package Names Syntax: make is end; Changing Importation of Scope Replacement of import clause in Ada Package Body. Syntax: make is needs ( => ) end; Implementation: Search package body to do name change on with statement (and ripple change down into rest of body (use clause and any qualifications). Constant Setting Use inline view. (Syntax will change based on need) Syntax: make is [ view => is ... parameter pairings ... ] end;

5.4.3

Additional Transformations

Note: by convention, a make statement with source and target can be the same. Extending an Enumerated Type Syntax: make is *(add literal to ()) end; Implementation: add to the enumerated at the beginning of the DIANA list in the AST.

5.4. SECOND VERSION OF AVIONICS DOMAIN EXAMPLE

107

Adding an Import Clause Syntax: make is *(add with ) end; Implementation: Create DIANA AST node for the “with” statement and insert it in the tree of package specification .

5.4.4

Wrapper Transformations

There may be several types of “wrappers” that need to be applied to Ada packages. These fall into the following categories: • Sequence (for daisy chaining) • Case

Daisy Chaining This is a sequencing wrapper. Syntax: make is *(add call to end;

())

Implementation: put call to as last statement of

Extending a Case Statement Note: the current LILEANNA implementation supports a subset of the full Ada syntax. In particular, the must be a single variable, not an expression, and only a single assignment statement or subprogram invocation can be selected. LILEANNA assumes only a single case statement in the . Syntax: make is *(add when to ( => ) ) end; Implementation: put call to => as last statement in the case statement.

108

CHAPTER 5. MODULE COMPOSITION EXAMPLES

5.4.5

Sample MEGEN Inputs and Outputs

Adding a Sensor This example shows the addition of the first GPS sensor to the system. Additions of other sensors would be analogous to this example. This transformation would be used whenever any sensor in the following list shows up in a type expression. ahrs_snsr_a24g_1a_sim ahrs_snsr_aqu_4a_sim ahrs_snsr_asn_145_sim dmg_snsr_asn_99_sim dmg_snsr_kg_10_sim dmg_snsr_kg_10_21_sim dns_snsr_apn_147_sim dns_snsr_apn_175_sim dns_snsr_apn_217_sim dns_snsr_apn_218_sim dns_snsr_apn_221b_sim dns_snsr_asn_128_sim dns_snsr_asn_137_sim dns_snsr_asn_138_sim flir_snsr_aad_7_sim flir_snsr_aaq_10_sim flir_snsr_aaq_15_sim flir_snsr_aaq_16_sim flir_snsr_aaq_17_sim flir_snsr_aaq_18_sim gps_snsr_arn_149v2_sim gps_snsr_arn_151_sim gps_snsr_asr_151_sim ins_snsr_asn_141_sim ins_snsr_asn_143_sim ins_snsr_asn_143rlg_sim ins_snsr_asn_90_sim ins_snsr_ln_15j_sim ins_snsr_ltn_51_sim ins_snsr_skn_2443_sim lfadf_snsr_adf_607_sim lfadf_snsr_arn_149v1_sim lfadf_snsr_arn_59_sim lfadf_snsr_arn_6_sim lfadf_snsr_arn_89_sim lfadf_snsr_arn_89b_sim lfadf_snsr_dfa_73_sim lfadf_snsr_df_206_sim lfadf_snsr_df_206a_sim

loran_snsr_apn_157_sim loran_snsr_arn_92_sim loran_snsr_arn_148_sim mhs_snsr_asn_145_sim mhs_snsr_asn_43_sim mhs_snsr_c_12_sim mhs_snsr_sahrs_sim mls_snsr_arn_152_sim omega_snsr_arn_131_sim omega_snsr_arn_148_sim omega_snsr_asn_131_sim pls_snsr_ars_6_sim radalt_snsr_apn_133_hi_sim radalt_snsr_apn_171_lo_sim radalt_snsr_apn_194_lo_sim radalt_snsr_apn_209_lo_sim radalt_snsr_apn_22_lo_sim radalt_snsr_apn_232_h_l_cara_sim radalt_snsr_apn_232_lo_sim radalt_snsr_hg_9050_lo_sim radalt_snsr_scr_718_hi_sim radar_snsr_apq_122v8_sim radar_snsr_apq_158_sim radar_snsr_apq_168_sim radar_snsr_apq_170_sim radar_snsr_apq_174a_sim radar_snsr_apq_175_sim radar_snsr_apq_180_sim radar_snsr_aps_133_sim tacan_snsr_arn_118_sim tacan_snsr_arn_52_sim uvadf_snsr_ara_25_sim uvadf_snsr_ara_50_sim uvadf_snsr_df_301e_sim uvadf_snsr_oa_8697_sim vor_ils_mb_snsr_arn_123v1_sim vor_ils_mb_snsr_arn_147_sim vor_loc_snsr_arn_14_sim vor_loc_snsr_vor_101_sim

When adding new sensors to the configuration, several transformations must take place: 1. create the new sensor package, 2. add the new sensor to the enumerated type in package ConfiguredSensors, and

5.4. SECOND VERSION OF AVIONICS DOMAIN EXAMPLE

109

3. add “with” and “call” to the new sensor in package SampleSenors. Changes to package ConfiguredSensors type List is (NONE); needs to be changed to type List is (GPS1, NONE); Changes to procedure SampleSensors with GPS1_Sensor; needs to be added to the file ** AND ** GPS1_Sensor.SampleState; needs to be added to the body of the procedure Note: The 1 in GPS1 would be another number if more than one GPS sensor were in the system. For example, if this was the second GPS sensor, then INS1 would be changed to GPS2, and so on. Changes to package INS1 Sensor Specification and Body INS1_Sensor needs to be changed to GPS1_Sensor ** AND ** INS_Sensor needs to be changed to GPS_Sensor NOTE: The 1 in GPS1 would be another number if more than one GPS sensor were in the system. For example, if this was the second GPS sensor, then INS1 would be changed to GPS2, and so on. LILEANNA Make Statements make ConfiguredSensors is ConfiguredSensors*( add literal to List ( GPS1 ) ) end; make

SampleSensors is SampleSensors*( add with GPS1_Sensor ) *( add call to SampleSensors (GPS1_Sensor.SampleState) )

110

CHAPTER 5. MODULE COMPOSITION EXAMPLES

end; make

GPS1_Sensor is INS1_Sensor needs (INS_Sensor => GPS_Sensor)

end;

Exemplar Ada Package Specification and Body --********************************************************************---* LORAL FEDERAL SYSTEMS -- OWEGO, NY. *---* DSSA/ADAGE *---* *---* COMPONENT NAME: ConfiguredSensors *---* *---********************************************************************---* FUNCTIONAL DESCRIPTION: Contains a list of all sensors in the *---* system. *---* *---* KEYWORDS: sensor *---* *---********************************************************************-package ConfiguredSensors is -- There are no type expressions that correspond to these type values. type List is (NONE); -- default is NONE. end ConfiguredSensors ; --********************************************************************---* LORAL FEDERAL SYSTEMS -- OWEGO, NY. *---* DSSA/ADAGE *---* *---* COMPONENT NAME: INS1 *---* *---********************************************************************---* FUNCTIONAL DESCRIPTION: This package implements an INS sensor. *---* *---* KEYWORDS: INS, Sensor *---* *---********************************************************************-with with with with

Useability; NavDataMgr; SYSTEM_TYPES_CONFIG; use SYSTEM_TYPES_CONFIG; AVIONICS_NUMERIC_TYPES;

package body INS1_Sensor is OperatingState : Control := Run ; SnsrData : INS_Sensor.DATA_TYPE := INS_Sensor.DefaultData; PreviousTime : SYSTEM_TYPES_CONFIG.F := NavDataMgr.SystemTime; InversePollingRate : SYSTEM_TYPES_CONFIG.F; PrevPos : AVIONICS_NUMERIC_TYPES.XYZ_POSITION_TYPE := AVIONICS_NUMERIC_TYPES.Position_XYZ_Default; PrevVel : AVIONICS_NUMERIC_TYPES.VELOCITY_ENU_TYPE := AVIONICS_NUMERIC_TYPES.Velocity_ENU_Default; procedure SampleState is

5.4. SECOND VERSION OF AVIONICS DOMAIN EXAMPLE

begin SnsrData := INS_Sensor.ConvertTruthToSensor (PrevPos, PrevVel, SnsrError); if (PollingRate = 0) or (PreviousTime + InversePollingRate ’identifier’; in_line_view -> ’VIEW’ package_expression ’=>’ package_expression ’IS’ view_body; package_operator_list

-> -> ’(’ ’(’ ’(’ ’(’ ’(’ ’(’ ’[’

package_operator; package_operator ’*’ package_operator_list;

package_operator

-> -> -> -> -> -> ->

’RENAME’ ’ADD’ ’HIDE’ ’HIDE’ ’REPLACE’ ’REPLACE’ view_name

name_map ’)’; package_element ’)’; package_element ’)’; identifier_list ’)’; name_map ’)’; op_map ’)’; ’]’;

package_element

-> ’literal’; -> ’identifier’; -> subprogram_specification;

Appendix B

Software Component Conceptual Model This appendix presents a formal framework for developing reusable software components that can be composed using LILEANNA. This conceptual model distinguishes between three distinct aspects of a software component: 1. the concept or abstraction the component represents — what a component does — (e.g., a theory in LILEANNA), 2. the content of the component — how a component is implemented — (e.g., an Ada package body), and 3. the context that component is defined under — what is needed to complete the definition of a concept or content within a certain environment — (e.g., an Ada generic parameter or with statement, or a requirement or interface theory or needs clause in LILEANNA). These three aspects of a software component make the following assumptions about their environment: 1. There is a problem space (application domain) that can be described as a set of concepts (or objects if one prefers using an object-oriented paradigm). 2. There is a multi-point solution space that is characterized by components that implement the behavior of the concepts. 3. The solution space is populated by several different implementations, or “parameterized1 ” implementations that can be instantiated with different contexts within the solution space. Before proceeding further into the material in this appendix, it is important for one to realize the subtle implications that “dynamic binding/polymorphism” has on one’s approach to programming. The conceptual model described in this appendix assumes a programming language and environment where all parameters are bound prior to run time (with the exception of actual parameters passed to subprogram operations). Fundamentally, the model recognizes that binding can occur at or before compile time, and at load/link edit time. This view of binding, to some readers, may appear limiting (as contrasted to the type of dynamic binding found in true object-oriented programming languages such as Smalltalk), but this limitation, in reality, is a trade-off between flexibility and early error detection, which, in some application areas (e.g., real-time embedded software systems), is considered to be of greater importance. The first section of this appendix defines the terms context, content, and concept, in more detail and describes their relationships to modularization, specification, interface design and parameterization. This is followed by an example of a software component that has been defined using the model’s framework. Finally, the appendix concludes with an overview of high-level process guidelines that describe how domain analysis information can be gathered and refined to develop software components that conform with the conceptual model. 1 Perhaps

“generalized” is a better word.

138

B.1. THREE ASPECTS OF A SOFTWARE COMPONENT

Term

Definition

Concept Content Context of Concept Content

The “What” The “How” The Environment of the What and the How

Development Phase Used Design Implementation Instantiation

139

Structuring Type Horizontal Vertical

Horizontal (Generic)

Structure Mechanism Inheritance Importation Parameterization and Inheritance (for Concepts)

Table B.1: Summary of Conceptual Model of Reusable Software Components

B.1

Three Aspects of a Software Component

This conceptual model for software components is motivated by the need to develop useful, adaptable, and reliable software modules with which to build new applications. These three needs, addressed individually by the model, are as follows:. 1. A useful component meets the high-level requirements of at least one concept necessary to design and implement a new software application. 2. An adaptable component provides a facility such that modules can be easily tailored to the unique requirements of an application. 3. A reliable component accurately implements the concept that it defines. This conceptual model for software components, referred to as the 3C model [Tra90, Tra91a], is based on three aspects of a software component: concept, context, and content2 . These three terms are summarized in table B.1 and addressed individually in the subsections that follow.

B.1.1

Concept

The concept represented by a reusable software component is an abstract description of “what” the component does. Concepts are identified through requirement or domain analysis [PDA91] as providing the desired functionality for some aspect of a system. A concept is realized by an interface specification and an (optionally formal) description of the semantics (as a minimum, the pre- and post-conditions) associated with each operation. A theory in LILEANNA or an Ada package specification with its behavioral semantics described in Anna is an example of a representation for a reusable software concept.

B.1.2

Content

The content of a reusable software component is an implementation of the concept, or “how” a component does “what” it is supposed to do. The 3C model assumes that each reusable software component may have several implementations that obey the semantics of it’s concept (e.g., functional characteristics are the same, but the operational characteristics (e.g., timing and resource usage profile) are different). The collection of (28) stack packages found among Grady Booch’s components [Boo87] is an example of a family of implementations for the same concept (stack). 2 Joseph

Goguen has suggested that the 3Cs should stand for Category Theory, Colimits, and Comma Categories.

140

B.1.3

APPENDIX B. SOFTWARE COMPONENT CONCEPTUAL MODEL

Context

One of the failures of software reuse is that user’s expectations of a reusable software component do not meet the designer’s expectations of the reusable software component (the square-peg-in-the-round-hole syndrome). By explicitly defining the context3 of a reusable software component at the concept and content level, and formally defining and constraining its domain of applicability (through parameterization), the user can better select and adapt the component for reuse. The context of a reusable software component takes on three dimensions: 1. the conceptual context of a reusable software component — how the interface and semantics of the module relate to the interface and semantics of other modules, 2. the operational context of a reusable software component — what the functional requirements of the data/parameters being manipulated are, and 3. the implementation context of a reusable software component — how the module depends on other modules for its implementation. Parameterization, inheritance, and importation of scope through the use of abstract machine interfaces are all language mechanisms that assist in separating context from content. Within the framework of the 3C model, one uses these language constructs as follows: 1. One specifies the conceptual context of a software component by using type inheritance to express relationships between concepts (module interfaces). This occurs when two concepts share the same syntax and semantics (specialization/generalization). 2. One defines the operational context of a software component by using genericity (parameterization) to specify data and operations on the data being manipulated by a module (at the conceptual or implementation level). 3. One decides on the implementation context of a software component by selecting external operations to be used for and by the implementation of a module. Type inheritance and importation of scope are the two languages mechanisms that support the definition of a module’s implementation context. One should note the explicit separate roles of code and type inheritance in this model. Type inheritance expresses the conceptual context of a module. The conceptual context of a software module forms a true partial order in that the concept inheriting another concept “is a” subtype of that concept. Code inheritance is used as an implementation mechanism and may or may not be the same as the type inheritance used to express the conceptual context of the concept associated with the software component for which the implementation is being created. An example of conceptual context is a deque (double ended queue) that can be used to describe the interface of a stack (see the example section B.2.1). The operational context for a stack is the type of the element being stored. The implementation context of a particular stack implementation might be a sequence abstraction. That is, the implementation would be designed to refer to operations in an abstract machine interface found in a sequence concept, which could have several implementations (e.g., array or linked list). Alternatively, the stack could be indirectly implemented by simply renaming some of the operations in an implementation of the deque and inheriting the rest. The selection of an implementation, or the content associated with the concept is determined by trade-offs in context. Clearly, knowing the characteristics of the type of data structure being manipulated will lead to more efficient implementations. This can result in the population of a reuse library with several efficient implementations of the same (parameterized) concept, each tailored to a particular context[MS87]. At design time, a programmer could identify the concept and define the context it is being manipulated under based on requirements or operating constraints. 3 Another

view of the context of a module is identifying what can change.

B.2. SOFTWARE COMPONENT EXAMPLE

141

generic theory QUEUE [ P::Private_Type ] is DEQUE*(rename (Deque => Queue) −− type inheritance (Add_Front => Add) (Remove_End => Remove) ) *(hide Add_End, Remove_Front) end QUEUE; where Private_Type is defined as: theory PRIVATE_TYPE is type Element; procedure ”:=” (X,Y : Element); −− obviously not in Ada end PRIVATE_TYPE; Figure B.1: Queue Concept Formal Description At implementation time, the programmer could instantiate an implementation of the concept by specifying the conceptual context plus any other contextual information required by the implementation (content). Separating out context from concept and content complements the work of Parnas in suggesting that the quality of software can be improved by predicting change [Par72c]. It had been demonstrated that software is more reusable, or more easily maintained, if the types of possible modifications to the software are taken into consideration at design time. One should note that all concepts and contents have a context. One of the most common problems faced with trying to reuse previously written software is determining the assumptions made by the original developer. These assumptions often encompass contextual information that is buried in the interface or implementation and point out the need to separate the context from the concept and content of a reusable software component.

B.2

Software Component Example

This section contains a small example of a software component that was designed following the concepts in the 3C model.

B.2.1

Queue Example

A queue is FIFO (First In, First Out) data structure. Its conceptual context is such that its interface can be described in terms of the concept of a deque, where the adding and deleting of elements can be only done at fixed opposite ends. A queue is a specialization of a deque. The queue’s operational context or generic parameters, like the deque, requires the type of data being stored along with an assignment operation on objects of that type. Figure B.1 contains a formal specification of the interface to the QUEUE concept in terms of its relationship to a DEQUE (interface is not shown). The content or implementation associated with the QUEUE theory must manipulate the SEQUENCE that the Elements are in. This information can be part of the implementation as expressed by an interface theory in the needs clause (figure B.2) or the operational context expressed as an requirements theory in the generic parameters (figure B.3). That is, linkage to the abstract interface associated with some implementation of a sequence can be established through importation of scope, or through a generic parameter. In the first case (figure B.2), the implementation context furnishes linkage to the SEQUENCE theory. The operational context is the same as in the QUEUE theory. Alternatively, the module could be implemented by combining the

142

APPENDIX B. SOFTWARE COMPONENT CONCEPTUAL MODEL

generic package QUEUE [ P::Private_Type ] needs SEQUENCE [ P::Private_Type ] is type Queue; function Add ( E: Element; Q:Queue ) return Queue; function Remove ( E: Element; Q Queue ) return Queue; function Is_Empty ( Q: Queue) return Boolean; procedure Print (Q: Queue); end QUEUE;

Figure B.2: Separating Operational and Implementation Context generic package QUEUE [ P::Private_Type; S::SEQUENCE[ P::Private_Type ] ] is type Queue; function Add ( E: Element; Q:Queue ) return Queue; function Remove ( E: Element; Q Queue ) return Queue; function Is_Empty ( Q: Queue) return Boolean; procedure Print (Q: Queue); end QUEUE; Figure B.3: Mixing Operational and Implementation Context implementation and operational context (figure B.3). Finally, the conceptual context may be carried over to the implementation context with explicit code inheritance from some implementation of a DEQUE (figure B.4). This example is written using the module composition and module expression capabilities of LILEANNA. The module DEQUE is inherited, with the appropriate elements renamed and hidden. One should note in this example, the polymorphic operations (Is_Empty and Print as shown in figure B.3) are not explicitly mapped. This is due in part to discipline in naming conventions that one can take advantage of this commonality. Analysis of Example This example has clearly shown that Ada implementations (package specifications and bodies) are syntactically different from the three LA package descriptions shown in figures B.2, B.3, and B.4. The first one “withs” a package, the second passes in generic formal parameters, and the third uses code inheritance (actually, if this example used

generic package QUEUE [ Private_Type ] is inherit DEQUE*(rename (Deque => Queue) −− code inheritance (Add_Front => Add), (Remove_Front => Remove)) *(hide Add_End, Remove_End); needs SEQUENCE [ Private_Type ] end QUEUE; Figure B.4: Explicit use of Conceptual Context as Implementation Context

B.3. PARAMETERIZATION PROCESS OUTLINE

143

Ada, we would be forced to use derived types and the re-packaging of operations.) How the user instantiates the modules used in this example is another issue that is beyond the capabilities of Ada (hence supporting the need for a module composition language such as LILEANNA). Since Ada expects one and only one package to satisfy the package specification of a “with” clause, the only way to circumvent this and to stay within the language is to unbundle the package and pass it piece meal as generic formal parameters (as shown in figure B.3, the second QUEUE example). But, this is less than desirable because this confuses the implementation context with the operational context, hence increasing the complexity of one interface, when they really are distinct interfaces used for different purposes. There are two general module interface “design rules of thumb” that apply to using the 3C model: 1. for abstract data types modules, the operation in the module’s interface should contain at least one operand of the type defined by the module. 2. for parameterized types, one should include a type/package in a module’s requirement theory (generic parameter list) if an operand of that type appears in the signature of an operation of that module. The second implementation example shown in figure B.3 violates the second rule of thumb, consequently the interface can be judged weaker than the one shown in figure B.2.

B.3

Parameterization Process Outline

This section describes, at a high level, the sequence of steps for using domain analysis information to derive parameterized software components based on the 3C model. One should recognize that domain analysis information can be gathered top down or bottom up. For example, top-down domain analysis could start with an entity relationship/object model or a data flow of an application domain and determine the components or features and their attributes. Bottom-up domain analysis is based on analyzing several existing systems in an application domain to identify commonality. In general, a domain analyst 1. 2. 3. 4.

identifies a concept, determines if variations exist, factors out the commonality, and provides selection parameters that specify the context.

Alternatively, a concept can be generalized over a range of contextual values. It becomes an economic issue and implementation trade-off as to how many implementations are associated with each concept. Clearly, one general purpose implementation might lead to certain inefficiencies, therefore, several implementations, separated and selectable by context, are often desired. A concept may have several implementations, each spanning a subset of the possible solution space bounded by the contextual information associated with the concept. The process of developing reusable software components based on the 3C model (the process of separating concept from context, content from concept, and context from content) may be described as follows: 1. Separating Concept from Content (a) Analyze an application domain. Recognize commonality of some functionality within an application domain. (b) Use commonality to define a concept/theory. (c) Isolate differences or variations of the functionality. (d) Isolate differences or variations in possible implementations of the functionality.

144

APPENDIX B. SOFTWARE COMPONENT CONCEPTUAL MODEL

(e) Record implementation issues for later use. (f) Define an interface to the concept in the form of an Abstract Data Type. (g) Define the semantics of the concept as pre- and post- conditions (as a minimum). 2. Separating Concept from Context (a) Use the differences in existing or planned implementations to define the operational context of the concept based on the data being manipulated. (b) Given a concept and its operational context, iterate and generalize the concept by expanding the context. (c) Given a concept and its context, iterate and refine the concepts and its context. Continue until the concept and its context are defined in terms of basic concepts (hopefully a set of which are in the reuse library). Note that inheritance type hierarchies are useful in expressing certain concepts in terms of related concepts. (d) Refine the interface to the concept, if necessary, taking into account contextual information. (e) Refine the semantics of the concept, if necessary. 3. Separating Context from Content (a) Define the operational and implementation context of the implementation. Determine the implementation variations and dependencies (e.g., operating system, hardware, or compiler dependencies). (b) Define a context of interest for the concept. (c) Define a context of interest for the content. (d) Implement variations of the concept according to trade-offs on performance and resources with respect to the context of interest. (e) Verify that the content (each implementation) matches the concept. A more detailed domain analysis process, formally specified in Teamware [YT92], a process programming language, is found in DSSA Engineering Process Guidelines [TC92]. A summary of these guidelines appears in [TCY93].

Appendix C

Module Composition Semantics Paper An Implementation-Oriented Semantics for Module Composition Joseph A. Goguen1 Department of Computer Science & Engineering University of California at San Diego La Jolla CA 92093-0114

Will Tracz Lockheed Martin Federal Systems Owego, New York 13827-3994

12 November 1996 Abstract: This paper describes an approach to module composition based on executing “module expressions” to build systems out of component modules, and gives it a novel semantics that is intended to aid with its implementation. This semantics is set theoretic, using the technical notions of tuple set, partial signature, and institution, and avoiding more difficult mathematics such as abstract algebra and category theory. Language features include information hiding, both vertical and horizontal composition, and views for binding modules to interfaces. Vertical composition refers to the hierarchical structuring of a system into layers, while horizontal composition refers to the structure of a given layer. Modules may involve information hiding, and views may involve behavioral satisfaction of a theory by a module. A number of “Laws of Software Composition” are given, showing how the various module composition operations are related.

C.1

Introduction No way of thinking or doing, however ancient, can be trusted without proof. – Henry David Thoreau, “Economy,” Walden (1854).

1 This research was supported in part by the Defense Advanced Research Projects Agency (DARPA) in cooperation with the US Air Force Wright Laboratory Avionics Directorate under contract # F33615-91-C-1788, the European Community under ESPRIT-2 BRA Working Groups 6071, IS-CORE (Information Systems COrrectness and REusability) and 6112, COMPASS (COMPrehensive Algebraic Approach to System Specification and development), Fujitsu Laboratories Limited, and the Information Technology Promotion Agency, Japan, as part of the R & D of Basic Technology for Future Industries “New Models for Software Architecture” project sponsored by NEDO (New Energy and Industrial Technology Development Organization).

145

146

APPENDIX C. MODULE COMPOSITION SEMANTICS PAPER

The approach to module composition described in this paper can be used for many different programming languages, by providing a simple module connection language (MCL) with “module expressions” that say how to manipulate and connect modules in the programming language. The general approach, called “parameterized programming” [Gog89], involves a module specification capability; however, programmers can specify as little of a module as they like, as long as they declare the syntax. The approach has been validated with experiments using lileanna [Tra93a] on real examples. lileanna, which has Ada [Ada83] as its implementation language and Anna [KBL80, Luc90] as its specification language, is also used for illustrations in this paper. Our semantics for module expressions uses simple set theoretic manipulations to define module composition operations. This semantics is intended to aid implementors of module composition facilities; hence it is not a denotational or axiomatic semantics in the usual sense. In particular, it does not directly address the semantics of statements or what happens at compile time or at run time; instead, it is concerned with the semantics of modules and their interconnection, i.e., with what happens at module composition time. For this reason, it abstracts away details of the languages used for specification and programming. Module composition languages can be used at least two different ways in software development: 1. descriptively, to specify and analyze a given design or architecture (i.e., the interfaces and interconnections of modules); and 2. constructively, to create a new design from existing modules, using operations that combine and transform modules. Using an MCL descriptively is like writing and reading a blueprint; it tells you about the structure of a house; languages that only provide these capabilities are often called MILs, Module Interconnection Languages [PDN86]. Using an MCL constructively is like having robots build a (modular) house by following the blueprint. This facilitates reuse and helps support controlled evolution. As illustrated in Figure C.1, it can simplify the software lifecycle by eliminating detailed design and coding2 , provided a suitable library of software modules with their specifications and interrelationships is available. Parameterized programming supports this by executing module expressions, and thus goes far beyond merely specifying modules and systems.

Specification r

r

r

Detailed Design

r

r r

Module Composition

Code r

6

Test

r

r

Figure C.1: Idealized Software Lifecycle with and without Module Composition

Analogy with Functional Programming It may be helpful to think of module composition as functional programming with modules (see [Bur85] for a nice exposition of this idea). The analogy is appropriate because there is no state at module composition time. Instead, we are evaluating expressions, called module expressions, that say how to put together software components. The result of evaluating a module expression is a value, namely the composed system; it is built by applying module composition operations to subsystems, which are also values. 2 This figure is itself a great oversimplification, because it omits the processes of feedback and reconstruction that occur in real software development projects. Research on the sociology of software development shows that the division of tasks into phases is to a large extent arbitrary, and is also subject to reclassification as the project evolves; see papers in [JG94], especially [BS94] and [Gog94], and also see [Gog96a].

C.1. INTRODUCTION

147

Under this analogy, modules have types that are called theories. A theory is another kind of module that is used to describe both the syntax and semantics of modules that supply code. We write C :: T to indicate that a module C has the “large grain type” T; this involves more than the usual notion of type indicated by the notation C : T, because (what we call) a view must be given from T to C, binding the formal symbols in T to actual symbols in C. Furthermore, the axioms in T should be satisfied by the corresponding implementations of the symbols in C. Views are also used in instantiating parameterized modules, to say how to bind symbols of a parameterized module’s interface theory to symbols (e.g., types and operators) in the module used as an actual parameter. The axioms in the theory of a module give rise to “proof obligations;” these are mathematical assertions that should be true in order for the program to work as expected. We do not recommend that the proof aspect of this notion of type should be required for normal production programming, but rather that a “hook” should be left, in case rigorous formal methods are required for some critical application. Thus in normal programming, the axioms serve to document properties of interfaces the designers and programmers considered especially important, and believed to be true, perhaps as the result of informal reasoning. The word “type” has many different meanings in computer science. There are the “small grain types” used in specification and programming languages; in our examples, these will be lileanna and Ada types. There are also the “large grain types” described above. Moreover, the word “type” is used in a branch of logic called “type theory” for syntactic expressions (that may include axioms) for describing collections of entities. Types in the type theory sense cannot handle modules in our sense very smoothly, because small grain types and operators can only be encapsulated together through an awkward encoding into a single complicated type. There are two kinds of type in lileanna: (1) purely syntactic types that correspond to those in a programming language, and (2) types in roughly the sense of type theory, i.e., programming language types and operations encapsulated together with some axioms, which are used to define semantic subtypes of the programming and specification language types. Hereafter this paper will in general only use the word “type” for programming language types, and will use the word “theory” for the large grain type used for modules3 . Some History The paradigm for module composition described in this paper is based on parameterized programming and hyperprogramming4 [Gog89, Gog90b]. Because this approach involves modules for both specification and code, both kinds of module must be composed. Specifications are used as “headers” for code, and are combined with simple set theoretic operations. At the code level, composition may be done with intermediate compiled code, which is then sent to the compiler backend after composition has been completed. In the case of lileanna with Ada, this means using diana5 [DIA83, Tra93b]. Although we avoid complex mathematics, such as abstract algebra and category theory, both the module composition facility and its semantics as described in this paper are largely inspired by work done using such formalisms, including the Clear specification language [BG77, BG80], and the OBJ [GM96a, GWM+ ar] system. The approach in this paper differs from that of those languages in providing a constructive module composition facility for an imperative programming language. This was contemplated for the CAT [GB80] and LIL [Gog85] systems, which are the closest ancestors of the approach in this paper; however, details of the semantics of these systems were not fully developed and they were not implemented. This paper also presents some extensions to the original conception of parameterized programming. The set theoretic implementation oriented semantics in this paper is novel. Sannella [San82] gave a pioneering set theoretic semantics for Clear. The elegant work of Wirsing [Wir86] on the ASL kernel specification language also involved a set theoretic approach. Moreover, many papers have used institutions and category theory to achieve generality (e.g., see [BG80, DGS91, ST88] and [Gog91a]). However, it has seemed that the generality of institutions was incompatible with the specificity of set theory, and this is the first paper to combine these two approaches, as well as the first to use tuple sets and partial signatures. The work in this paper also differs from most previous work in the algebraic specification tradition in that it is concerned with generating systems, rather than just specifying them, 3 The parameterized programming literature [Gog89] uses the word “sort” for small grain types, and we may occasionally use this terminology if extra clarity is needed. 4 The term megaprogramming has more recently been used for these ideas within the DARPA community [BS92, WWC92]. 5 This is an acronym for Descriptive Intermediate Attributed Notation for Ada.

148

APPENDIX C. MODULE COMPOSITION SEMANTICS PAPER

i.e., it is constructive as well as descriptive. Another unusual feature is the use of information hiding in specifications and the resulting behavioral (i.e., “black box”) notion of satisfaction for views [GM96b, GM96c]). The version of parameterized programming in this paper, like that in LIL, provides both horizontal and vertical composition. Vertical composition concerns the structuring of a system into layers, each providing services to higher layers, while horizontal composition concerns the structure of each layer. Some basic laws for composing specifications were proved in [GM82] and also in [ST88].

C.1.1

Summary of the Paper

There are two main technical ideas that make the goals of this paper feasible: (1) tuple sets, which allow simple recursive definitions of basic set theoretic operations like union and inclusion for complex structures like signatures and specifications, and (2) institutions, which axiomatize the intuitive notion of logical system. Tuple sets are introduced in Section C.2.2; they permit simplified definitions of operations for combining modules, based on set theory. They also permit us to define the notion of partial signature, which again simplifies our definitions. Institutions, introduced in Section C.2.3, let us handle both specification and programming languages while avoiding the enormous complexity of their semantic definitions. Horizontal composition is discussed in Section C.4, beginning with a discussion of the module graph in Section C.2.1. This is the basic data structure for our semantics; its nodes are module names, and its edges indicate relationships between modules, including inheritance. Each module name M has associated to it a specification module Q(M ), and a list I(M ) of implementation modules. The main mechanisms for horizontal composition are renaming, aggregation, and instantiation of generic modules. These are discussed in Sections C.3.3, C.4.4, and C.4.6, respectively; views are discussed in Section C.3.3. The notion of view introduced there differs from previous notions in that it respects information hiding, i.e., it only requires behavioral satisfaction of axioms; it also takes account of imported modules. Overload resolution is discussed in Section C.3.1 and a way of adding or removing functionality from modules is discussed in Section C.4.6. Module expressions are discussed in Section C.4.7. Vertical composition, as discussed in Section C.5, allows designs to have multiple layers, where each layer provides services to higher layers. Furthermore, each layer may itself have a complex horizontal structure, involving the composition of multiple modules. The module system provided by parameterized programming is much more powerful than that of Ada, and in fact it provides all of the power of higher order programming without the need for higher order functions, as shown in [Gog90a].

C.2. FOUNDATIONAL CONCEPTS

149

This paper includes a number of “Laws of Software Composition,” which say how various composition operations are related to one another. These are useful in implementing a module composition system, and many have never appeared before, especially those involving vertical composition.

C.1.2

Conventions for Exposition

Definitions, examples, results (theorems, propositions, corollaries, etc.) are numbered sequentially on a single “counter.” Thus, Example 4 follows Definition 3, and there is no Definition 4. This method of enumeration is intended to facilitate browsing. Assumptions and laws each have their own environment, and are numbered separately. Assumptions are gradually enriched with further conditions, in the hope that many readers will find the paper easier to follow this way. We may abbreviate “if and only if” by “iff”. If S is a set, then S ∗ denotes the set of all finite lists from S, including the empty list, which is denoted []. We denote a list with n elements using the notation [a1 , ..., an ].

C.2

Foundational Concepts Let all laws be clear, uniform, and precise; to interpret laws is almost always to corrupt them. – Francois Voltaire, Philosophical Dictionary (1764).

This section discusses some concepts that provide the mathematical foundation for the development that follows. These are: (1) the module graph; (2) the notions of tuple set and signature; (3) the notions of sentence and model; and (4) the notion of institution, which ties together the notions of signature, sentence and model. These concepts are necessarily rather abstract, because of our goal of treating any (suitable) combination of programming and specification language.

C.2.1

The Module Graph

The parts from which systems are constructed are called modules. Parameterized programming has two kinds of module: (1) specification modules, and (2) implementation modules, which are discussed in Sections C.3.1 and C.3.2, respectively.

150

APPENDIX C. MODULE COMPOSITION SEMANTICS PAPER

The following gives a first definition for the basic module graph data structure that is assumed throughout this paper. It is gradually extended with further assumptions to handle further features of parameterized programming as they are introduced. Definition 1 A module graph G consists of a finite set N of nodes, called module names, a finite set E of edges, two functions, d0 , d1 : E → N , which respectively give the source and target node for each edge, plus an acyclic subgraph H, called the inheritance graph, having the same node set N as G; thus, the edges of H, called inheritance edges, are a subset I of E, and the source and target functions of H agree with d0 and d1 on I. We write N < M if there is a path from N to M in H. We may use the notation e : M → M to indicate that e is an edge with d0 (e) = M and d1 (e) = M . 2 Intuitively, the edges of the module graph indicate relationships between modules, of which the most basic are those of inheritance, indicated by the edges in the inheritance subgraph6 . N < M means that M inherits from N . This relation is transitive because the composition of two paths is another path. (The idea of module graphs may be found in [Gog91b].) Another kind of relationship between modules, indicated by edges in the module graph, is given by a view, which asserts that the target module satisfies the axioms given in the source module; views are discussed in Section C.3.3. The module graph G describes the resources available at a given moment for building systems, including both modules and knowledge about their properties; the role of G is analogous to that of an environment for evaluating expressions in a programming language. Assumption 1 There is a fixed module graph G, with a fixed inheritance subgraph H. 2 Example 2 Assume modules M 1, M 2, M 3 such that module M 3 inherits M 2 and module M 2 inherits M 1, i.e., M 1 < M 2 < M 3. Then the module graph G has three nodes, named M1 , M2 and M3 , and two edges, one from M1 to M2 , and one from M2 to M3 , as shown in Figure C.2. Both edges are inheritance edges. 2

#

inherits

M1 "

#

Ã

!

-

inherits

M2 "

#

Ã

!

-

à M3

"

!

Figure C.2: A Simple Module Graph As we will see later, other information can also be associated with a module name, such as a description of the data representations used, test cases, and administrative information (e.g., the history of various implementations, such as programmer, date, and comments). All this is important for handling the evolutionary aspects of software development.

C.2.2

Signatures and Tuple Sets

We will later assume that there is a given fixed class Sign of signatures, which are used for declaring the syntax of modules, such that all signatures are tuple sets, in the sense of Definition 3 below. In this recursive definition, (0) is the base case and (1) is the recursive step. It may help the reader to visualize tuple sets as ordered trees that have sets on their leaf nodes. Definition 3 A tuple set is either 6 Instead

of having a subgraph H of G, we could put the label “inherits” on all inheritance edges, as suggested by Figure C.2.

C.2. FOUNDATIONAL CONCEPTS

151

(0) a set, or else (1) a tuple of tuple sets. Two tuple sets have the same form if and only if they are both sets, or else are tuples of the same length such that their corresponding components have the same form. More formally, t ∼ t iff (0) t and t are both sets, or else (1) t and t are tuples of the same length, say n, and ti ∼ ti for i = 1, ..., n. We will use the notation (t1 , ..., tn ) for a tuple having n components, called an n-tuple. The sets that occur as the bottom level components of a tuple set are called its base sets, and their elements are called its symbols. 2 Figure C.3 shows a typical tuple set structure, where the circles represent the base sets of symbols, and the internal nodes represent tuples. Sometimes we want to use pairs, triples, lists, etc. that are not tuples. For these cases, we will use the notation [t1 , ..., tn ] for a list of length n, and call it a pair when n = 2 and a triple when n = 3. r ¡ @ ¡ j@ @r ¡r ¡ @ ¡ ¡ @@ j j

¡r ¡ @ j¡ j@ j

@ r ¡ @ @ j j¡

Figure C.3: A Tuple Set Structure

Example 4 Any set S is a tuple set. Also, any tuple of sets is a tuple set, e.g., ({a, b}, {1, 2}). Similarly, any tuple of tuple sets is a tuple set, e.g., ({a, b, c}, ({a, b}, {1, 2})). And so on recursively. The tuple set ({a, b, c}, ({a, b}, {1, 2})) has symbols a, b, c, 1, 2. On the other hand, the tuple set {[0, 0], [−, 1]} has as its symbols the lists [0, 0] and [−, 1], rather than 0, 1, and −. 2 (Although we want to avoid being distracted by problems in the foundations of mathematics in this paper, we also want to be consistent with the usual set theoretic foundations. Therefore we want everything to be a set, including our tuples and our tuple sets. To this end, we may distinguish between sets as used in Definition 3 (these are the circles in Figure C.3) and sets in the sense of set theory, by calling the former “set”s and the latter just “set”s. Then tuples should be “set”s but not “set”s. This can be achieved in various ways. One is to use special ground elements (also called “urelements”) to index the components of tuples, and never use those urelements for anything else; then everything is a “set”, but only those “set”s that have not been formed using tuples (i.e., using the special urelements) are “set”s. Another approach is to give an axiomatization for set theory with a special constructor for tuples in addition to the usual ones for power set, etc. A third approach is to define tuple sets as the abstract data type of ordered trees with leaf nodes labeled by sets.)

The ordered tree view of tuple sets shows that we can use paths as selectors for the base sets of tuple sets. If Σ is a tuple set and p is a path to a leaf node, then we let Σp denote the base set that is attached to that leaf node. Example 5 The various signatures used for equational logic are tuple sets. For unsorted equational logic, there is a set O of operator symbols, plus an assignment of an arity7 a(o) to each o ∈ O. Since a is a function, it can of course be viewed as a set of pairs in the usual way. Thus, we have Σ = (O, a) where a is a set of pairs [o, n] where n is a non-negative integer and o ∈ O, such that there is exactly one such pair for each o ∈ O. 7 The

word “arity” in its general sense refers to the constraints on the arguments of an operator.

152

APPENDIX C. MODULE COMPOSITION SEMANTICS PAPER

Alternatively, we could let Σ be a set of pairs [o, n], where n is a natural number giving the arity8 ; we can then define O from Σ, as the set of all o’s that occur in the pairs in Σ. Notice that this second approach allows overloading, in that the same operator symbol o can occur with more than one arity n. (Note that the pairs [o, n] are not tuple sets here, they are symbols.) For many sorted (also called “heterogeneous”) equational logic, signatures consist of a set S of sorts (i.e., “types”) plus a set Σw,s of operator symbols of sort s and arity w, for each s ∈ S and w ∈ S ∗ . Thus, a signature is a pair (S, r) where S is a set and r is a set of triples [w, s, o] with s ∈ S, w ∈ S ∗ , and o an operator symbol. This formulation again allows overloading, in that a given operator symbol o can occur with more than one “rank” [w, s]. 2 We can define relations and operations on tuple sets like equality, inclusion, union, intersection, product and difference, component-wise, by recursion over the tuple set structure: Definition 6 Two tuple sets, t, t are equal, written t = t , iff they have the same form and all their corresponding components are equal, i.e., if and only if t and t have the same number of components, say n, and ti = ti for i = 1, ..., n. We define inclusion similarly: t ⊆ t iff t and t have the same form, with say n components, and ti ⊆ ti for i = 1, ..., n. Finally, the difference (or the union) of two or more tuple sets, all of the same form, is formed by taking the difference (or union) of the tuple sets occurring as their components. 2 Because signatures are tuple sets, Definition 6 gives us a natural notion of subsignature: Σ is a subsignature of Σ iff Σ ⊆ Σ as tuple sets. For example, we have (H, (T, O, V, E), (TX , OX , VX , EX ), A) ⊆ (H , (T , O , V , E ), (TX , OX , VX , EX ), A ) if and only if H ⊆ H and T ⊆ T and O ⊆ O and so on.

The assumption that signatures are tuple sets also gives us a natural notion of signature map. Here, we define it as a special case of the (also natural) notion of a relation between two tuple sets of the same form, as a tuple set again having the same form, and having pairs of symbols as its own symbols.

Definition 7 Given tuple sets Σ, Σ of the same form, then a tuple set relation Σ → Σ is a tuple set9 of pairs of symbols (i.e., a subset of Σ × Σ ). A tuple set relation is a tuple set map iff each of its base sets is a relation that is actually a function. A tuple set map is injective or surjective iff each of its base set functions is. Similarly, a tuple set map is an isomorphism iff each of its base set functions is; in this case we may write Σ ≈ Σ . Given a tuple set Ω and a tuple set relation h : Ω → Σ, let Ω ∗ h be the tuple set with each symbol a in each base set B of Ω replaced by the corresponding symbols a in pairs [a, a ] in the corresponding base set of h, for each symbol a in Ω, i.e., (Ω ∗ h)p = {a | a ∈ Ωp and [a, a ] ∈ hp } , where Ωp denotes the base set of Ω at the end of the path p. We call Ω ∗ h the renaming of Ω by h. We may use the same notation when no target signature is given, since a smallest signature that will work can always be recovered from a tuple set of pairs with symbols from Σ. 2 Although tuple sets of the same form are certainly closed under set theoretic operations performed component-wise10 , a given class of signatures need not be closed under all set theoretic operations, as shown by the following: Example 8 Many sorted equational signatures are not closed under tuple set difference. In particular, let S = {a, b}, S = {a}, r = {[ab, a, f ], [a, a, g], [b, b, h]}, r = {[a, a, g]}, and consider (S, r)−(S , r ) = ({b}, {[ab, a, f ], [b, b, h]}), which is not a signature. 2 This motivates the following: 8 Including

the arity in the name of an operator agrees with conventions used for Prolog. we should also add the source and target tuple sets to this tuple set of pairs. 10 That is, the union, intersection, difference, etc. of two tuple sets of the same form are again tuple sets of that same form. 9 Technically,

C.2. FOUNDATIONAL CONCEPTS

153

Definition 9 Given a collection Sign of tuple sets called “signatures,” let Sign − denote the smallest class of tuple sets that contains Sign and is closed under tuple set difference. The elements of Sign − will be called partial signatures. 2 The union of a partial signature with a signature can be a signature; for example, if Σ and Σ are signatures with Σ ⊆ Σ, then Σ − Σ is a partial signature, and Σ ∪ (Σ − Σ ) = Σ. (We could use a signature inclusion Σ → Σ to represent the difference Σ − Σ . This would be adequate because we only need first order differences, and it would be more elegant, but it is less concrete and harder to understand.) Example 10 A lileanna signature has the form (T, O, V, E), where • • • •

T is the set of visible (i.e., exported) type declarations, O is the set of visible (i.e., exported) operator declarations (with their input and output types), V is the set of variable declarations (including types), and E is the set of exception declarations.

The declarations in Figure C.4 form a partial signature, because the types Integer and Boolean are used but not declared (they are assumed to be imported). Here T contains Stack, O contains Is_Empty, Is_Full, Push, Pop, and Top, while E contains Stack_Empty plus Stack_Overflow, and V is empty. 2

type Stack; function Is_Full ( S : Stack ) return Boolean; function Is_Empty ( S : Stack ) return Boolean; exception Stack_Overflow; exception Stack_Empty; function Push ( I : Integer; S : Stack ) return Stack; function Pop ( S : Stack ) return Stack; function Top ( S : Stack ) return Integer; Figure C.4: lileanna Signature for Bounded Stack of Integers

C.2.3

Institutions, Sentences, and Models

Laws and institutions must go hand in hand with the progress of the human mind. Jefferson, July 12, 1816.

– Thomas

This subsection presents an abstract framework that allows a broad range of notions of signature and axiom for use in specifications, as well as of models for use as implementations. For example, specifications can be written in Anna, and implementations in Ada; other pairs of a specification and an implementation language could also be used. We achieve this generality by using an axiomatization of the notion of “logical system” called an institution. In this way, we avoid having to work with a fixed logical system, with a fixed notion of axiom for specification modules, and a fixed notion of model. Instead, we can use any logical system that satisfies the reasonable assumptions given in Definition 11 below. It is outside the scope of this paper to develop the details of any particular logical system, and then show that it satisfies the conditions of Definition 11. For example, to provide all of the necessary details for lileanna specifications with Ada packages for models would probably take more than a hundred pages of formal semantics, and would add little to the value of this paper. However, there are good reasons for believing that such examples will in fact satisfy the definition (e.g., see the many examples in [GB92]), and we will use Anna and Ada to illustrate the theory developed in this paper. An intuitive explanation of each condition is given in the paragraph following the formal definition; the reader may wish to read this material in parallel with the formal definition.

154

APPENDIX C. MODULE COMPOSITION SEMANTICS PAPER

Definition 11 An institution satisfies the following: 0. There is a class11 Sign of signatures. 1. For each signature Σ, there is a set Sen(Σ) of what we will call (well formed) sentences built using Σ. 2. For each signature Σ, there is a class M od(Σ) of Σ-models, which provide (what we will call) interpretations for the symbols in Σ. 3. For each signature Σ, there is a satisfaction relation between Σ-sentences and Σ-models, written c |=Σ a, where c is a Σ-model and a is a Σ-sentence; we may omit the subscript Σ on |=Σ if it is clear from context. We pronounce c |= a as “c satisfies a”. 4. For any signatures Σ and Σ , there is a set M or(Σ, Σ ) of signature maps from Σ to Σ , where h ∈ M or(Σ, Σ ) may be written h : Σ → Σ . There is a composition defined for signature maps, a function M or(Σ, Σ ) × M or(Σ , Σ ) → M or(Σ, Σ ) for each Σ, Σ , Σ , which is associative and has an identity 1Σ in M or(Σ, Σ) for each Σ. We use the notation h; h for the composition of h in M or(Σ, Σ ) with h in M or(Σ , Σ ). Then we have (h; h ); h = h; (h ; h ) and h; 1ΣI = h and 1Σ ; h = h for suitable h, h , h . 5. Given a signature map h : Σ → Σ and a Σ-sentence a, there is a renaming of a by h, also called a translation of a by h, denoted a ∗ h, which is a Σ -sentence, such that a ∗ 1Σ = a and a ∗ (h; h ) = (a ∗ h) ∗ h , for h : Σ → Σ and h : Σ → Σ . 6. For each signature map h : Σ → Σ and each Σ -model c , there is a renaming of c by h, denoted c ∗ h, a Σ-model such that c ∗ 1Σ = c and c ∗ (h; h ) = (c ∗ h ) ∗ h. 7. Given a Σ-sentence a, a Σ -model c and a signature map h : Σ → Σ , we require that c |=ΣI a ∗ h iff c ∗ h |=Σ a . (This is called the satisfaction condition.) We say that a Σ-model c satisfies a set A of Σ-sentences iff it satisfies each one of them, and in this case we write c |=Σ A. 2 Condition 0. in the above definition just says that we have signatures for declaring notation to be used in sentences and models. In our examples, signatures have types and operators, private types and private operators, and exceptions. Condition 1. says that there are some sentences. In our examples, Σ-sentences are built using the types, operators and exceptions in Σ as non-logical symbols. Condition 2. says that there are some concrete entities, such as Ada programs, to which sentences can refer (provided the signatures match). In our examples, Σ-models provide concrete data representations for the types in Σ and concrete operators for the operator symbols in Σ. Condition 3. introduces the mechanism through which sentences can refer to models: a model may or may not satisfy a given sentence; then a set of sentences determines the class of all models that satisfy all the given sentences, i.e., that meet the given requirements. Condition 4. introduces “signature maps,” which allow you to change notation. The composition of two signature maps describes the change in notation resulting from first applying one and then the other. It is intuitive that such a composition operation should be associative, and should have an “identity” map that indicates the substitution of each non-logical symbol in the signature for itself, i.e., no change in notation. Condition 5. introduces the renaming operation for sentences, and states two properties that intuition says it should satisfy. Condition 6. gives the corresponding operation and properties for renaming models; it is interesting to notice the reversal of direction in model renaming. Finally, condition 7. gives a property relating sentence and model renaming through satisfaction; this condition essentially says that truth is invariant under changes of notation. More information about institutions can be found in [GB92]. 11 This paper seeks to avoid being distracted by foundational problems, but of course we wish to ensure that everything we do is sound. For this reason, when we encounter collections that may be too large to be sets, we will speak of classes, in the sense of G¨ odel-Bernays set theory.

C.2. FOUNDATIONAL CONCEPTS

155

The machinery of institutions can be used to define some important relationships between sentences: Definition 12 A Σ-sentence a is a semantic consequence of a set A of Σ-sentences, written A |=Σ a, iff c |=Σ a whenever c |=Σ A, i.e., iff every model that satisfies A also satisfies a; we may also say that A (semantically) entails a. Given sets A and A of Σ-sentences, we say that A (semantically) entails A (or that A is a semantic consequence of A) iff every model that satisfies A also satisfies A , i.e., iff c |=Σ A implies c |=Σ A for all Σ-models c. 2 For many logical systems, there is a natural notion of deduction on sentences, denoted , such that A a iff A |= a; this makes it easier to implement checks of satisfaction, although it must be noted that in general the satisfaction problem is undecidable. Every notion of signature of which we are aware is a tuple set in the sense of Definition 3; see Examples 5 and 10. Also, in each case signatures are closed under union, intersection and renaming. Moreover, every notion of signature map of which we are aware is a tuple set map in the sense of Definition 7. This motivates the following: Assumption 2 Each signature in our fixed institution is a tuple set in the sense of Definition 3, and the class of all signatures is closed under ∪, ∩ and ∗. Also, signature maps are exactly the corresponding tuple set maps. 2 It follows that inclusions Ψ ⊆ Σ correspond to signature inclusion maps i : Ψ → Σ. If h is a signature inclusion Ψ ⊆ Σ and c is a Σ-model, then we may write c|Ψ for c ∗ h, and call it the restriction or reduct of c to Ψ; this agrees with the standard terminology in first order logic, but in fact, we may use this terminology and notation even when h is not an inclusion. We now extend renaming from individual sentences and models to classes of sentences and classes of models, respectively: Definition 13 Let A be a class of Σ-sentences, let C be a class of Σ -models, and let h : Σ → Σ be a signature map. Then we define and

A ∗ h = {a ∗ h | a ∈ A} C ∗ h = {c ∗ h | c ∈ C } .

Note that C ∗ h is a class of Σ-models. 2

156

APPENDIX C. MODULE COMPOSITION SEMANTICS PAPER

It is interesting that we can give a very general notion of what it means for two signatures to be the same up to renaming of their symbols, based only on Definition 11: Definition 14 A signature map h : Σ → Σ is an isomorphism iff there is another signature map g : Σ → Σ such that h; g = 1Σ and g; h = 1ΣI . 2

C.3

Modules and Views The meek shall inherit the earth. – The Bible.

In this section, making use of the foundations provided in the previous section, we say what specification and implementation modules actually are, and we also define views, which relate modules of various kinds.

C.3.1

Specification Modules

No theory is good except on condition that one use it to go beyond. – Andre Gide, Journals, Aug. 5, 1931, translated by Justin O’Brien. Parameterized programming has two kinds of specification module: (1) theories and (2) packages. Both have signatures, which give the syntax for the module. Theories define properties of other modules, while packages specify what is to be implemented. A specification module is attached to each module name (in the module graph G), as a “header,” to (partially) describe the implementations (if any) attached to that name; it should at least include declarations for whatever is exported. Specifications are attached to the nodes of the module graph according to the following assumption; the actual form of specification modules is given just a little later in Definition 15. Assumption 3 For each module name M in the module graph, there is given a specification module Q(M ), with a tag saying whether it is a theory or a package12 . Both the signature and sentences of each module Q(M ) are from a given fixed institution, in the sense of Definition 11. 2 Note that different module names could refer to the same specification module; i.e., it is possible that Q(M ) = Q(M ) with M = M . Note also that specification modules are tuple sets, because they are 4-tuples having two sets and two tuple sets as components (see Definition 15 below). For convenience, we may write M when we really mean the specification module Q(M ) that is assigned to the module name M . Definition 15 A specification module is a tuple of the form (H, Σ, Ψ, A), where 1. 2. 3. 4.

H is a set of module names (for its imported modules), Σ is a partial signature (to declare its types, operators, etc.), Ψ is a sub-partial signature of Σ, called the visible signature13 (for its exported types, operators, etc.), and A is a set of (Σ ∪ |H|)-sentences (also called axioms), where  |Q(N )| , |H| = N ∈H

and where Σ ∪ |H| is required to be a signature.

We may call Σ the local signature of M to distinguish it from the visible signature Ψ of M , and we may call |M | the flat visible signature of M . Let us write H(M ) for the H component of M , Σ(M ) for its signature, Ψ(M ) for its visible signature, and A(M ) for its axioms. We call M atomic if H(M ) = ∅; note that14 in this case, |M | = Ψ(M ). Finally, we let

12 Technically, this means giving two functions from the node set N of G: one function (namely Q) to the class of specification modules, and the other, let us denote it t, to the set {th, pkg}. We will see later that we can reconstruct the function t from other data associated with G. 13 The symbols in Σ but not in Ψ are “local” or “private” types, operators, variables, etc. used in axioms to describe the semantics of operators, exceptions, etc.; they are not exported. This gives us an information hiding capability, in the sense of [Par72a]. 14 This is because of the convention that f (M ) = ∅ for any formula f , i.e., the result of an empty union is empty. M ∈∅

C.3. MODULES AND VIEWS

157

||(H, Σ, Ψ, A)|| = Σ ∪ |H| ,

and call it the working signature of the module; it contains all the non-logical symbols15 that can be used in the axioms of the module. Note that 4. above requires that ||M || be a signature. 2 It is convenient to say “the axioms of module M ” as shorthand for the A component of the tuple Q(M ) associated to the name M , i.e., A(Q(M )). Note that |M | includes all the visible symbols that M inherits from other modules. Also note that the two definitions of | | are recursive over the inheritance hierarchy, and co-recursive with each other. Finally note that the working signature includes only visible symbols from inherited modules, but includes hidden symbols from the signature of the current module16 . Definition 15 implies that inheritance is transitive, in the sense that if M3 imports M2 and M2 imports M1 , then everything visible in M1 is also imported into M3 . We do not require M1 to appear in the import set of M3 , only that M1 appear in the import set of M2 and M2 in that of M3 . (See Example 2.) Example 16 Given module names M1 , M2 , M3 with associated specification modules Q(Mi ) for i = 1, 2, 3, suppose that H(M1 ) = ∅, H(M2 ) = {M1 }, and H(M3 ) = {M2 }. Then |H1 | = ∅ |H2 | = Ψ1 |H3 | = Ψ1 ∪ Ψ2

2

|M1 | = Ψ1 |M2 | = Ψ1 ∪ Ψ2 |M3 | = Ψ1 ∪ Ψ2 ∪ Ψ3

||M1 || = Σ1 ||M2 || = Σ1 ∪ Ψ2 ||M3 || = Σ1 ∪ Ψ2 ∪ Ψ3

According to Definition 15, |M | = Ψ ∪ |H| can be a partial signature. For lileanna, this means, in particular, that some the interface to a module may have operations that involve types that are hidden (i.e., private). For example, a STACK module is likely to have its Stack type hidden, but its operations pop and push visible. This means that users of the module can pop and push all they like, but they cannot look directly at the results of these operations, which might, for example, be an ugly mass of pointers. In considering the semantics of specification modules, we often need to collect all the sentences that are visible consequences of the axioms in the module, which may involve non-exported symbols. This is done with the following: Definition 17 Given a signature Σ, a set A of Σ-sentences, and a subsignature Ψ of Σ, we let the Ψ-theory of A be defined by T hΨ (A) = {a ∈ Sen(Ψ) | A |=Σ a} . Given a module M = (H, Σ, Ψ, A), let T h(M ), called the theory of M , be defined by  T h(M ) = T h||M|| (A(M ) ∪ T h(N )) . N ∈H(M)

Similarly, we define the visible theory of M , denoted Vth(M ), by Vth(M ) = T h|M| (T h(M )) . 2 Note that T h(M ) is defined recursively over the inheritance hierarchy, so as to include all the consequences of all inherited axioms. Note also that T h(M ) = T hΣ (A) when M is atomic, by the convention about unions over the empty index set and the fact that ||M || = Σ. Similarly, Vth(M ) = T hΨ (A). Finally, note that Vth(M ) ⊆ T h(M ).

If M is the STACK module with type Stack hidden, then its equation pop(push(I,S)) = S 15 The

so called logical symbols are those that are used for combining the non-logical symbols; typical examples are ∨, ∧, ¬ and ∀. distinction between the visible (exported) operations and the hidden (internal or auxiliary) operations allows us to treat information hiding, but it also makes our development more complicated. 16 The

158

APPENDIX C. MODULE COMPOSITION SEMANTICS PAPER

does not have a visible sort, and so cannot be part of Vth(M ) (see Figure C.6). However, this equation does have a number of visible consequences, which are equations of visible sort, that would be part of Vth(M ), including the following: top(pop(push(I,S))) = top(S) top(pop(pop(push(I,S)))) = top(pop(S)). The following properties of T hΨ (A) will be useful later on: Lemma 18 Given sets A and A of Σ-sentences and a subsignature Ψ of Σ, then A ⊆ A implies T hΨ (A) ⊆ T hΨ (A ) , T hΨ (T hΨ (A)) = T hΨ (A) . Proof: For the first assertion, the operation T hΨ ( ) (which takes a set A of axioms to the set T hΨ (A) of axioms) its is monotone (i.e., inclusion preserving) because it is composed from monotone operations. To prove the second assertion, we have T hΨ (T hΨ (A)) ⊆ T hΨ (A) by the first assertion. To prove the opposite inclusion, let a ∈ T hΨ (A). We need to show that if c |= T hΨ (A) then c |= a for all Σ-models c. So we assume that for all b ∈ Sen(Ψ), ((∀d) d |= A implies d |= b) implies c |= b. Since Definition 17 gives us that c |= A implies c |= a for all Σ-models c, we conclude that c |= a. 2 However, it is not true that A ⊆ T hΨ (A) ,

because T hΨ (A) only contains Ψ-axioms. Also, it is not true that c |=Σ A iff c |=Σ T hΨ (A) . Proposition 19 If M is a specification module then |M | ⊆ ||M ||, and if H(M ) = {N }, then |N | ⊆ |M |. More generally, if H(M ) = H, then  |N | ⊆ |M | , (Law 1) N ∈H

and if17 N < M then |N | ⊆ |M |.

Proof: The first two inequalities follow from Definition 15. The first of the two more general inequalities follows from the definition of |M | as Ψ(M ) ∪ N ∈H |N |. The second follows from the first plus induction on the length of the path from N to M . 2 We now relate the inheritance sets of specification modules to the inheritance edges in the module graph G; then we describe some details of how module inheritance works in parameterized programming. Assumption 4 The function Q defined on (the set of module names in) the module graph G is such that there is an inheritance edge from M to M iff18 the name M appears in the import set H(M ) of the specification module Q(M ) for M . Also, it is forbidden to inherit a theory into a package. 2 Notation 20 If M is a module that inherits a set H of modules, then we may use the notation M H to emphasize the role of H; however, this superscript is not really needed, because H is already given in the associated specification module Q(M ). We may also write M N when the inheritance set for M is {N }, M N1 ,N2 when it is {N1 , N2 }, etc. 2

C.3. MODULES AND VIEWS

159

theory ANY TYPE is type Element; end ANY TYPE; theory MONOID is type Element; function ”∗” ( X, Y : Element) return Element; − − |axiom −−| for all X, Y, Z : Element: => −−| (X ∗ Y) ∗ Z = X ∗ (Y ∗ Z) ; −− associative property − − |axiom −−| for all X : Element:=> −−| exist I : Element: => −−| ( X ∗ I = X ) and ( I ∗ X = X ) ; −− identity property end MONOID; theory EQV is type Element; function Equal ( X, Y : Element ) return Boolean; − − |axiom −−| for all E1, E2, E3 : Element => −−| Equal ( E1, E1 ), −−| (Equal ( E1, E2 ) and −−| Equal ( E2, E3 ) − > Equal ( E1,E3 )), −−| (Equal ( E1, E2 ) − > Equal ( E2, E1 )); end EQV; Figure C.5: Some lileanna Theory Specifications

160

APPENDIX C. MODULE COMPOSITION SEMANTICS PAPER

Example 21 Figure C.5 shows three lileanna specification modules19 . The first is a theory called ANY TYPE; it has a single type Element, and nothing more; it can be satisfied by any module that has a type. The second theory, MONOID, contains a single type and one operation that takes two parameters of that type and returns a Boolean value. This theory also contains two axioms. The Anna “annotation” languages uses conventions, stylized Ada comments (following the symbol --|) to assert formal semantics of this lileanna specification module. An Ann annotation consists of variable declarations, quantifiers, and boolean expressions, whose values are asserted to be true using the symbol =>.” The first axiom asserts the associative law for the operation ∗, and the second asserts the identity law. The third theory, EQV, for equivalence relations, is similar to MONOID in having a single type and three assertions for its operation, which are reflexivity, symmetry, and transitivity, given by three Anna assertions, separated by commas; the last two assertions use Anna’s implication symbol “->.”

package INTSTACK is inherit INTEGER; type Stack; Stack_Empty : exception ; Stack_Overflow: exception ; function Is_Empty ( S : Stack ) return Boolean; function Is_Full ( S : Stack ) return Boolean; function Push ( I : Integer; S : Stack ) return Stack; − − | where −−| Is_Full ( S ) => raise Stack_Overflow; function Pop ( S : Stack ) return Stack; − − | where −−| Is_Empty ( S ) => raise Stack_Empty; function Top ( S : Stack ) return Integer; − − | where −−| Is_Empty ( S ) => raise Stack_Empty; − − |axiom −−| for all I : Integer; S : Stack => −−| not Is_Full ( S ) − > −−| (Pop ( Push ( I, S ) ) = S) and −−| (Top ( Push ( I, S ) ) = I ); end INTSTACK; Figure C.6: lileanna Bounded Integer Stack Package Specifications

Figure C.6 shows a package specification called INTSTACK for a bounded stack of integers; it has some relevant exceptions, and it inherits the module INTEGER, which defines the integers with appropriate operations (i.e., H(INTSTACK) = {INTEGER}). The axioms describe how the operations change the state of objects of type Stack. The exception annotations on the operations Push, Pop and Top specify when these exceptions are raised. 17 Recall 18 As

that this means that there is a path from N to M in the inheritance graph. a result of this assumption, we do not really need the inheritance subgraph H, because it can be reconstructed from data given

by Q. 19 We adopt the convention that module names have all capital letters, and appear in the text in typewriter font. Type and operation names will also appear in this font in the text.

C.3. MODULES AND VIEWS

161

Theory modules are not intended to be implemented, but instead are used to define interfaces and declare properties. For example, the theory MONOID an the appropriate interface for iterators over certain data structures (see [Gog89] for details). On the other hand, package modules like INTSTACK are intended to be implemented, and this may be done in a variety of ways. The module INTSTACK also illustrates the use of partial specifications, because no axioms are given for Is Full. The package INTSTACK illustrates three of Anna’s formal specification mechanisms: subprogram annotations, exception annotations, and axioms. Anna also has object annotations, type and subtype annotations, propagation annotations, context annotations, “virtual functions,” and package states in axioms. The goal of this paper gives a semantics for module composition, taking advantage of the fact that semantics have already been given for Anna and Ada; [LvH85] gives a good overview of Anna with examples. 2 The following law says that all the exported signature symbols and all the visible consequences of axioms from inherited modules are available to an importing module: Proposition 22 If M N is a specification module with H(M ) = {N }, then Vth(N ) ⊆ Vth(M N ). More generally, if H(M ) = H, then  (Law 2) Vth(N ) ⊆ Vth(M H ) , N ∈H

and if N < M then Vth(N ) ⊆ Vth(M ). The same results hold for T h(M ).

Proof: We prove the general case using Definition 17. For each N ∈ H we have  Vth(N )) , T h|M| (A(N )) ⊆ T h|M | (A(M ) ∪ because

N ∈H(M)

T h|M| (A(N )) ⊆ T h|M | (Vth(N )) , because T h|M| (A(N )) ⊆ Vth(N ) .

The final assertion now follows by induction on the length of the path from N to M . We omit the proofs for T h(M ); they are similar to those above. 2 Example 23 As in Example 16, assume module names M1 , M2 , M3 with associated specification modules Q(Mi ) for i = 1, 2, 3, such that H(M1 ) = ∅, H(M2 ) = {M1 }, and H(M3 ) = {M2 }. Also assume that Σ(M1 ) = {[a, 0], [b, 0], [c, 0]} Ψ(M1 ) = {[a, 0], [c, 0]} A(M1 ) = {a = b, b = c} Σ(M2 ) = {[f, 1]} Ψ(M2 ) = ∅ A(M2 ) = ∅ Σ(M3 ) = {[g, 1], [h, 1]} Ψ(M3 ) = {[g, 1]} A(M3 ) = {g(x) = h(x)} . Then

2

Vth(M1 ) = {a = b, b = c, a = c, a = a, b = b, c = c, b = a, c = b, c = a} Vth(M2 ) = Vth(M1 ) Vth(M3 ) = Vth(M1 ) ∪ {k(l) = k (r) | k, k ∈ {g, h}, l = r ∈ A(M1 )} .

Overload Resolution We will assume below that each symbol has a “true name” that is tagged with the name of the module where it is declared. Because it is awkward to use such names in practice, we want a parser that can recover the true name from a “nick name” that omits the tag; the process of finding the closest true name corresponds to what is called “dynamic binding” in the object oriented community.

162

APPENDIX C. MODULE COMPOSITION SEMANTICS PAPER

Notation 24 Because signatures are tuple sets, the required tagging can be accomplished by replacing each symbol σ by the pair [σ, M ], where M is the specification module involved; we will write this more simply as σ M . 2 Forming a tuple set of such pairs can be seen as a new operation on tuple sets. But first we need an auxiliary notion: Definition 25 Given a signature map h : Ψ → Ω with Ψ ⊆ Σ such that Ω is disjoint from Σ − Ψ, we can extend h to a map h : Σ → Ω = Ω ∪ (Σ − Ψ) by defining h = h ∪ (1Σ−Ψ ). 2 We do not always use the notation h explicitly, but instead just write h. This convention allows us to write a ∗ h when a is a Σ-sentence with h : Ψ → Ω and Ψ ⊆ Σ, and to write A ∗ h when A is a set of Σ-sentences. Definition 26 If T is a tuple set and M is a symbol, let T M denote the tuple set having the same form as T with each symbol σ in a leaf node being replaced by the pair [σ, M ]. If Q = (H, Σ, Ψ, A) is a module, then let Q M = (H, Σ M, Ψ M, A ∗ rΣ,M ) ,

where rΣ,M is a signature map from Σ to Σ M having the same shape as Σ, with each symbol σ in a leaf node set replaced by [σ, [σ, M ]], and the overbar indicates its extension to ||M ||. 2 Example 27 If the signature Σ of a module M has symbols [0, 0], [−, 1], [+, 2], etc., then the tagged forms of these symbols in the tuple set Σ M , are [0, 0] M, [−, 1] M, [+, 2] M , etc., or in the long form, [[0, 0], M ], [[−, 1], M ], [[+, 2], M ], etc. 2 Assumption 5 The symbols in the local and visible signatures of each specification module are tagged with the module name. If we let Q0 (M ) = (H, Σ, Ψ, A) denote the untagged form of the module associated to the name M , then Q(M ) = Q0 (M ) M . 2 In particular, if Σ and Σ are the local signatures of modules with distinct names M and M , respectively, then Σ and Σ are disjoint. Example 28 As in Example 16, assume module names M1 , M2 , M3 with associated specification modules Q(Mi ) for i = 1, 2, 3, such that H(M1 ) = ∅, H(M2 ) = {M1 }, and H(M3 ) = {M2 }. Also assume that Σ(M1 ) = {[a, 0], [b, 0], [c, 0]} Ψ(M1 ) = {[a, 0], [c, 0]} Σ(M2 ) = {[a, 0], [d, 0]} Ψ(M2 ) = {[d, 0]} Σ(M3 ) = {[c, 0], [d, 0]} Ψ(M3 ) = {[c, 0]} . Then the “tagged” forms of these signatures are as follows: Σ M1 = {[a, 0] M1 , [b, 0] M1 , [c, 0] M1 } Ψ M1 = {[a, 0] M1 , [c, 0] M1 } Σ M2 = {[a, 0] M2 , [d, 0] M2 } Ψ M2 = {[d, 0] M2 } Σ M3 = {[c, 0] M3 , [d, 0] M3 } Ψ M3 = {[c, 0] M3 } .

2

We want a parser that will allow us to use the symbols that appear in Q0 (M ) as shorthand for the full notation that appears in Q(M ), i.e., to use the simpler notation σ for a symbol σ M in Q(M ). This parser should disambiguate a symbol σ occurring in M according to the following rules: 1. if the symbol σ is declared in M , then σ is parsed as σ M ; 2. if σ is not declared in M but is declared in some modules inherited by M , then it is parsed as σ N where N is the unique least module20 inherited by M that declares σ, if there is one; 3. otherwise, a parse error is produced for σ in M . (In this case, the label σ cannot be disambiguated by the parser, and should have been explicitly written σ N for some module name N .) Example 29 Assume modules M1 , M2 , M3 as in Example 28 above. Then the overload resolution mechanism would give the results shown in Figure C.7 below. 2 20 In

the sense that the path from N to M is shortest in H having this property.

C.3. MODULES AND VIEWS

163

Module M1

M2

M3

Symbol [a, 0] [b, 0] [c, 0] [a, 0] [b, 0] [c, 0] [d, 0] [a, 0] [b, 0] [c, 0] [d, 0]

Overload Resolution Result [a, 0] M1 [b, 0] M1 [c, 0] M1 [a, 0] M2 parse error [c, 0] M1 [d, 0] M2 [a, 0] M1 parse error [c, 0] M3 [d, 0] M3

Figure C.7: Results of Overload Resolution

C.3.2

Implementation Modules

We are now ready to say what is an implementation for a given package specification module M . Note that we do not need to implement what is hidden inside of M . In fact, we take an implementation of a module to be a model of what it exports as well as everything it inherits. This provides a precise correctness criterion for code while avoiding many complications. Definition 30 An implementation for a specification module M = (H, Σ, Ψ, A) is an |M |-model C satisfying Vth(M ); in this case we say that C satisfies M , we write C |= M , and we may call C an M -module. Let [[M ]] denote the class of all implementations for M , called the denotation of M . 2 It is possible for C to implement M using hidden operations that are completely different from those in Σ − Ψ. We do not include such hidden operations in the signature of C, but our institution may be such that this information is already in C, e.g., if models consist of Ada code. We now get the following, which just says that if the visible theories of two specifications are the same, then they have the same denotation: Proposition 31 Given specification modules M and M , then |M | = |M | and Vth(M ) = Vth(M ) imply [[M ]] = [[M ]]. 2 Proposition 32 Let M be a package specification module and let C be an implementation module for M . Then C |= M implies C||N| |= N for each N < M in the module inheritance graph H. Proof: This is because Vth(N ) ⊆ Vth(M ) by Law 2 (Proposition 22). 2

Thus, we can require compatibility with a prior implementation CN of an inherited module N by imposing the condition that C|N = CN .

At a given point in a software development project, there may be zero or more implementation modules associated to a given module name M . These fit into the module graph structure that we are developing as follows: Assumption 6 There is a partial function I from module names to lists of implementation modules, defined on the names of package specification modules, and not on those of theory specification modules21 . (Note that I(M ) may be the empty list.) Each implementation module in I(M ) defines an implementation for Q(M ), for each module name M in the module graph G. 2 21 This means that we do not really need the tagging function t, because we can determine the kind of specification module by looking at the domain of definition of I.

164

APPENDIX C. MODULE COMPOSITION SEMANTICS PAPER

Figure C.8 may help the reader to visualize some of the structure that is associated with module names in the module graph; recall that the function I is partial, and that the function t can be eliminated. (We will add more to this structure as the paper progresses.) #

à M

² ±

´ +´ Q(M )

´

Q´ ´ ´ ¯ °

Q! Q

´" t

? ² th / pkg ±

Q I Q

¯ °

Q Q Qs ² C1 , C2 , ..., Cn ±

¯ °

Figure C.8: Attributes of a Module Name

Now that we have defined specification and implementations, we can begin to develop the operations for composing modules, and to prove some of their properties.

C.3.3

Views

Vision is the art of seeing things invisible. – Jonathan Swift, Thoughts on Various Subjects (1711). A view in lileanna is a signature map used to assert that some theory is satisfied by some other specification module; more technically, a view binds the “formal” symbols in the signature of the source theory to “actual” symbols in the target module, which may be either a theory or a package, in such a way that the proof obligations arising from the signature map are satisfied. Definition 33 below makes this precise, using the notation of Definition 25. Definition 33 A view from a specification module M = (H, Σ, Ψ, A) to a specification module M = (H , Σ , Ψ , A ) is a signature map h : |M | → |M | such that Ψ ∗ h ⊆ |M | and T h(M ) |=||M I || a ∗ h for each a ∈ Vth(M ), i.e., such that T h(M ) |=||M I || Vth(M ) ∗ h. In this case, we may write h : M → M . We may also call a view a specification module map. 2 In practice, we often have H ⊆ H , so that a view h : M → M can be given by just a signature map h : Ψ → Ψ , which then extends to a map h : Ψ∪|H| → Ψ ∪|H |, provided Ψ is disjoint from |H|. In this case, the proof obligations are just that T h(M ) |=||M I || a ∗ h for each a ∈ T h(A), i.e., that the translations of the visible consequences of axioms in A hold for any model of T h(M ). This means that some axioms in M , namely those involving “hidden” types and/or operations, need only “appear” to be consequences of the axioms in M ; i.e., we are dealing with what is called “behavioral satisfaction,” as is appropriate to the “black box” notion of module used in this paper. Techniques for proving this kind of satisfaction have been developed in hidden sorted algebra; see [GM96b, GM96c, GD94]. For example, in the example of a STACK module with type Stack hidden, the equation pop(push(I,S)) = S is not satisfied by the standard implementation using an array and a pointer, but all of its visible consequences are. So a view from M as this STACK specification module to another more concrete specification module M for stacks implemented with an array and a pointer would satisfy the condition of Definition 33, since everything in Vth(M ) is satisfied by M . Example 34 The lileanna code in Figure C.9 gives the theory of partially ordered sets; the axioms say that the operation “ Natural ); −− no ops map - take the default end NAT_DEF; Figure C.10: Views of Natural Numbers as POSETs

Lemma 35 Given specification modules M = (H, Σ, Ψ, A) and M = (H , Σ , Ψ , A ), then a signature map h : |M | → |M | is a view h : M → M iff Vth(M ) ∗ h ⊆ Vth(M ). Proof: Suppose h is a view. Then Ψ ∗ h ⊆ |M | and Vth(M ) ∗ h ⊆ T h(M ). But Vth(M ) = T h|M I | (T h(M )) and Vth(M ) ∗ h ⊆ Sen(|M |), so that Vth(M ) ∗ h ⊆ Vth(M ).

For the converse, suppose that Vth(M ) ∗ h ⊆ Vth(M ). Then Vth(M ) ∗ h ⊆ T h(M ). Moreover, |M | ∗ h ⊆ |M | implies Ψ ∗ h ⊆ |M |. 2 Proposition 36 If M → M is a specification module inclusion, then it is a view. Proof: Suppose M ⊆ M where M = (H, Σ, Ψ, A) and M = (H , Σ , Ψ , A ). Then we have H ⊆ H , Σ ⊆ Σ , Ψ ⊆ Ψ , and A ⊆ A , and thus |M | ⊆ |M |. Let h : |M | → |M | denote the inclusion. Because Vth(M ) |=|M| a for

166

APPENDIX C. MODULE COMPOSITION SEMANTICS PAPER

each a ∈ Vth(M ) and Vth(M ) ⊆ Vth(M ), we have Vth(M ) |=|M I | a ∗ h, because a ∗ h = a. 2 Proposition 37 If h : M → M and h : M → M are views, then so is their composition, h; h : M → M . Proof: Suppose M = (H, Σ, Ψ, A), M = (H , Σ , Ψ , A ), and M = (H , Σ , Ψ , A ). Then h : |M | → |M | and h : |M | → |M | are such that Vth(M ) ∗ h

Vth(M ) ∗ h

⊆ Vth(M ) ,

⊆ Vth(M ) .

Therefore Vth(M ) ∗ h; h

= (Vth(M ) ∗ h) ∗ h ⊆ Vth(M ) ∗ h ⊆ Vth(M ) ,

by using Lemma 35 repeatedly. 2 Languages that support the parameterized programming paradigm of this paper can declare views; thus, views are “first class citizens,” on the same level as modules. Because views are relationships between modules, it makes sense that they should be recorded as edges in the module graph, between the modules that they relate. This is formalized as follows: Assumption 7 Each non-inheritance edge e : M → M in the module graph G is labeled with a view denoted Q(e) from Q(M ) to Q(M ), where Q(M ) is a theory. We may denote this view by just e. When the edge e : M → M is an inheritance edge (i.e., in case M ∈ H(M )), we let Q(e), or just e, denote the inclusion view Q(M ) → Q(M ), which is given by the inclusion signature map |M | → |M |. 2 Default Views When the user feels there is an obvious view to use, it is annoying to have to write out that view in full detail. Default views capture the intuitive notion of “the obvious view” and often allow omitting part, or even all, of the details of a view. This subsection follows [Gog89], which should be consulted for further information. We first give some “abbreviating rules,” for simplifying views; these rules have corresponding inverses for reconstructing the intended view from the abbreviation. Given a view v : M → M , the rules for abbreviating the type map are: 1. Any pair of the form T to T can be omitted. 2. A pair T to T’ can be omitted if T is not shared between M and M and if T’ is the type in M having the same order as T has in M , where the order of a type in a module is defined by first defining the primary type of a module to be the first type introduced into its body (either by declaration or from an imported module), or if there isn’t one, its first parameter type, or if there isn’t even one of those, then Boolean. Then the secondary type of a module is the primary type of the module that remains after removing its primary type, and so on recursively. For example, any view from ANY TYPE (see Figure C.5) where the primary sort of the target module is the target of Element can be abbreviated to the empty view, and thus has a default view. There are also rules for omitting operation pairs from a view. The first of these is the following: 1. any pair of the form o to o can be omitted.

C.3. MODULES AND VIEWS

167

Again this abbreviation rule leads to a rule for reconstructing a view from the abbreviation; see Figure C.10 for an example. The second rule for abbreviating operation mappings is more complex and requires some preliminary definitions. Given an operation o in M , we say that an operation o in M subsumes o if the arity of o is (component-wise) < the arity of o (after translation by the type mapping), if the value type of o is < that of o (again after translation), and if o contains the attributes of o. Also, given o in M , let us call the those operations o available in M that have the same rank and attributes as o the cohorts of o. To define the order of an operation in a set O of operations available in a module M , we first define the primary operation in O relative to M by the following (ordered) rules; it is 1. the operation in O that is first (in textual order) introduced in M in an explicit declaration, if there is one, 2. otherwise, the primary operation in O relative to the first module expression explicitly imported into M , or if there is no operation from O there, then in the second imported module expression, and so on, 3. otherwise, the primary operation in O relative to the first parameter requirement theory of M , or if there is none there, then relative to the second requirement theory of M , and so on, 4. otherwise, the primary operation in O relative to BOOL, and finally 5. otherwise, there is no primary operation in O relative to M . The secondary operation in O relative to M is the primary operation in O − {o1} relative to M , where o1 is the primary operation in O relative to M ; the tertiary operation in O relative to M is defined similarly; and so on recursively, finally defining an order for each operation in O that is available in M . (Notice that the ordering of these rules differs from that of the rules defining order for types.) Then the second rule for abbreviating operation maps is: 2. a pair o to o’ can be omitted if o is not shared, if o’ subsumes o, and if o’ has the same order among the operations compatible with o as o has among its cohorts. (Some more rules are given in [Gog89].) A null view, where everything has been left out, is the extreme case of an abbreviated view; null views are also called default views, and the syntax ModuleName [ Mexp ] is used.

168

APPENDIX C. MODULE COMPOSITION SEMANTICS PAPER

Although these conventions work well for simple examples, it cannot be expected that they will always produce exactly the particular view that the user really wants. Nevertheless, these conventions are very effective in practice.

C.4

Horizontal Composition

This section considers system structure at a given “horizontal” layer of abstraction, i.e., the system parts and how they are put together. Vertical structure, which is how a system is composed of layers, each providing services to higher layers, is considered in Section C.5.

C.4.1

Renaming for Specifications

This subsection describes how a signature map can be used for renaming of specification modules. Definition 38 Given a specification module M = (H, Σ, Ψ, A) and given a signature map h : Ψ → Ψ , we define the renaming or translation of M by h, denoted M ∗ h, to be the specification module (H, Σ ∗ h, Ψ , A ∗ h), where the first h is 1Σ−Ψ ∪ h and the second h is 1(||M||)−Ψ ∪ h, recalling Definition 25 and that ||M || = Σ ∪ |H|. 2 We may also use the notation M ∗ h when Ψ(M ) = Ψ and h : Ψ → Ψ with Ψ ⊆ Ψ, by extending h to h, starting from Ψ instead of from Ψ. Similarly, we may use this notation when Ψ ⊆ Ψ by first restricting h to Ψ, i.e., by extending22 h♦ = 2Ψ ; h instead of extending h. A more general formal definition is given below. Definition 39 Given a signature map h : Σ → Ω and Ψ ⊆ Σ, we can restrict h to Ψ by defining h♦ = 2Ψ ; h. 2 As with the overbar notation of Definition 25, we often do not use the notation h♦ explicitly, but instead just write h. This convention allows us to write a ∗ h when a ∗ h for a a Ψ-sentence with h : Σ → Ω and Ψ ⊆ Σ.

Note that if h in Definition 38 is not injective, then some sorts and operations may be “munged” together. Then the resulting single operation will have to satisfy the union of (the translated versions of) all the axioms for the operations that were identified. This is a form of multiple inheritance that could be useful, e.g., if we want a single operation to accomplish a number side effects that were previously done by separate operations. We can now prove that the basic properties of renaming for signatures in condition 5. in Definition 11 (of institutions) extend to renaming of specification modules:

Proposition 40 Given specification module M = (H, Σ, Ψ, A) and signature maps h : Ψ → Ψ and h : Ψ → Ψ , then (Law 3) M ∗ 1Ψ = M , (Law 4) (M ∗ h) ∗ h = M ∗ (h; h ) .

Proof: For the first assertion, if h = 1Ψ then Σ = Σ and so the first h is 1Σ and the second is 1||M|| , so that (H, Σ, Ψ, A) ∗ h = (H, Σ, Ψ, A), using the first part of condition 5. in Definition 11. For the second assertion, ((H, Σ, Ψ, A) ∗ h) ∗ h =

(H, Σ ∗ h, Ψ , (A ∗ h)) ∗ h =

(H, (Σ ∗ h) ∗ h , Ψ , (A ∗ h) ∗ h ) =

(H, Σ ∗ (h; h ), Ψ , (A ∗ (h; h )) =

(H, Σ ∗ (h; h ), Ψ , A ∗ (h; h )) ,

where the next to last step uses the second part of condition 5. in Definition 11, and the last step depends on the identity 22 We

let 2Ψ denote the “square” relation, consisting of all pairs from Ψ.

C.4. HORIZONTAL COMPOSITION

169

Σ ∗ h; h = Σ ∗ h; h ,

which follows from the calculation (1Σ−Ψ ∪ h); (1(Σ−Ψ)∪ΨI ∪ h ) = (1Σ−Ψ ; 1(Σ−Ψ)∪ΨI ) ∪ h; h = 1Σ−Ψ ∪ h; h ,

because the other two terms in the expansion vanish (i.e., 1Σ−Ψ ; h and h; 1(Σ−Ψ)∪ΨI equal ∅) because of Assumption 5 about the names of symbols in modules, while 1Σ−Ψ ; 1(Σ−Ψ)∪ΨI is just 1Σ−Ψ . A similar calculation yields the other identity we need, namely, 2

A ∗ h; h = A ∗ h; h .

C.4.2

Hiding and Enriching

We now discuss two operations on modules (or systems, or subsystems) that were not implemented in Clear or OBJ, although similar ideas were proposed for LIL in [Gog85]. These operations allow deleting some of the visible interface of a module, and also adding to it. lileanna syntax for the first operation is M ∗ (hide Φ), and for the second is M ∗ (add Φ, A), where Φ is a partial signature and A is a set of axioms. We can give precise definitions for these operations in our set theoretic semantics as follows: Definition 41 Given a specification module M = (H, Σ, Ψ, A) and a partial subsignature Φ ⊆ Ψ, we define M ∗ (hide Φ) = (H, Σ, Ψ − Φ, A) , where it is required that ((Ψ − Φ) ∪ |H|) is a signature. In the special case where Ψ = Φ, we may write (hide-all) for (hide Φ). Given a partial subsignature Φ disjoint from ||M || and a set A of (Σ ∪ Φ ∪ |H|)-axioms, let M ∗ (add Φ, A ) = (H, Σ ∪ Φ, Ψ ∪ Φ, A ∪ A ) , where it is required that (Σ ∪ Φ ∪ |H|) and (Ψ ∪ Φ ∪ |H|) are signatures. 2

Note that H(M ∗ h) = H(M ).

Sometimes we want to know what axioms could have been translated to produce some of a given set of axioms. The following construction answers this question: Definition 42 Given h : Φ → Ψ and M = (H, Σ, Ψ, A), let −1

h−1 (M ) = (H, Φ, Φ, h

(Vth(M )))

where h : Φ ∪ |H| → Ψ ∪ |H| and −1

h

(Vth(M )) = {a ∈ Sen(Φ ∪ |H|) | a ∗ h ∈ Vth(M )} .

We call h−1 (M ) the inverse image module of M under h. 2 −1

Proposition 43 Given h : Φ → Ψ where M = (H, Σ, Ψ, A), then h : h

(M ) → M is a view.

Proof: Note that as a signature map, h : Φ ∪ |H| → Ψ ∪ |H|. Then h is a view, because −1

h

(Vth(M )) ∗ h = {a ∈ Sen(Φ ∪ |H|) | a ∗ h ∈ Vth(M )} ∗ h = {a ∗ h | a ∈ Sen(Φ ∪ |H|) and a ∗ h ∈ Vth(M )} ⊆ Vth(M ) .

170

APPENDIX C. MODULE COMPOSITION SEMANTICS PAPER

2 Proposition 44 If M = (H, Σ, Ψ, A) and C is a |M |-model, then C |= M ∗ (hide Φ) iff C |= i−1 (M ), where i is the inclusion of Ψ − Φ into Ψ. −1

Proof: From the definitions, C |= M ∗ (hide Φ) iff C |= T h(Ψ−Φ)∪|H| (M ), and C |= i−1 (M ) iff C |= i −1

where i is the inclusion (Ψ − Φ) ∪ |H| → Ψ ∪ |H|. But T h(Ψ−Φ)∪|H| (M ) = i

(Vth(M ))

(Vth(M )). 2

Thus, the specification modules M ∗ (hide Φ) and i−1 (M ) are in a sense equivalent.

C.4.3

Renaming and Transformation for Implementations

Users of a system like lileanna will in general be more interested in implementations than in specifications. Hence we should investigate transformations of implementations. We first discuss renaming. As with condition 6. in the definition of institution (Definition 11), renaming implementations moves in the opposite direction from that of the signature map involved, whereas for specifications it moves in the same direction. Thus, there is a duality between specifications and implementations, parallel to the duality between sentences and models, as reflected in the following: Proposition 45 If h : M → M is a view and C is an M -module, then C ∗h is an M -module. Let [[h]] : [[M ]] → [[M ]] denote the function that sends C to C ∗ h. Proof: Let M and M be (H, Σ, Ψ, A) and (H , Σ , Ψ , A ), respectively. Since h is a view, we have Vth(M ) ∗ h ⊆ Vth(M ), and we also have that C |=|M I | Vth(M ). Therefore C |=|M I | Vth(M ) ∗ h, and so by the satisfaction condition, C ∗ h |=|M| Vth(M ). 2 Copying and renaming of implementations is surprisingly powerful: it allows deleting functionality from an implementation, as well as adding new functionality that is defined in terms of what is already present; it also allows making one or more new copies of items exported by a module. We first consider the case of adding functionality. Definition 46 Given a specification module M = (H, Σ, Ψ, A), an implementation C for M , and a partial subsignature Φ ⊆ Ψ, let i denote the inclusion of Ψ − Φ into Ψ. Then C ∗ i is denoted C ∗ (hide Φ). 2 Example 47 Figure C.11 shows a module transformation using renaming, hiding, and enriching: the package RECTANGLE is inherited, extended with the operation Side, with its type Rectangle renamed to Square, and its operations Length and Width hidden (but not removed!); an axiom has also been added. (This is based on an example in [Mey88].) 2 More generally, if h : M → M is not surjective23 , then implementations for symbols in the visible signature of M that are not the image of any symbol in the visible signature of M are dropped (or “sliced”) from C when C ∗ h is formed. Proposition 48 Given a specification module M = (H, Σ, Ψ, A) and a |M |-model C, then C |= M implies C ∗ (hide Φ) |= M ∗ (hide Φ). Proof: Let i denote the inclusion of Ψ − Φ into Ψ. Then i : i−1 (M ) → M is a view by Proposition 43, and so C ∗ i satisfies i−1 (M ). Therefore C ∗ i |= M ∗ (hide Φ) by Proposition 44. But C ∗ i is C ∗ (hide Φ). 2 23 I.e.,

there may be symbols p such that there is no symbol p such that p = h(p ).

C.4. HORIZONTAL COMPOSITION

171

package SQUARE is inherit RECTANGLE ∗ (rename Rectangle => Square) ∗ (add function Side ( S : Square ) return Real) ∗ (hide Width, Length); − − | axiom −−| for all S : Square => −−| Length ( S ) = Width ( S ); end; Figure C.11: Specifying a SQUARE Package as a Transformation of RECTANGLE

Rather than give a construction for adding functionality to an implementation, we give a correctness criterion that should be satisfied by any proposed construction. The reason for this indirect approach is that many different constructions are possible, depending on the language involved, on the compiler technology used for that language, and even on the amount of effort that the implementor wants to undertake. Definition 49 Given a specification module M = (H, Σ, Ψ, A), an implementation C of M , a partial subsignature Φ disjoint from ||M ||, and a set A of (Σ ∪ Φ ∪ |H|)-axioms, then we write C = C ∗ (add Φ, A ) iff C |= M ∗ (add Φ, A ). 2 Note that C is not unique. More generally, whenever h is not injective24 , C ∗ h will have one or more extra copies of some items implemented by C . Example 50 Figure C.12 shows the package SINGLE_LINKED_LIST enriched to DOUBLE_LINKED_LIST by adding the operation Previous. Some users might prefer to include Ada code directly in the module expression, but the current implementation of lileanna requires placing it in a separate file, which is translated to intermediate representation (DIANA), and then manipulated as specified by the module composition operations. This two step process simplifies parsing module expressions and keeps lileanna grammar focused on specification level composition, while still supporting implementation level composition. 2 A further generalization extends the notion of view so as to map simple symbols in the signature of M to expressions formed from the symbols in M . Such maps are called derivors in the algebraic literature25 . This means that the signature of M can introduce new operations for C ∗ h that are defined in terms of old ones in C .

The operations for renaming, hiding and adding to implementations are very useful as part of a module connection language, because existing modules often do not do exactly what is wanted. Another useful operation on implementations has an apparently more ad hoc character, simply adding to the signature and code. For example, to add a new enumeration literal to an enumerated type, the lileanna syntax is: C ∗ (add literal to TypeName , Literal ). We have been able to prove the satisfaction condition for implementation modules with respect to specification modules, but this paper is not the place for such a technical result.

C.4.4

Aggregation of Specifications

We must hang together, or assuredly we shall all hang separately. – Benjamin Franklin, July 4, 1776. 24 I.e., 25 A

we can have h(p) = h(p ) for some symbols p = p . simpler approach is to define a derivor as an ordinary view to an extension of the theory of the target module.

172

APPENDIX C. MODULE COMPOSITION SEMANTICS PAPER

package DOUBLE_LINKED_LIST is inherit SINGLE_LINKED_LIST ∗ (rename SLList => DLList) ∗ (add function Previous ( N : Node; L : DLList ) return Node − − | axiom Next ( Previous ( N, L ) ) = N ; − − | raise Invalid_Node => NOT_IN ( N, L ); − − | raise Invalid_Node => HEAD ( L ); −− code found in separate package due to −− limitations on current implementation. ) end; package MAKELIL is −− By convention, the code to be added is found in this −− separately parsed Ada package/DIANA AST. function Previous ( N : Node; L : DLList ) return Node is begin if Current.Value = N.Value and Current.Next = N.Next then raise Invalid_Node; −− At Head end if; while Current.Next /= null loop if Current.Value = N.Value and Current.Next = N.Next then return Last; −− found else −− move down list Last := Current.Next; Current := Last.Next; end if; end loop; −− drop through if not found raise Invalid_Node; end; end MAKELIL; Figure C.12: Transforming a single linked list package

C.4. HORIZONTAL COMPOSITION

173

Module aggregation is the flat combination of modules. An important issue is sharing common inherited modules; to understand how sharing works under aggregation, recall that all symbols introduced in a module are tagged with the name of that module. By convention, modules that are inherited are always shared : If A and B both inherit M , then the resulting value of the module expression A + B (the aggregation of modules A and B) only has one copy of M ; this is consistent with the way that Ada handles imports. Here is the formalization: Definition 51 If M = (H, Σ, Ψ, A) and M = (H , Σ , Ψ , A ) are specification modules, then the aggregation or sum of M and M is M + M = (H ∪ H , Σ ∪ Σ , Ψ ∪ Ψ , A ∪ A ) . For convenience, we may also use the notation M ∪ M in some formulae. More generally, given specification modules Mi = (Hi , Σi , Ψi , Ai ) for i = 1, ..., n, let n 3

Mi = (

i=1

n 

Hi ,

i=1

We may also use the

n 

Σi ,

i=1

n 

Ψi ,

i=1 notation ni=1

n 

Ai ) .

i=1

Mi .

Notice that there are natural views Mj → M = 2

n

i=1

Mi , which are just inclusions; we denote these Jj for j = 1, ..., n.

Notice that operators with the same name (say o) introduced in different modules are automatically “named apart” in the aggregation (i.e., they are σ M and σ M , because of Assumption 5 about the names of symbols in modules.) Aggregation satisfies some useful properties, which are given below as laws. Example 52 Assume module names M1 , M2 , M3 , M4 with associated specification modules Q(Mi ) for i = 1, 2, 3, 4, such that H(M1 ) = {M2 }, H(M2 ) = {M3 }, H(M3 ) = ∅, and H(M4 ) = {M3 }. The module graph is shown in Figure C.13. #

inherits #

à M3

"

¡ !

¡

¡

¡

¡

¡

à M4

¡¡µ "

!

#

Ã

inherits -

# inherits

M2 "

!

-

M1 "

Figure C.13: Module Graph for Example 52

Let us also assume that Σ(M4 ) = {[e, 0], [f, 0], [c, 0]} Ψ(M4 ) = {[e, 0], [c, 0]} Σ(M3 ) = {[a, 0], [b, 0], [c, 0]} Ψ(M3 ) = {[a, 0], [c, 0]} Σ(M2 ) = {[a, 0], [d, 0]} Ψ(M2 ) = {[d, 0]} Σ(M1 ) = {[c, 0], [d, 0]} Ψ(M1 ) = {[c, 0]} . These signatures would be “tagged” as follows: Σ Σ Σ Σ

M4 M3 M2 M1

= {[e, 0] M4 , [f, 0] M4 , [c, 0] M4 } = {[a, 0] M3 , [b, 0] M3 , [c, 0] M3 } = {[a, 0] M2 , [d, 0] M2 } = {[c, 0] M1 , [d, 0] M1 }

Ψ Ψ Ψ Ψ

M4 M3 M2 M1

Ã

= {[e, 0] M4 , [c, 0] M4 } = {[a, 0] M3 , [c, 0] M3 } = {[d, 0] M2 } = {[c, 0] M1 } .

!

174

APPENDIX C. MODULE COMPOSITION SEMANTICS PAPER

Hence the result of the aggregation M1 + M4 would have H(M1 ∪ M4 ) = H(M1 ) ∪ H(M4 ) = {M2 , M3 } , Σ(M1 ∪ M4 ) = {[c, 0] M1 , [d, 0] M1 , [e, 0] M4 , [f, 0] M4 , [c, 0] M4 } , Ψ(M1 ∪ M4 ) = {[c, 0] M1 , [e, 0] M4 , [c, 0] M4 } .

Let us further assume that A1 = {c = d} , A2 = {a = d} , A3 = ∅, A4 = {e = f } . Then A(M1 ∪ M4 ) = {c M1 = d M1 , a M2 = d M2 , e M4 = f M4 } .

(Note that this example is about aliasing, since all the operations are constants.) 2 Proposition 53 Aggregation is associative, commutative and idempotent, i.e., (Law 5) M + M = M + M , (Law 6) M + (M + M ) = (M + M ) + M , (Law 7) M + M = M , (Law 8) M ⊆ M + N ,

(Law 9) N ⊆ M implies M + N = M . Proof: These follow from the corresponding properties for unions of tuple sets. 2 The idempotent (Law 7) law depends on the fact that the two modules not only have the same content, but also the same name. If Q(M ) = Q(M ) with M = M , then M + M = M does not hold. The main equality about inheritance follows directly from the definition: Proposition 54 Given specification modules Mi for i = 1, ..., n, then n n 3  Mi ) = H(Mi ) . H( i=1

2

i=1

From this we can easily prove results like the following: Proposition 55 Given specification modules M1 and M2 and a set H of module names, (Law 10) M H + M

H

= (M + M )H ,

(Law 11) M N1 ,N2 = M N2 ,N1 = M N1 +N2 , H

(Law 12) M N1

, N2H

H

= M (N1 +N2 )

,

(Law 13) M N + N = M N . Proof: For the first assertion, H(M + M ) = H(M ) + H(M ) = H ∪ H = H . For the second assertion, we have that H(M N1 ,N2 ) = H(M N2 ,N1 ) = H(N1 ) ∪ H(N2 ) , and also that

C.4. HORIZONTAL COMPOSITION

175

H(N1 + N2 ) = H(N1 ) ∪ H(N2 ) . The third equality now follows from the first two, and the last assertion follows from the last two Laws of Proposition 53. 2 The following formulae can be useful in proving other laws: Lemma 56 Let M and M be specification modules. Then T h(M + M ) = T h(M ) ∪ T h(M ) , |M + M | = |M | ∪ |M | , ||M + M || = ||M || ∪ ||M || . 2 However, it is not true that [[M + M ]] = [[M ]] ∪ [[M ]] . We have the following distributive law for specifications: Proposition 57 Given specification modules M1 , M2 and a signature map h : Ψ1 + Ψ2 → Ψ, then (Law 14) (M1 + M2 ) ∗ h = (M1 ∗ h) + (M2 ∗ h) . Proof: Note that Mi ∗ h requires restricting h to Ψi , i.e., using h♦ in the sense of the paragraph after Definition 38; after that, it must be extended to Σi , i.e., h♦ must be used. We therefore calculate as follows:

2

(M1 + M2 ) ∗ h = (M1 + M2 ) ∗ h = (M1 ∪ M2 ) ∗ (1(||M1 ||+||M2 ||)−(Ψ1 +Ψ2 ) ∪ h) = M1 ∗ (2||M1 || ; (1(||M1 ||+||M2 ||)−(Ψ1 +Ψ2 ) ∪ h)) ∪ M2 ∗ (2||M2 || ; (1(||M1 ||+||M2 ||)−(Ψ1 +Ψ2 ) ∪ h)) = M1 ∗ (2||M1 || ; 1(||M1 ||+||M2 ||)−(Ψ1 +Ψ2 ) ∪ 2||M1 || ; h) ∪ M2 ∗ (2||M2 || ; 1(||M1 ||+||M2 ||)−(Ψ1 +Ψ2 ) ∪ 2||M2 || ; h) = M1 ∗ (1||M1 ||−Ψ1 ∪ 2Ψ1 ; h) ∪ M2 ∗ (1||M2 ||−Ψ2 ∪ 2Ψ2 ; h) = M1 ∗ h + M2 ∗ h .

Sometimes we need to aggregate two copies of the same module. This can be accomplished with renaming, but it is useful to have a special notation for the construction. Definition 58 Let Mi be specification modules for i = 1, ..., n. Then we define n 7 i=1

Mi =

n 

Mi i .

i=1

and we call this operation separated aggregation. When n = 2 we will use the notation M1 ⊕ M2 . 2 Note that symbols from imported modules are not renamed in the right side of Definition 58, so that any shared submodules remain shared in the separated sum. (Strictly speaking, only overloaded symbols need to be renamed, but it would be cumbersome to write a formula taking account of such a more refined notion.) Later on, we shall need the following definition and lemma:

176

APPENDIX C. MODULE COMPOSITION SEMANTICS PAPER

Definition 59 Given specification module maps (i.e., views) Pi : Ti → M for i = 1, 2, define P = P1 !P2 : T1 ⊕ T2 → = Pi (σ) for i = 1, 2. Similarly, given specification module maps Pi : Ti → M for i = 1, ..., n, define M by P (σ i)  n P = ni=1 Pi : i=1 Ti → M by P (σ i) = Pi (σ) for i = 1, ..., n.

!

Similarly, given specification module maps h1 : T1 → N1 and h2 : T2 → N2 , define h = (h1 ⊕ h2 ) : (T1 ⊕ T2 ) → (N1 ⊕ n n N ) by h(σ i) = h (σ) for i = 1, 2. More generally, given h : T → N for i = 1, ..., n, define h = h : i i i i i=1 i i=1 Ti → 2n N by h(σ i) = h (σ) for i = 1, ..., n. 2 i i=1 i Lemma 60 In the above definition, P is a specification module map. Furthermore, if each Pi is injective and if the images of each Ti in M are disjoint, then P is injective. 2

In some module composition laws, the two module expressions do not have the same signature, but instead define specifications that are isomorphic under a renaming of symbols. We make this precise as follows: Definition 61 Let M = (H, Σ, Ψ, A) and M = (H , Σ , Ψ , A ) be specifications. Then we write M ≈ M iff H = H and there is a signature isomorphism h : ||M || → ||M || such that Σ ∗ h = Σ , and Ψ ∗ h = Ψ , and A ∗ h = A . 2 Note that this definition requires that the hidden parts of the two modules are isomorphic. Proposition 62 Separated aggregation is commutative and associative, i.e., (Law 15) M ⊕ M ≈ M ⊕ M , (Law 16) M ⊕ (M ⊕ M ) ≈ (M ⊕ M ) ⊕ M .

Proof: For the first law, let Σ12 = Σ1 1+Σ2 2, let Σ21 = Σ2 1+Σ1 2, and let h : Σ12 → Σ21 be defined to send each symbol f i to the symbol f i, where 1 = 2 and 2 = 1. Then h defines an isomorphism h : ||M ⊕ M || → ||M ⊕ M ||, with inverse g : Σ21 → Σ12 sending f i to f i, because i = i for i = 1, 2. The proof of the second assertion is similar. 2

C.4.5

Aggregation of Implementations

Nothing is good or bad but by comparison.

– Thomas Fuller, Gnomolagia.

Rather than give a construction for the aggregation of implementations, we again take the indirect approach of giving a correctness criterion that any construction for this aggregation should satisfy. But first, we need an auxiliary concept: Definition 63 Given an implementation C satisfying a specification M = (H, Σ, Ψ, A), let the complete specification or theory of C be T h(M, C) = (H, Ψ, Ψ, T hΨ (C)) , where T hΨ (C) = {a ∈ Sen(Ψ) | C |= a} . 2 Thus, we are forming a theory of C in which the main signature is Ψ and everything is considered visible under Ψ. Definition 64 Given package specification modules Mi = (Hi , Σi , Ψi , Ai ) and implementation n Ci with Ci |= Mi for i = 1, ..., n, then an implementation C is an aggregation or sum of the C iff it satisfies i i=1 T h(Mi , Ci ), and in  this case we may write C = ni=1 Ci (noting that C is not in general unique). 2 n (Actually, the model corresponding to the sum of specifications Mi is the product i=1 ci of the component models, but we will use the sum notation anyway.)

In lileanna, where diana trees are manipulated to construct implementations, the aggregation C = C1 + C2 is built in several steps. First, a new (null) Ada package is created and called C. Then a copy of the diana tree for C1 is created (fully qualified to avoid potential name conflicts) and inserted into C. Then a fully qualified copy of the diana tree for C2 is created and inserted into C. The procedure of aggregating n implementation modules is similar.

C.4. HORIZONTAL COMPOSITION

C.4.6

177

Parameterization and Instantiation

One effective way to increase the reusability of software modules is to parameterize them. The form of parameterization described in this paper is much more powerful than what is found in most programming languages today because, following the functional programming analogy given in the introduction, (multiple) other modules are allowed as parameters. Definition 65 A (horizontally) parameterized or generic specification module is an injective specification module map P : T → M , where T is a theory. We may use the notation M [P :: T ], and say that M is parameterized by P , that T is the interface theory, and M is the body. We may write M H [P :: T ] when H(M ) = H. 2 The notation M [P :: T ] identifies the “formal parameter” P with the injection T → M ; we discuss how “actual parameters” are handled in Definition 68 below. The following shows that we can always rename some symbols in M so that the injection becomes an inclusion: Lemma 66 If P : T → M is an injective specification module map, then there is an inclusion of T into M ∗ P ♦ 2

If it is convenient, we can assume that this renaming has already been done, so that P itself is an inclusion. Now we enrich our notion of module graph with information showing how parameterization is represented:

Assumption 8 If P : T → M is a generic module, then the injection P appears as an edge in the module graph from T to M , labeled to indicate that it is a generic module. 2 It is worth noting that a given module M could be parameterized in more than one way, and that, at least in theory, a given module M can be parameterized in new way just by defining a new view to it; however, in practice, this might require considerable reworking of the external syntax and the internal representations. Example 67 The interface theory of the generic STACK module in Figure C.14 is ANY TYPE, which means that it is parameterized by a single type Element. Item :: ANY TYPE => STACK denotes the inclusion. Hence the declaration “type Element” is part of STACK implicitly through this inclusion. Note that this specification is incomplete, since it does not say when the exception Is Full is true, and hence the exception Stack Overflow is raised. 2 A parameterized module can only realize its potential for reuse when its parameters are instantiated. “Fitting views” are used to apply generics to actual modules; they describe the binding of formal symbols in the signature of the interface theory to actual symbols in the signature of the actual module. In addition, they assert that the actual module satisfies the properties demanded by the interface theory. These properties are needed in order for the instantiation to behave as desired. (In Example 67, no such properties are required.) Definition 68 Given a generic specification module M [P :: T ] with body (H, Σ, Ψ, A) and given a view h : T → N , we let the instantiation of M by h be defined by the formula M [h :: N ] = M ∗ h♦ + N . The view h is called a fitting view. We may also use the notation M [h], or sometimes even M [N ] if h is clear from context. 2 Note that H(M [h]) = H(M ) ∪ H(N ); this says that shared modules are not duplicated. We will see later that when a generic module is instantiated, it is added to the module graph with a link to the generic that it came from. Example 69 If we instantiate the generic STACK module shown in Figure C.14, using the view Int View shown in Figure C.15, which sends the type Element to the type Integer, then we get the module INTSTACK shown in Figure C.6. Note that the type Integer is inherited as part of the module STANDARD, and hence is not explicitly declared. 2

178

APPENDIX C. MODULE COMPOSITION SEMANTICS PAPER

generic package STACK[Item :: ANY TYPE] is type Stack; Stack_Empty : exception ; Stack_Overflow: exception ; function Is_Empty ( S : Stack ) return Boolean; function Is_Full ( S : Stack ) return Boolean; function Push ( E : Element; S : Stack ) return Stack; − − | where −−| Is_Full ( S ) => raise Stack_Overflow; function Pop(S : Stack) return Stack; − − | where −−| Is_Empty ( S ) => raise Stack_Empty; function Top ( S : Stack ) return Element; − − | where −−| Is_Empty ( S ) => raise Stack_Empty; − − |axiom −−| for all E : Element; S : Stack => −−| not Is_Full ( S ) − > −−| ( Pop ( Push ( E,S ) ) = S ) and; −−| ( Top ( Push ( E,S ) ) = E ); end STACK; Figure C.14: lileanna Generic Specification for Bounded Stack

view INT_View :: ANY_TYPE => STANDARD is types ( Element => Integer ); end INT_View ; Figure C.15: A View of the Integers

C.4. HORIZONTAL COMPOSITION

#

179

Ã

ST AN DARD " ! 6 view to get INTEGER # ANY_TYPE "

à parameterizes !

# -

à instantiation via INT_View

ST ACK "

!

#

Ã

INT_STACK "

!

Figure C.16: Module Graph for Example 69

Definition 70 We use the notation M [P1 :: T1 , P2 :: T2 ] as an abbreviation for the module M [P :: T1 ⊕ T2 ] with the specification module map P = P1 !P2 : T1 ⊕ T2 → M ; we will only use this notation when the images of T1 and T2 are disjoint in M , so that (by Lemma 60) P is injective. n More generally, we let M [P1 :: T1 , ..., Pn :: Tn ] serve as an abbreviation for M [P :: T ] where T = i=1 Ti and P = n i=1 Pi , under the assumption that the images of the Ti are mutually disjoint in M , so that P is again injective. Now given fitting views hi : Ti → Ni for i = 1, ..., n, we get a fitting view h = ni=1 hi : T → N = ni=1 Ni , and we let the instantiation M [h1 , ..., hn ] be the module M [h :: T ], as in Definition 68. 2

!

!

Note that by Lemma 66, P can be taken as an inclusion if we so wish. Example 71 The generic module REDUNDANT SENSOR, shown in Figure C.17, is parameterized by three interface theories (i.e., ANY SENSOR TYPE (twice) and POLLING RATE). Each sensor can have its status set and queried, and each sensor is updated at some periodic rate. The status of the Sensor Set is determined by the Status function. Now we can use lileanna to produce generic Ada packages from partial instantiations of REDUNDANT SENSOR. Example 77 will show how High Rate View, in Figure C.19, can be used to generate a generic Ada package that can be configured to provide redundancy handling for any two sensors types. Similarly, the two views shown in Figure C.18 can be used to create a generic redundant INS (inertial navigation system) sensor package that can be instantiated with a specific polling rate at a later date. 2 The following formalizes the aggregation of parameters: Definition 72 Given parameterized modules Mi [Pi :: Ti ] for i = 1, 2, we define their sum M1 [P1 :: T1 ] ⊕ M2 [P2 :: T2 ] to be the parameterized module M [P :: T ], where M = M1 ⊕M2 and T = T1 ⊕T2 , with P = P1 !P2 the injection. More n generally, given parameterized modules M [P :: T ] for i = 1, ..., n, define i i i i=1 Mi [Pi :: Ti ] to be the parameterized n n n module M [P :: T ], where M = i=1 Mi and T = i=1 Ti , with P = i=1 Pi the injection. 2

!

Proposition 73 Aggregation and Horizontal Parameterization: Given parameterized specification modules M1 [P1 :: T1 ] and M2 [P2 :: T2 ], and given fitting views h1 : T1 → N1 and h2 : T2 → N2 such that N1 and N2 are distinct26 , then (Law 17) M1 [h1 :: N1 ] ⊕ M2 [h2 :: N2 ] = (M1 ⊕ M2 )[h1 :: N1 , h2 :: N2 ] , and if M1 and M2 are distinct, then ⊕ in the left side above can be replaced by +, provided = is also replaced by ≈, 26 This means that the names N and N are different, even though they could denote equal modules, i.e., N = N even if O(N ) = 1 2 1 2 1 O(N2 ).

180

APPENDIX C. MODULE COMPOSITION SEMANTICS PAPER

theory ANY_SENSOR_TYPE is type Sensor; function Status ( S : Sensor ) return Boolean; −− Status = true if operational, false = broken procedure Reset ( S : Sensor ); end ANY_SENSOR_TYPE; theory POLLING_RATE is procedure Set_Rate ( I in: Integer); −− number of times per minute procedure Get_Rate ( I out: Integer); end POLLING_RATE; generic package REDUNDANT_SENSOR [ A,B :: ANY_SENSOR_TYPE, P :: POLLING_RATE ] is type Sensors_Set; function Status ( S : Sensor_Set ) return Boolean; procedure Reset ( S : Sensor_Set); end REDUNDANT_SENSOR; Figure C.17: Example of Parameterized Module with two Parameter Modules

view S1_View :: ANY_SENSOR_TYPE => INS is types ( Sensor => ASN_141 ); end S1_View ; view S2_View :: ANY_SENSOR_TYPE => INS is types ( Sensor => LN_15J ); end S2_View ; Figure C.18: Two Views of Different Inertial Navigation Sensors (INS)

view High_Rate_View :: POLLING_RATE => RATES is OPS ( Set => Set_High ); ( Get => Get_High ); end High_Rate_View ; Figure C.19: View for High-Rate Polling of Sensors

C.4. HORIZONTAL COMPOSITION

181

(Law 17a) M1 [h1 :: N1 ] + M2 [h2 :: N2 ] ≈ (M1 ⊕ M2 )[h1 :: N1 , h2 :: N2 ] . When T1 = T2 , P1 = P2 and N1 = N2 = N is shared27 , then (Law 17b) M1 [h :: N ] + M2 [h :: N ] = (M1 + M2 )[h :: N ] . Proof: Starting with the left side of Law 17, M1 [h1 :: N1 ] ⊕ M2 [h2 :: N2 ] = ♦ = (M1 ∗ h♦ 1 + N1 ) ⊕ (M2 ∗ h2 + N2 ) ♦ = (M1 ∗ h♦ 1 ) ⊕ (M2 ∗ h2 ) + (N1 ⊕ N2 ) ♦ ) + (N ⊕ N ) , (M1 ⊕ M2 ) ∗ (h1 ⊕ h♦ 1 2 2 which equals its right side. For Law 17a, if M1 and M2 are distinct, then M1 [h1 :: N1 ] ⊕ M2 [h2 :: N2 ] ≈ M1 [h1 :: N1 ] + M2 [h2 :: N2 ] , because of Assumption 5 on page 162, which says that Σ1 and Σ2 are already disjoint. For Law 17b, for the left side we have (M1 ∗ h♦ + N ) + (M2 ∗ h♦ + N ) = (M1 + M2 ) ∗ h♦ + N . 2 Example 74 Aggregating the packages SEARCH_AND_RESCUE and NAP_OF_THE_EARTH with the module expression, SEARCH_AND_RESCUE_MODE[ Pattern :: VECTOR ] + NAP_OF_THE_EARTH [ Height :: INTEGER ] with the “sum” construct of Section C.4.7, can be used to create a new navigation module COVER_SEARCH_and_Rescue [ Pattern :: Vector; Height :: Integer ] that flies search and rescue missions, by staying a certain height off the ground, following a certain flight pattern. 2 Example 75 Similarly, we can aggregate device driver packages for an Inertial Navigation System (INS) sensor with device driver packages for a Radar Altimeter (RALT) sensor. Such aggregations are actually quite common in creating integrated avionics software with sensor fusion or redundant sensors having different accuracies/costs/weight, and since device manufactures have started providing multi-function sensors, it is particularly useful to have a composition mechanism to configure the software for these sensor suites. For example INS [ Rate :: POLLING_RATE ] + RALT [Rate :: POLLING_RATE ] might become the module INS_RALT [ Rate :: POLLING_RATE ] . Notice that the result module is parameterized with a single POLLING_RATE. A parameterized module with two different polling rates could have just as easily been generated, by replacing the two “Rate”s in the module expression by “Rate1” and “Rate2”. 2 The power of using modules as parameters allows some perhaps surprising things to be done without great difficulty. For example, in an object oriented institution (such as that provided for foops [GM87]), we can easily adapt the signature of an implementation to a new context, by parameterizing the module and instantiating it in a new way, 27 This

means that the names N1 and N2 are equal, not just that they denote equal modules, i.e., that O(N1 ) = O(N2 ).

182

APPENDIX C. MODULE COMPOSITION SEMANTICS PAPER

noting that references through chains of object valued attributes can be schematic in foops. In [LX93], this kind of flexibility is called “adaptive software” and is supported by exotic language features; but it arises in a natural way in parameterized programming. Adaptivity is further supported by the ability of parameterize systems with interface subtheories which can express constraints on class structure and methods [Gog89]. One can even have parameterized interface theories, as discussed in [Gog90a]. Parameterized Implementations Users of a module composition language are more likely to be interested in reusing code then in reusing specifications. However, it does not seem possible to define parameterized implementations in a way that is both general and direct, because the way that implementations are constructed from their parts varies from one institution to another. For example, in lileanna, we only have to substitute values for symbolic variables in diana code. But for equational logic, we have to do free extensions of algebras, which may involve generating an infinite number of new terms; operations are then defined syntactically on (equivalence classes of) these terms. This motivates giving an indirect definition, based on the results that must be produced: Definition 76 Given a generic module P : T → M where M = (H , Σ , Ψ , A ) and T = (H, Σ, Ψ, A), then an implementation of P is function [[P ]] : [[T ]] → [[M ]] such that if C is an implementation of T , then [[P ]](C)||T | = C. If C ∈ [[T ]], then[[P ]](C) is the instantiation of P with C. 2

(Recall that c|Ψ denotes the restriction of model c to a subsignature Ψ and [[M ]] is the class of all implementations for M (see Definition 30 in Section C.3.2).) The parameterization and instantiation capabilities of lileanna have clear advantages over those of Ada. In lileanna, one can use an entire Ada package as an actual parameter, instead of having to unpackage its contents. In addition, lileanna allows passing exceptions as parameters. Finally, because instantiation is done by making a copy of the generic module and renaming its formal parameters with the actual parameter values given by the view, the resulting instantiation generally has better performance than a comparable Ada instantiation. This is because no code sharing is attempted by the compiler and better code optimization takes place. (Results vary among compilers, but a 50% improvement in execution speed has been found on some test cases.)

C.4.7

Module Expressions and Make

We have described a number of operations for modifying and combining modules, and we can use them to form complex expressions, involving many modules and operations. We call such expressions module expressions, and we can use them to describe systems and subsystems. But in parameterized programming, we can do more than that, we can evaluate module expressions; when this happens, the (sub)system described by the expression in actually created. Note that evaluating a module expression involves evaluating all its subexpressions. This process agrees with the analogy with functional programming in the introduction: module expressions are analogous to functional expressions that are combinations of previously defined functions. But in this case, the result of evaluating an expression is a software system, plus its specification, not just a function. The lileanna syntax for module expressions is as follows: Mexp

:= | | | | | |

ModuleName ModuleName [ View ] Mexp + Mexp Mexp ∗ (rename MexpMap ) Mexp ∗ (hide Psig ) Mexp ∗ (add Psig , AxS ) Mexp ∗ (replace MexpMap )

where Mexp indicates module expressions, ModuleName indicates modules, MexpMap indicates a tuple set of pairs of elements, Psig indicates a partial signature, AxS indicates a set of axioms, Id indicates an identifier, and View indicates a view, which is defined as follows:

C.4. HORIZONTAL COMPOSITION View

ViewDef

183

:= ViewName | ViewDef | ViewName , View | ViewDef , View ::= view Id is MexpMap end Id

The replace28 operation requires some explanation, since it might seem to violate the principles of parameterized programming. The capability to replace existing module elements with new “implementations” allows lileanna’s module composition capability to be used to evolve existing components and systems in a consistent, traceable manner; this has considerable benefits over using an editor to remove existing code and then insert new code. lileanna also allows the implicit instantiation of parameterized modules by a default view (i.e., module expressions of the form ModuleName [ Mexp ] — see Section C.3.3). Also, a tuple of views that appears as an argument is regarded as abbreviating the aggregated view. The “make” command takes a new name and a module expression as arguments. The result of executing this command is to evaluate the module expression, for both the specification and the code, allocate storage for the resulting code (this is called “elaboration” in the jargon of Ada) and assign it the given name. Thus, executing a make yields executable code, attached to an appropriate specification. This code can then be used in building other systems by evaluating other module expressions that include the name of the newly made module. Example 77 Figure C.20 illustrates29 how a module can be enhanced and refined by replacing parts of an implementation to make it more efficient. A new axiom is also added. make SQUARE is RECTANGLE ∗ (rename Rectangle => Square) ∗ (add function Side(S : Square) return Real) ∗ (replace (Area => Area) ) −− where the new function is implemented as: −− function Area(S : Square) return Real is −− begin −− return Length(S) ** 2; −− end; ∗ (replace (Perimeter => Perimeter) ) −− function Perimeter(S : Square) return Real is −− begin −− return Length(S) ∗ 4; −− end; − − | axiom −−| for all S : Square => −−| Length(S) = Width(S); end; Figure C.20: Making a Square from a Rectangle

Figure C.21 involves hiding and enriching, and is based on an existing set of Ada packages [MM89] that defines a Prolog interpreter. Aggregating these packages into one (called NEW ADA LOGIC INTERFACE) means a user only has to import (i.e., “with”) one package rather than five. The result of this make statement is a merged package specification where, 28 This is more than a “hide” operation followed by an “add” operation, because it actually removes source code from the implementation and then adds new code. 29 This example is based on an example found in [Mey88]

184

APPENDIX C. MODULE COMPOSITION SEMANTICS PAPER

make NEW_ADA_LOGIC_INTERFACE is IDENTIFIER_PACKAGE + CLAUSE_PACKAGE ∗ (hide Copy) + SUBSTITUTION_PACKAGE + DATABASE_PACKAGE + QUERY_PACKAGE ∗ (add function Query_Fail (C: Clause; L: List_Of_Clauses) return Boolean) ∗ (rename (Query_Answer = > Query_Results) ) end; Figure C.21: Making an ADA LOGIC INTERFACE using Aggregation

1. the Copy operation is not available on Clauses, 2. the operation Query Fail has been added to those inherited from Query Package; the implementation of this operation is brought in from the system default package MAKELIL, 3. the Query Answer operation has been renamed Query Results. make HIGH_RATE_GENERIC_SENSOR is REDUNDANT_SENSOR [ High_Rate_View ] end; make REDUNDANT_INS_GENERIC_SENSOR is REDUNDANT_SENSOR [ S1_View , S2_View ] end; Figure C.22: Making Parameterized Modules from Parameterized Modules Figure C.22 continues Example 71 (Figures C.17 — C.19), using lileanna to generate partial instantiations (generic Ada packages) of REDUNDANT SENSOR. In Example 77, High Rate View (Figure C.19) is used to generate the parameterized module HIGH RATE GENERIC SENSOR, which can be configured to provide redundancy handling for any two sensors types. Similarly, the two views in Figure C.18 are used to create a parameterized redundant INS (inertial navigation system) sensor REDUNDANT INS GENERIC SENSOR that can be instantiated with a specific polling rate at a later date. 2 Module expression evaluation is eager rather than lazy; this is appropriate because the issues that lead to lazy evaluation in functional programming do not arise in module expression evaluation. Assumption 9 Module expressions are evaluated bottom up, for both specification and implementation modules. Whenever a subexpression is evaluated, it is given a new name and added to the module graph. Moreover, whenever a module instantiation is evaluated, inclusion views30 from the body module and the actual parameter module to the resulting instantiation are added; the view from the body will be labeled “instance” and the view from the actual “actual-of”. Similarly, whenever an aggregation M = M1 + ... + Mn is evaluated, inclusion views Jj : Mj → M are added to the module graph (for j = 1, ..., n) and labeled “summand-of”. Finally, whenever a transformation M ∗ r is evaluated31 , then the new module M ∗ r is linked to the old module M by a non-view edge labeled “transformed-to”. 2 30 Technically, the instantiation module is the “pushout” of the two views from the parameter theory, one to the body and the other to the actual module, as in [BG80, GB92]. 31 A sequence M ∗ r ∗ ...r of transformations can be treated as a single transformation for this purpose. 1 k

C.5. VERTICAL COMPOSITION

185

This assumption concerns dynamic updating of the module graph, which thus serves as a database for a development project. Although Assumption 9 is stated in an informal way, unlike all our previous assumptions, it would be straightforward, though somewhat tedious, to formalize it, using for example, an abstract machine for graphs. However, we have decided that given our goal of helping implementors, this would not be worth the trouble, and indeed, would make it harder for many readers to understand our intention. Note that a transformation edge (from some M to M ∗ r) in the module graph is not a view, but just a link. To emphasize this, it will be indicated in drawings of the module graph with a dashed line.

C.4.8

Summary

We have seen that module expressions can be used for: 1. instantiating a parameterized module using a view to create: (a) a new parameterized package/module, (b) a new unparameterized package/module, (c) implementations for types and operators in the module. 2. Composing/structuring new modules by: (a) combining given modules (taking account of multiple inheritance), e.g., merging two module’s operators and types, (b) adding something to a given module, e.g., an operator, (c) removing something from the interface of an existing module, e.g., hiding an operator, (d) renaming something, i.e., textually changing the names of operators and/or types in an interface, (e) selecting from a family of implementations through vertical composition (see the next section), (f) replacing something in an existing module, i.e., removing and then adding, e.g., a type, operator, exception, or even an axiom.

C.5

Vertical Composition

Vertical structure concerns the structuring of a software system into “layers,” where each layer calls upon resources provided by layers below it; that is, vertical structure provides “virtual machines” as resources for higher levels. The motivation for layering comes from the construction of large, complex systems, such as communication protocols (e.g., TCP/IP) and operating systems, where it is convenient to implement higher level services using lower level services, where the lowest level services are far from the user but close to the machine, and the highest form the user interface. This motivation implies that inheritance under vertical composition should be intransitive, as opposed to the transitive inheritance of horizontal composition. Intuitively, passing operations from the “lower” interface of a layer to its “upper” interface violates the very idea of layering32 . Therefore, no modules inherited into a module through vertical composition should be exportable. Furthermore, all the types and operations imported into a module through vertical composition should be hidden, i.e., non-exportable. Another implication of the layering motivation for vertical composition is that the horizontal structuring operations of aggregation, renaming, etc. do not make sense: only access to the visible signature of lower layers is allowed. Thus, the only vertical composition operations are parameterization and instantiation; all the other structure of a layer must come from horizontal composition. Since the point of layering is to provide access to a service, more than one module may need that access. Since modules have unique names, there is no difficulty in vertically instantiating two different modules with the same module V representing a vertical layer; V is then shared by these two modules; e.g., A(V ) + B(V ) has just one copy of the layer V . Perhaps surprisingly, the machinery we have developed to treat hiding for horizontal structure is just as well suited for the semantics of vertical parameterization and instantiation. 32 However (perhaps unfortunately), nothing prevents a given layer from “wrapping” some functionality from a lower layer and then exporting it.

186

APPENDIX C. MODULE COMPOSITION SEMANTICS PAPER

Definition 78 A (vertically) parameterized or generic specification module with (vertical) interface theory R and body M is an injective specification module map Q : R → M where R is a theory. We will write M (Q :: R) and say that M is vertically parameterized by Q. We may write M H (Q :: R) when H(M ) = H. 2 Definition 79 Given a vertically parameterized specification module M (Q :: R) with body (H, Σ, Ψ, A) and given a view h : R → N , we let the (vertical) instantiation of M by h be defined by the formula M (h :: N ) = (M ∗ h♦ ) + (N ∗ (hide − all)) .

The view h again may be called a fitting view. We may also use the notation M (h), or even M (N ) if h is clear from context. 2 Notice that M (H(h)) = H(M ), since nothing from N is imported. We treat multiple vertical parameters exactly the same way that we did multiple horizontal parameters, using the operation ⊕ to combine interfaces and the operation to combine the fitting maps of actual parameters.

!

We now explore some of the properties of vertical composition, and will later explore how they relate to horizontal composition. The first result is the direct analog of Proposition 73 for horizontal composition; these algebraic properties are the same because multiple layering and inheritance play no role here. Proposition 80 Aggregation and Vertical Parameterization: Given vertically parameterized specification modules M1 (Q1 :: T1 ) and M2 (Q2 :: T2 ), and fitting views h1 : T1 → N1 and h2 : T2 → N2 such that N1 and N2 are distinct33 , then (Law 18) M1 (h1 :: N1 ) ⊕ M2 (h2 :: N2 ) = (M1 ⊕ M2 )(h1 :: N1 , h2 :: N2 ) , and if M1 and M2 are distinct, then ⊕ in the left side above can be replaced by +, provided = is also replaced by ≈, (Law 18a) M1 (h1 :: N1 ) + M2 (h2 :: N2 ) ≈ (M1 ⊕ M2 )(h1 :: N1 , h2 :: N2 ) . When T1 = T2 , Q1 = Q2 and N1 = N2 = N is shared34 , then (Law 18b) M1 (h :: N ) + M2 (h :: N ) = (M1 + M2 )(h :: N ) . Proof: The proof is similar to that of Proposition 73, but using the formula for vertical instantiation instead of that for horizontal instantiation. 2 We now define modules with both horizontal and vertical parameters: Definition 81 A (horizontally and vertically) parameterized or generic specification module with vertical interface theory R, horizontal interface theory T , and body M consists of two injective specification module maps, P : T → M and Q : R → M , where T and R are theories. We will write M [P :: T ](Q :: R) and say that M is horizontally parameterized by P and vertically parameterized by Q. We may write M H [P :: T ](Q :: R) when H(M ) = H. 2 Definition 82 Given a vertically and horizontally parameterized specification module M [P :: T ](Q :: R) with body (H, Σ, Ψ, A) and given views g : T → N and h : R → N , we let the instantiation of M by g, h be defined by the formula M [g :: N ](h :: N ) = ((M ∗ g ♦ ) ∗ h♦ ) + N + (N ∗ (hide − all)) . The views g, h may be called fitting views. We may also use the notation M [g](h), or even M [N ](N ) if g, h are clear from context. 2

C.5. VERTICAL COMPOSITION

#

Ã

!

Ã

# inherits

H "

187

-

M "

¾ parameterized by

P :: T

! 6 needs (virtual machine layer)

V :: R

Figure C.23: Horizontal and Vertical Relationships for M H [P :: T ](V :: R)

Figure C.23 illustrates the relations among the parts of M H [P :: T ](V :: R), a module inheriting a set H of module names, horizontally parameterized by P with interface theory T , and vertically parameterized by V with interface theory R. Example 83 Figure C.24 shows the package LIL_STACK, which is parameterized horizontally by the theory ANY_TYPE, and is parameterized vertically by the theory LIST_THEORY, which itself is parameterized by the theory ANY_TYPE, which here is instantiated with the same “Item” as in the horizontal parameterization. This means that if the horizontal parameter is instantiated with (say) N , then the vertical must be instantiated with LIST_THEORY[N ]. 2 generic package LIL_STACK[ Item :: ANY_TYPE ] −− LILEANNA Package needs ( ListP :: LIST_THEORY[ Item ] ) is type Stack; includes Element as Stack; function Is_Empty ( S: Stack ) return Boolean; function Push ( E: Element; S: Stack ) return Stack (comm); function Pop ( S: Stack ) return Stack; function Top ( S: Stack ) return Element; − − |axiom − − | for all E: Element, S: Stack => − − | Pop( Push ( E, S ) ) = E; − − | (Pop ( X ) and Is_Empty ( X ) ) => raise Stack_Underflow; −− strong propagation annotation − − | raise Stack_Overflow => S [ in Stack ] = S [ out Stack ]; −− weak propagation annotation end LIL_STACK; Figure C.24: LILEANNA Generic Stack Package Example with Vertical Parameterization Assumption 10 If Q : R → M is a vertically parameterized module, then the injection Q appears as an edge in the module graph from R to M , labeled to indicate that it is a vertical generic module. Vertical instantiations in module expressions are evaluated bottom up, just like all other module composition operations, with evaluated subexpressions being given new names and added to the module graph. 2 33 Recall 34 Recall

this means the names N1 and N2 are different, even though they might be equal, i.e., N1 = N2 even if O(N1 ) = O(N2 ). this means the names N1 and N2 are equal, not just that they denote equal modules.

188

APPENDIX C. MODULE COMPOSITION SEMANTICS PAPER

The next result follows directly from the definitions: Proposition 84 Inheritance for Horizontal and Vertical Parameters: Given a specification module M [P :: T ](V :: R) that is both vertically and horizontally parameterized, then for any instantiation by some g, h, (Law 19) H(M [g :: N ](h :: N )) = H(M ) ∪ H(N ) . 2

C.6

Results and Lessons

The DSSA-ADAGE (Domain-Specific Software Architecture — Avionics Domain Application Generation Environment) project used lileanna to generate integrated avionics software subsystems, and to specify their layered architectures. The two most common module composition constructs used were renaming and instantiation, often in combination with simple vertical parameterization. The need for implementation-level module expression operations to “glue” low-level components together became apparent in this project, and so lileanna was extended to allow adding 1. enumerated types, 2. vertical structure, and 3. explicit invocation of operations. Because of the size of the decision space and the degree of configurability of these avionics subsystems, lileanna module expressions were not written by users. Instead, a Graphical Layout User Environment (glue) coupled with a constraint-based reasoning system was created to allow the user to select components and specify instantiation parameters (and also capture design rationale). This information was represented as a decision tree and passed to another tool, megen (Module Expression GENerator), which constructed the appropriate lileanna make statements. With this approach, a complete avionics subsystem could be generated in about an hour, whereas over 10 hours had been required previously. While the anticipated benefits of parameterized programming were realized in the generating more efficient code (often 20-30% faster), the real savings resulted from the structure imposed on the configuration process by glue. This tool removed some of the syntactic tedium of creating lileanna views as well as worries about violating module semantics. glue was especially useful in helping users configure the vertical structure of an application by selecting from families of implementations.

Appendix D

List of Acronyms ADAGE AKRL ALRM ANNA ARPA BASIC COBOL DARPA DIANA DSO DSSA FOOPS GLUE ISO LA LIL LILEANNA MCL MEGEN MIL MIF OSI PIC SAM SDIO TSL

Avionics Domain Application Generation Environment Avionics Knowledge Representation Language Ada Language Reference Manual Annotated Ada Advanced Research Projects Agency Beginners All-purpose Symbolic Instruction Code COmmon Business Oriented Language Defense Advanced Research Projects Agency Descriptive Intermediate Attributed Notation for Ada Data Source Object Domain-Specific Software Architecture Functional and Object-Oriented Programming System Graphical Layout User Environment International Organization of Standardization LILEANNA Library Interconnect Language LIL Extended with ANNA Module Composition Language Module Expression GENerator Module Interconnect Language Module Interconnect Formalism Open Systems Interconnection Precise Interface Control Surface-to-Air Missile Strategic Defense Initiative Organization Task Sequencing Language

189

Bibliography [Ada83]

US Department of Defense, US Government Printing Office. The Ada Programming Language Reference Manual, 1983. ANSI/MIL-STD-1815A-1983 Document.

[Ada95]

Intermetrics. Annotated Ada 95 Reference Manual Version 6.0, 1995.

[Ale86]

N.A. Alexandridis. Adaptable Software and Hardware: Problems and Solutions. Computer, 19(2):29—39, February 1986.

[Bar93]

J. Barnes. Introducing Ada 9X. Technical report, DoD, February 1993.

[Bat93a]

D. Batory. A Domain Model for Avionics Software. Technical Report ADAGE-UT-93-03, University of Texas at Austin, May 1993.

[Bat93b]

D. Batory. A Domain Model of Radio Navigation, Guidance and Flight Director Software. Technical Report ADAGE-UT-93-01, University of Texas at Austin, January 1993.

[BCG83]

R.M. Balzer, T.E. Cheatham, and C. Green. Software Technology in the 1990’s: Using a New Paradigm. IEEE Computer, pages 39—42, November 1983.

[Bea86]

D.S. Batory and et al. GENESIS: An Extensible Database Management System. IEEE Transactions on Software Engineering, March 1986.

[Bea87]

F.P. Brooks and et al. Report of the Defense Science Board Task Force on Military Software. Technical Report AD-A188 561, Department of Defense, Washington DC 10301, September 1987.

[BG77]

R.M. Burstall and J.A. Goguen. Putting Theories Together to Make Specifications. In Proceedings of Fifth International Joint Conference on Artificial Intelligence, pages 1045—1058. Department of Computer Science, Carnegie-Mellon University, 1977.

[BG80]

R.M. Burstall and J.A. Goguen. The Semantics of Clear, a Specification Language. In Proceedings of the 1979 Copenhagen Winter School on Abstract Software Specification, pages 292—332. Springer-Verlag, 1980.

[BO91]

D.S. Batory and S.W. O’Malley. The Design and Implementation of Hierarchical Software Systems. Technical Report TR-91-22, University of Texas, 1991.

[Boe81]

B.W. Boehm. Software Engineering Economics. Prentice Hall, Englewood Cliffs, NJ, 1981.

[Boo87]

G. Booch. Software Components with Ada. Benjamin/Cummings, 1987.

[BP84]

T.J. Biggerstaff and A.J. Perlis. Forward: Special Issue on Software Reusability. IEEE Transactions on Software Engineering, SE-10(5):474—476, September 1984.

[BR87]

T.J. Biggerstaff and C. Richter. Reusability Framework, Assessment and Directions. In Proceedings of The Hawaii International Conference on System Sciences, pages 502—512, January 7-10 1987.

[Bro87]

F.P. Brooks. No Silver Bullet: Essence and Accidents of Software Engineering. IEEE Computer, 20(4):10—19, April 1987.

[BS92]

B.W. Boehm and W.L. Scherlis. Megaprogramming. In Proceedings of Software Technology Conference 1992, pages 63—82, April 1992.

[BS94]

G. Button and W. Sharrock. Occasioned Practises in the Work of Implementing Development Methodologies. In M. Jirotka and J. Goguen, editors, Requirements Engineering: Social and Technical Issues, pages 217—240. Academic Press, 1994.

[Bur85]

R. Burstall. Programming with Modules as Typed Functional Programming. In Proceedings, International Conference on Fifth Generation Computing Systems, 1985.

190

BIBLIOGRAPHY

191

[Car88]

J.R. Carter. MMAIM: A Software Development Method for Ada. ACM SIGAda Letters, VIII(3):107—114, May/June 1988.

[CGS87]

D. Coleman, R. Gallimore, and V. Stavridou. The Design of a Rewrite Rule Interpreter from Algebraic Specifications. IEEE Software Engineering Journal, pages 95—104, July 1987.

[CGST92]

L. Coglianese, M. Goodwin, R. Smith, and W. Tracz. An Avionics Domain-Specific Software Architecture. In Proceedings of DARPA Software Technology Conference, 1992, pages 211—214, April 1992.

[Cha85]

R. Chang. ParseGen a LALR(1) Parser Generator. Technical Report Program Verification Report PV-28, CSD NOTE STAN-CS-85-283, Stanford University, 1985.

[Coo79]

L.W. Cooprider. The Representation of Families of Software Systems. PhD thesis, Department of Computer Science, Carnegie-Mellon University, April 1979.

[CST92]

L. Coglianese, R. Smith, and W. Tracz. DSSA Case Study: Navigation, Guidance and Flight Director Design and Development. In Proceedings of IEEE Symposium on Computer-Aided Control System Design (CACSD’92), March 1992.

[CTea91]

L. Coglianese, W. Tracz, and et all. Avionics Architectures: The Avionics Development and Application Generation Environment (DSSA-ADAGE) Project. Technical Report IBM-BAA#90-21, IBM Federal Systems Company, February 1991.

[CW85]

L. Cardelli and P. Wegner. On Understanding Types, Data Abstractions, and Polymorphism. Computing Surveys, 17(4):471—522, 1985.

[DGS91]

R. Diaconescu, J. Goguen, and P. Stefaneas. Logical Support for Modularisation. In in proceedings of workshop on Logical Environments, pages 83—130. Cambridge, 1993, May 1991.

[DIA83]

Tartan Laboratories Incorporated. DIANA Reference Manual, Revision 3, 1983.

[DK76]

F. DeRemer and H. Kron. Programming-in-the-Large versus Programming-in-the-Small. IEEE Transactions on Software Engineering, SE-2(2):80—86, June 1976.

[Edw90]

S.H. Edwards. An Approach for Construction Reusable Software Components in Ada. Technical Report IDA Paper P-2378, Institute for Defense Analysis, Alexandria, VA, September 1990.

[EW87]

D.W. Embley and S.N Woodfield. A Knowledge Structure for Reusing Abstract Data Types. In Proceedings of 9th Annual International Conference on Software Engineering, page 13, March 30 - April 2 1987.

[FGJM85]

K. Futatsugi, J.A. Goguen, J.P. Jouannaud, and J. Meseguer. Principles of OBJ2. In Proceedings of Symposium on Principles of Programming Languages, volume 12, pages 52—66, New York, 1985. Association for Computing Machinery, Inc.

[Gar95]

D. Garlan. First International Workshop on Architectures for Software Systems Workshop Summary. ACM SIGSOFT Software Engineering Notes, 20(3):84—89, July 1995.

[Gau87]

R.J. Gautier. A Language for Describing Ada Software Components. In Proceedings of Ada-Europe Conference, May 26-28 1987.

[GB80]

J.A. Goguen and R.M. Burstall. CAT, A System for the Structured Elaboration of Correct Programs from Structured Specifications. Technical Report CSL-118, SRI International, Menlo Park, CA, October 1980.

[GB92]

J. Goguen and R. Burstall. Institutions: Abstract Model Theory for Specification and Programming. Journal of the Association for Computing Machinery, 39(1):95—146, January 1992. Draft appears as Report ECSLFCS-90-106, Computer Science Department, University of Edinburgh, January 1990; an early ancestor is “Introducing Institutions,” in Proceedings, Logics of Programming Workshop, Edward Clarke and Dexter Kozen, Eds., Springer Lecture Notes in Computer Science, Volume 164, pages 221—256, 1984.

[GD94]

J. Goguen and R. Diaconescu. Towards an Algebraic Semantics for the Object Paradigm. In H. Ehrig and F. Orejas, editors, Proceedings, Tenth Workshop on Abstract Data Types, pages 1—29. Springer, 1994. Lecture Notes in Computer Science, Volume 785.

[GH93]

J.V. Guttag and J.J. Horning. Larch: Languages and Tools for Formal Specification. Springer-Verlag, 1993.

[GHJV94]

E. Gamma, R. Helm, R. Johnson, and J Vlissides. Design Patterns — Microarchitectures for Reusable ObjectOriented Software. Addison-Wesley, 1994.

[GHW82]

J.V. Guttag, J.J. Horning, and J.M. Wing. Some Notes on Putting Formal Specifications to Productive Use. Science of Computer Programming, 2:53—68, December 1982.

192

BIBLIOGRAPHY

[GHW85]

J.V. Guttag, J.J. Horning, and J.M. Wing. The Larch Family of Specification Languages. IEEE Software, 5(2):24—36, September 1985.

[GL83]

J.A. Goguen and K.N. Levitt, editors. Report on Program Libraries Workshop. SRI International, Menlo Park, Calif., 1983.

[GM82]

J. Goguen and J. Meseguer. Universal Realization, Persistent Interconnection and Implementation of Abstract Modules. In M. Nielsen and E.M. Schmidt, editors, Proceedings, 9th International Conference on Automata, Languages and Programming, pages 265—281. Springer, 1982. Lecture Notes in Computer Science, Volume 140.

[GM87]

J.A. Goguen and J. Meseguer. Unifying Functional, Object-Oriented and Relational Programming with Logical Semantics. In B. Shriver and P. Wegner, editors, Research Directions in Object-Oriented Programming, pages 417—477. MIT Press, 1987.

[GM92]

M. Graham and E. Mettala. The Domain-Specific Software Architecture Program. In Proceedings of DARPA Software Technology Conference, 1992, pages 204—210, April 1992. Also published in CrossTalk, The Journal of Defense Software Engineering, October, 1992, pages 19-21, 32.

[GM96a]

J. Goguen and G. Malcolm. Algebraic Semantics of Imperative Programs. MIT, 1996.

[GM96b]

J. Goguen and G. Malcolm. Extended Abstract of a Hidden Agenda. In J.A. Meystel and R. Quintero, editors, Proceedings, Conference on Intelligent Systems: A Semiotic Perspective, pages 159—167. National Inst. Standards and Technology, October 20-23 1996.

[GM96c]

J. Goguen and G. Malcolm. Proof of Correctness of Object Representation. In J.A. Meystel and R. Quintero, editors, Proceedings, Conference on Intelligent Systems: A Semiotic Perspective, pages 159—167. National Inst. Standards and Technology, October 20-23 1996.

[GMP83]

J.A. Goguen, J. Meseguer, and D. Plaisted. Programming with Parameterized Abstract Objects in OBJ. In D. Ferrari, M. Bolognani, and J. Goguen, editors, Formalizing Programming Concepts, pages 163—193. NorthHolland, 1983.

[Gog77]

J.A. Goguen. Abstract Errors for Abstract Data Types. In P. Neuhold, editor, Proceedings of First IFIP Working Conference on Formal Description of Programming Concepts, pages 21.1—21.32. MIT, 1977. Also published in Formal Description of Programming Concepts, P. Neuhold, ed., pp. 491-522. North-Holland, 1979.

[Gog79]

J.A. Goguen. Some Design Principles and Theory of OBJ-0, a Language for Expressing and Executing Algebraic Specifications of Programs. In Proceedings, Mathematical Studies of Information Processing, pages 425—473. Springer-Verlag, 1979.

[Gog83]

J.A. Goguen. LIL — A Library Interconnect Language. In Report on Program Libraries Workshop, pages 12—51, Menlo Park, Calif., October 1983. SRI International.

[Gog84]

J.A. Goguen. Parameterized Programming. IEEE Transactions on Software Engineering, SE-10(5):528—543, September 1984.

[Gog85]

J. Goguen. Suggestions for Using and Organizing Libraries in Software Development. In Proceedings, First International Conference on Supercomputing Systems, pages 349—360. IEEE Computer Society, 1985. Also in Supercomputing Systems, Steven and Svetlana Kartashev, Eds., Elsevier, 1986.

[Gog86a]

J.A. Goguen. One, None, a Hundred Thousand Specification Languages. In H.J. Kugler, editor, Information Processing’86, pages 995—1003. Elsevier, 1986.

[Gog86b]

J.A. Goguen. Reusing and Interconnecting Software Components. Computer, 19(2):16—28, February 1986.

[Gog89]

J.A. Goguen. Principles of Parameterized Programming. In Software Reusability Volume I, Concepts and Models, pages 159—225. Addison-Wesley Publishing Company, 1989.

[Gog90a]

J. Goguen. Higher-order Functions Considered Unnecessary for Higher-Order Programming. In D. Turner, editor, Research Topics in Functional Programming, pages 309—352. Addison Wesley, 1990. University of Texas at Austin Year of Programming Series; preliminary version in SRI Technical Report SRI-CSL-88-1, January 1988.

[Gog90b]

J.A. Goguen. Hyperprogramming: A Formal Approach to Software Environments. In Proceedings of Symposium on Formal Methods in Software Development, Tokyo, Japan, January 1990.

[Gog91a]

J. Goguen. A Categorical Manifesto. Mathematical Structures in Computer Science, 1(1):49—67, March 1991.

[Gog91b]

J.A. Goguen. Types as Theories. In G.M. Reed, A.W. Roscoe, and R.F. Wachter, editors, Topology and Category Theory in Computer Science, pages 357—390. Oxford, 1991. Proceedings of a Conference held at Oxford, June 1989.

BIBLIOGRAPHY

193

[Gog94]

J. Goguen. Requirements Engineering as the Reconciliation of Social and Technical Issues. In M. Jirotka and J. Goguen, editors, Requirements Engineering: Social and Technical Issues, pages 165—200. Academic Press, 1994.

[Gog96a]

J. Goguen. Formality and Informality in Requirements Engineering. In Proceedings, International Conference on Requirements Engineering, pages 102—108, April 1996.

[Gog96b]

J. Goguen. Parameterized Programming and Software Architectures. In Proceedings of Fourth International Workshop on Software Reuse, pages 2—10, April 1996.

[GR83]

A. Goldberg and D. Robson. Smalltalk-80: The Language and its Implementation. Addison-Wesley Publishing Company, 1983.

[GT95]

J.A. Goguen and W.T. Tracz. An Implementation-Oriented Semantics for Module Composition. Technical Report ADAGE-OSF-95-01, Oxford University and Loral Federal Systems, October 1995.

[GTP95]

D. Garlan, W. Tichy, and F. Paulisch. Summary of the Dagstuhl Workshop on Software Architecture. ACM SIGSOFT Software Engineering Notes, 20(3):63—83, July 1995.

[Gut75]

J.V. Guttag. The Specification and Application to Programming of Abstract Data Types. PhD thesis, Department of Computer Science, University of Toronto, 1975.

[GWM+ ar]

J. Goguen, T. Winkler, J. Meseguer, K. Futatsugi, and J. Jouannaud. Introducing OBJ. In J. Goguen and G. Malcolm, editors, Algebraic Specification with OBJ: An Introduction with Case Studies. Cambridge, to appear. Also Technical Report, SRI International.

[Har87]

G.C. Harrison. An Automated Method of Referencing Ada Reusable Code Using LIL. In Proceedings of Fifth National Conference on Ada Technology and Fourth Washington Ada Symposium, March 17-19 1987.

[HHT94]

J.T. Higgins, L. Holmquist, and W. Tracz. ADAGE Layout Editor User’s Manual. Technical Report ADAGELOR-94-04, Loral Federal Systems — Owego, July 1994.

[HL85]

D. Helmbold and D. Luckham. TSL: Task Sequencing Language. In Proceedings of the Ada International Conference, Paris, France, May 14-16 1985.

[Hoa69]

C.A.R. Hoare. An Axiomatic Basis for Computer Programming. Communications of the ACM, 12(10):576—581, 1969.

[HTN94]

J. Higgins, W. Tracz, and E. Newton. DOMAIN (DOmain Models, All INtegrated) User Guide. Technical Report ADAGE-LOR-94-06A, Loral Federal Systems — Owego, September 1994.

[HTS96]

L. Holmquist, W. Tracz, and R. Selby. An Evaluation of DSSA-ADAGE Technology. Technical Report ADAGELOR-96-01, Lockheed Martin Federal Systems — Owego, July 1996. To appear in CrossTalk.

[Hug89]

J. Hughes. Why Functional Programming Matters. The Computer Journal, 32(2), 1989.

[HW93]

P. Hall and R. Weedon. Object Oriented Module Interconnection Languages. In Proceedings of Second International Workshop on Software Reuse, pages 29—38, March 1993.

[Inc88]

Software Productivity Solutions Inc. Draft: Classic-Ada User Manual. Beta Test Copy, 1988.

[ISO83]

International Organization for Standardization. Basic Reference Model for Open Systems Interconnection, 1983.

[JC95]

J. Jeng and B.H.C. Cheng. Specification Matching for Software Reuse: A Foundation. In Proceedings of the Symposium on Software Reusability, pages 97—105, April 1995.

[JG94]

M. Jirotka and J. Goguen. Requirements Engineering: Social and Technical Issues. Academic Press, 1994.

[Jon84]

T.C. Jones. Reusability in Programming: A Survey of the State of the Art. IEEE Transactions on Software Engineering, SE-10(5):488—493, September 1984.

[KBL80]

Bernd Krieg-Br¨ uckner and David Luckham. anna: Towards a language for annotating Ada programs. SIGPLAN Notices, 15(11):128—138, November 1980.

[KFO87]

J. Meseguer K. Futatsugi, J. A. Goguen and K. Okada. Parameterized Programming in OBJ2. In Proceedings of the Ninth Annual International Conference on Software Engineering, pages 51—60. IEEE CS Press, 1987.

[Kov87]

V.J. Kovarik. The HOOPS Reference Manual. Technical Report TR87-7736-01, Harris Corporation, November 1987.

[Kru92]

C.W. Krueger. Software Reuse. ACM Computer Surveys, 24(2):131—183, June 1992.

194

BIBLIOGRAPHY

[Lam87]

J.V. Lamping. A Unified System of Parameterization for Programming Languages. PhD thesis, Department of Computer Science, Stanford University, Stanford, California, 1987.

[Liu88]

D.B. Liu. A Knowledge Structure of a Reusable Software Component in LIL. In Proceedings, Sixth National Conference on Ada Technology, Ft. Monmouth, N.J., March 14-17 1988. U.S. Army Communications-Electronics Command.

[LMS+ 79]

B.H. Liskov, E. Moss, C. Schaffert, B.Scheifer, and A. Snyder. CLU Reference Manual. Technical report, MIT Lab for Computer Science, 1979.

[LNR87]

D.C. Luckham, R. Neff, and D.S. Rosenblum. An Environment for Ada Software Development based on Formal Specification. Ada Letters, VII(3):94—106, May, June 1987.

[Luc90]

D.C. Luckham. Programming with Specifications: An Introduction to ANNA, A Language for Specifying Ada Programs. Texts and Monographs in Computer Science. Springer-Verlag, October, 1990.

[LVB+ 92]

D.C. Luckham, J. Vera, D. Bryan, L. Augustine, and F. Belz. Partial Orderings of Event Sets and Their Application to Prototyping Concurrent Timed Systems. In Proceedings of Software Technology Conference 1992, pages 443—451, April 1992.

[LvH84]

D.C. Luckham and F.W. von Henke. ANNA: A Language for Annotating Ada Programs. In IEEE Computer Society Conference on Ada Applications and Environments, October 15-18 1984.

[LvH85]

D.C. Luckham and F.W. von Henke. An Overview of Anna, a Specification Language for Ada. IEEE Software, 2(2):9—23, March 1985.

[LvHKBO87] D.C. Luckham, F.W. von Henke, B. Kreig-Brueckner, and O. Owe. Lecture Notes in Computer Science, no. 260: Anna, A Language For Annotating Ada Programs, Language Reference Manual. Springer-Verlag, 1987. [LX93]

K. Lieberherr and C. Xiao. Minimizing Dependency on Class Structure with Adaptive Programs. In S. Nishio and A. Yonezawa, editors, Proceedings, Symposium on Object Technologies for Advanced Software, pages 424— 441. Springer, 1993.

[Mat86]

R.F. Mathis. The Last 10 Percent. IEEE Transactions on Software Engineering, SE-12(6):705—712, June 1986.

[McA94]

D. McAllester. Variational Attribute Grammars for Computer Aided Design. Technical Report ADAGE-MIT94-01, MIT, June 1994.

[McI69]

M.D. McIlroy. Mass Produced Software Components. In Proceedings of NATO Conference on Software Engineering, pages 88—98, New York, 1969. Petrocelli/Charter.

[Mey88]

B. Meyer. Object-oriented Software Construction. Prentice Hall Publishing Company, 1988.

[MG94]

G. Malcolm and J. Goguen. Proving Correctness of Refinement and Implementation. Technical Report Technical Monograph PRG-114, University of Oxford, 1994. Submitted for publication.

[Mis87]

F.C. Mish, editor. Webster’s Ninth New Collegiate Dictionary. Merriam-Webster, Inc., 1987.

[MM89]

N. Madhav and W. Mann. Abstract Specification of Automated Reasoning Tools: An Ada-Logic Interface. Program Analysis and Verification Group, Stanford University, 1989.

[MS87]

D. Musser and A. Stepanov. A Library of Generic Algorithms in Ada. In In Proceedings of 1987 ACM SIGAda International Conference on the Ada Programming Language, December 8-10 1987.

[Nef89]

R. Neff. The Formal Specification and Analyses of Ada Programs. PhD thesis, Stanford University, Stanford, California, 1989.

[Par72a]

D. Parnas. Information Distribution Aspects of Design Methodology. In Proceedings of 1972 IFIP Congress, pages 339—344, 1972.

[Par72b]

D.L. Parnas. A Technique for Software Module Specification with Examples. Communications of the ACM, 15(5):330—336, May 1972.

[Par72c]

D.L. Parnas. On the Criteria to be Used in Decomposing Systems into Modules. Communications of the ACM, 15(12):1053—1058, December 1972.

[Par76]

D.L. Parnas. On the Design and Development of Software Families. IEEE Trans. Software Engineering, SE2(1):1—9, January 1976. R. Prieto-D´iaz. Domain Analysis for Reusability. In Proceedings of COMPSAC’87, pages 23—29, 1987.

[PD87] [PDA91]

R. Prieto-D´iaz and G. Arango. Domain Analysis and Software Systems Modeling. IEEE Computer Society Press, 1991.

BIBLIOGRAPHY

[PDN86]

195

R. Prieto-D´iaz and J.M. Neighbors. Module Interconnection Language. Journal of Systems and Software, 6(4):307—344, November 1986.

[Raj85]

V. Rajlich. Paradigms for Design and Implementation in Ada. CACM, 28(7):718—727, July 1985.

[RS92]

L. Rapanotti and A. Socorro. Introducing FOOPS. Technical report, Programming Research Group, Oxford University, 1992.

[San82]

D. Sannella. Semantics, Implementation and Pragmatics of Clear, a Program Specification Language. PhD thesis, University of Edinburgh, Computer Science Department, 1982. Report CST-17-82.

[SC88]

R. Simonian and M. Crone. InnovAda: True Object-Oriented Programming in Ada. Journal of Object-Oriented Programming, 4(1):14—21, November/December 1988.

[SC93]

S. Shafer and L. Coglianese. Avionics Type Expressions: Realm Definitions and an Example System. Technical Report ADAGE-IBM-93-06, IBM Federal Systems Company, April 1993. Preliminary Version.

[Sha84]

M. Shaw. Abstraction Techniques in Modern Programming Languages. IEEE Software, 5(1):10—26, October 1984.

[SMC74]

W.P. Stevens, G.J. Myers, and L.L. Constantine. Structured Design. IBM Systems Journal, 2:115—139, 1974.

[SS86]

E. Seidewitz and M. Stark. Towards a General Object-Oriented Software Development Methodology. In Proceedings of First International Conference on Ada Programming Language Applications for the NASA Space Station, pages D.4.6.1.1—D.4.6.14, Clear Lake, Tex., June 2-5 1986. University of Houston at Clear Lake.

[ST88]

D. Sannella and A. Tarlecki. Specifications in an Arbitrary Institution. Information and Control, 76:165—210, 1988. Earlier version in Proceedings, International Symposium on the Semantics of Data Types, Lecture Notes in Computer Science, Volume 173, Springer, 1985.

[Str86]

B. Stroustrup. The C++ Programming Language. Addison-Wesley Publishing Company, 1986.

[Str88]

B. Stroustrup. Parameterized Types in C++. uary/February 1988.

[Tar81]

J. Tardo. The Design, Specification, and Implementation of OBJT: A Language for Writing and Testing Abstract Algebraic Program Specifications. PhD thesis, Computer Science Department, UCLA, 1981.

[TC92]

W. Tracz and L. Coglianese. DSSA Engineering Process Guidelines. Technical Report ADAGE-IBM-92-02A, IBM Federal Systems Company, December 1992.

[TCY93]

W. Tracz, L. Coglianese, and P. Young. A Domain-Specific Software Architecture Engineering Process Outline. ACM Software Engineering Notes, 18(2):40—49, April 1993.

[Tho76]

J.W. Thomas. Module Interconnection in Programming Systems Supporting Abstraction. PhD thesis, Department of Computer Science, Brown University, June 1976.

[Tic80]

W.F. Tichy. Software Development Control Based on Systems Structure Description. PhD thesis, Department of Computer Science, Carnegie-Mellon University, January 1980.

[Tra87a]

W. Tracz. Formal Specification of Program Schema. Thesis Proposal, Stanford University, November, 1987.

[Tra87b]

W. Tracz. RECIPE: A Reusable Software Paradigm. In Proceedings of The Hawaii International Conference on System Sciences, pages 546—555, January 7-10 1987.

[Tra87c]

W. Tracz. Software Reuse: Motivators and Inhibitors. In Proceedings of COMPCON87, February 1987.

[Tra88a]

W. Tracz. Software Reuse: Emerging Technology. IEEE Computer Society Press, 1988.

[Tra88b]

W. Tracz. Software Reuse Maxims. ACM Software Engineering Notes, 13(4):28—31, October 1988.

[Tra88c]

W. Tracz. Software Reuse Myths. ACM Software Engineering Notes, 13(1):17—21, January 1988.

[Tra90]

W. Tracz. The 3 Cons of Software Reuse. In Proceedings of Fourth Workshop on Software Reuse, Syracuse, NY, July 1990.

[Tra91a]

W. Tracz. A Conceptual Model for Megaprogramming. ACM Software Engineering Notes, 16(3):36—45, July 1991.

[Tra91b]

W. Tracz. An Outline for a Domain-Specific Software Engineering Process. In Proceedings of Fourth Workshop on Software Reuse, November 1991.

[Tra92]

W. Tracz. Software Reuse Technical Opportunities. In Proceedings of DARPA Software Technology Conference, 1992, pages 33—41, April 1992.

Journal of Object-Oriented Programming, 4(1):5—16, Jan-

196

BIBLIOGRAPHY

[Tra93a]

W. Tracz. LILEANNA: A Parameterized Programming Language. In Proceedings of Second International Workshop on Software Reuse, pages 66—78, March 1993.

[Tra93b]

W. Tracz. Parameterized Programming in LILEANNA. In Proceedings of ACM Symposium on Applied Computing SAC’93, pages 77—86, February 1993.

[Tra93c]

W. Tracz. Second International Workshop on Software Reuse Workshop Summary. ACM Software Engineering Notes, 18(3):A73—77, July 1993.

[Tra94]

W. Tracz. LILEANNA Language Reference Manual. Technical Report ADAGE-LOR-94-07, Loral Federal Systems — Owego, July 27 1994.

[Tra95]

W. Tracz. Collected Confessions of a Used Program Salesman — Institutionalizing Software Reuse. AddisonWesley Publishing Co., New York, NY, 1995.

[TSV94]

W. Tracz, S. Shafer, and A. Villarica. MEGEN (Module Expression GENerator) Development Guide. Technical Report ADAGE-LOR-94-09, Loral Federal Systems — Owego, November 1994.

[WCW85]

A.L. Wolf, L.A. Clarke, and J.C. Wileden. Ada-Based Support for Programming-in-the-Large. IEEE Software, 2(2):58—71, March 1985.

[Win87]

J.M. Wing. Writing Larch Interface Language Specifications. ACM Transactions on Programming Languages and Systems, 9(1):1—24, January 1987.

[Wir86]

M. Wirsing. Structured Algebraic Specifications: A Kernel Language. Theoretical Computer Science, 42:123— 249, 1986.

[WLS76]

W.A. Wulf, R.L. London, and M. Shaw. Abstraction and Verification in Alphard: Introduction to Language Methodology. Technical report, Carnegie-Mellon University, 1976.

[Wol85]

A.L. Wolf. Language and Tool Support for Precise Interface Control. PhD thesis, Computer and Information Science Department, University of Massachusetts, Amherst, Mass., 1985.

[WWC92]

G. Wiederhold, P. Wegner, and S. Ceri. Toward Megaprogramming. Communications of the ACM, 35(11):89— 99, 1992.

[YT92]

P.S. Young and R.N. Taylor. Teamware: Process Programming Support for Managers and Teams, July 1992.

[ZW93]

A.M. Zaremski and J.M. Wing. Signature Matching: A Key to Reuse. In Proceedings of ACM SIGSIFT’93 Symposium on the Foundations of Software Engineering, pages 182—190, December 1993.

Suggest Documents