A Comparison of Module Constructs in Programming ... - CiteSeerX

2 downloads 0 Views 530KB Size Report
Languages like Ada [l], Modula-2 [16] and Oberon [18, 19] have module constructs that are in two parts: the specification part which lists all the public entities, ...
A Comparison of Module Constructs in Programming Languages Frank W. Calliss College of Engineering and Applied Sciences Department of Computer Science and Engineering Arizona State University Tempe, AZ 85287-5406, U.S.A. calliss@ asuvax.eas.asu.edu

Abstract

Modules are constructs thai help implement the principle of information hiding. Some languages provide general purpose modules, while others provide modules that are more specialised. This paper compares the modules that are provided by some languages.

1

W h a t is a M o d u l e ?

A module is a named collection of entities, where a programmer has control over the entities that are exported to or imported from the surrounding environments. The use of this construct in programming was originally demonstrated by Dahl and Hoare [6] and Parnas [13]. As the benefits of using modules became more accepted [21], so more languages began to emerge containing modules. The role of modules in programming languages is twofold. Firstly, it is an abstraction mechanism, separating the logical properties of a resource away from the implementation details. Secondly, it is a protection mechanism, preventing unauthorised programmers from using some resource (entity). Languages have tended to place the emphasis either on the abstraction mechanism or on the protection mechanism. This has led to the creation of two families of module. These are modules that support data abstraction and modules that support general information hiding. Although the nature of the modules from these two families differs, they share certain characteristics: • They support multiple views of a subsystem. The number of views normally being two: - the client view - - what the subsystem does

- the supplier view - - what the subsystem does and how it does it Some languages allow more than one client view. • The implementor is able to hide entities from other clients.

A C M S I G P L A N N o t i c e s , Vol. 26, N o . 1, January 1991 38

In the following sections, the module construct of various languages are compared with respect to supporting information hiding. In.formation hiding as advocated by Parnas [12, 13], is the hiding of a design decision from programmers who do not need that information.

2

Public and Private Entities

An essential part of information hiding is the ability to divide a module's entities into public and private entities. Public entities are made available to client modules, whereas private entities are those that are hidden in the module containing the entity's declaration - - the supplier module. Simula 67 [7] makes all its entities public, thereby providing no in.formation hiding capabilities. This weakness has been remedied in more recent versions of Simula [4] where a protected declaration has been added. An entity declared as protected is not available to client module. In addition to this, Simula has a hidden declaration. The hidden declaration provides a means by which a parent module can hide an entity from a child module. Entities that are hidden are also protected, so hidden entities are only visible to the declaring module. With Simula, a programmer has to determine whether the supplier or parent module has barred access to an entity. This means that programmers working on a client or child module has to be aware of all the entities in the supplier or parent module. This goes against the ideas of information hiding, as ideally the programmers of client modules should only know about entities they can use. By making entities public by default, Simula makes it easy to violate the ideas of restricting the distribution of information between modules. If a programmer forgets to make an entity hidden or protected, then a child or client module can use an entity they should not have access to. The approach employed in most languages is that a module states which entities are being made visible to client modules. Therefore client modules check to see which entities they can use from another module. Languages like Ada [l], Modula-2 [16] and Oberon [18, 19] have module constructs that are in two parts: the specification part which lists all the public entities, and the implementation part which lists all the private entities. Splitting a module into two physical parts emphasises the division of public and private entities for a given module. A module does not have to be divided into two physical parts in order for it to list the public entities. C++ [15], CLU [9] and Eiffel z [10] are examples of languages whose module construct is in one piece and which allows the public entities to be listed. CLU lists the public entities in the module header, Eiffel lists the public entities in an export clause, and C++ divides a module into private and public parts, all the entities declared or defined in the public part of a module are public entities. With all of these languages, if an entity is not denoted as being public then it is private. Smalltalk 802 [8] allows a limited amount of control over the visibility of exported entities. All methods (routines) are public, but the visibility of variables can be controlled. Smalltalk 80 divides its tEi.ffel is a trademark of Interactive Software Engineering Inc. ZSrnalltalk 80 is a registered trademark of ParcPlace Systems

39

variables into two main categories: instance variables and shared variables. Instance variables are private variables that exist for the entire lifetime of the object. Shared variables are public variables. Smalltalk 80 provides three forms of shared variables, these are: class variables, pool variables and global variables. Class variables are shared by all instances of a particular class, pool variables are shared by all instances of a set of classes, and global variables are a special case of pool variables where the variables are visible to all instances of all classes. The module construct in Fortran 8X [3] makes all of its entities public by default. Just as in Simula, a programmer can selectively hide entities from a client module. However, Fortran 8X allows a programmer to change this default setting so that all the entities are private by default. The programmer would then make selected entities visible. This is an improvement on Simula, but allowing some modules to have public entities as the default while other modules have private entities as the default will make inter-module code analysis very cumbersome.

3

Restricted Export/Import of Entities

The number of client views of a module is normally one but some languages like Extended Pascal [2 ] and Eiffel allow for multiple client views. This facility can be used to allow a supplier module to restrict the visibility of the entities it exports. For example, consider the following Eiffel export clause: export entityl { M1} , entiry2 { M1,

M2 } , entity3 { M2 } , entity4

This clause says that the module M1 has access to the entities entityl, entity2 and entity4 while the module M2 has access to the entities entity2, entity3 and entity4. The entity entity4 is not selectively exported, therefore any module can import and use it. The selective export capability helps prevent unauthorised access to an entity exported by a module. In the above example, module M1 cannot access entity3 even though it does import entities from the same module exporting entio,3. To restrict the visibility of an exported entity in this way, requires that the supplier module be aware of which client modules use it and which entities they require. If a new module is added to the system and the new module requires the use of entities that are selectively exported by an existing module, then the existing module has to be modified to allow the new module access to the required entities. This shows that when the supplier is able to restrict the visibility of exported entities, the security against unauthofised access to an entity is gained at the expense of module independence. A supplier module would have to be modified (and recompiled) because a new client module has been created. Modula-2 presents a different solution to the problem of unauthorised access to entities, selective imports. With selective imports, the client module is required to explicitly state which entities it needs. To selectively import entities in Modula-2 the following statement is employed, FROM

Module-Name

For example, a statement of the form:

40

IMPORT

Entity-List;

FROM M0 IMPORT

entityl,

entity2;

means that the entities e n t i t y l and e n t i t y 2 are imported from the module M0. Any other entities provided by M0 are not imported and so cannot be used. If a new module is added to a system, it only has to import the desired entity from the existing module. The existing module does not have to be modified, therefore it does not need to be recompiled. Eiffel shows how restricted exports can help ensure that there is no unauthorised use of art exported entity. The restricted imports approach is not as secure as the restricted exports approach with respect to preventing unauthorised use of an exported entity, as the supplier module has no control over which modules are valid client modules. With restricted imports, the onus is on the client modules not to import what they do not need. Modules that are connected by selectively exported entities are more strongly coupled than equivalent modules connected by either selectively imported entities, or by modules that adopt the importing strategy used in languages like Ada, CLU and Oberon, where all the public entities are always imported. The reason for this increase in the coupling measure is that a change in a client module has more possibility of resulting in a change in the supplier module when selective export is used. For example, with the selective export example given above, the module M1 could be modified in such a way that it now needed access to entity3. The effects of this modification should be restricted to the module M1 and with Ada, CLU, Oberon and Modula-2 that is the case, with Eiffel however the supplier module also has to be modified to allow M1 access to the entity entio,3.

4

Name Clashes

Name clashes occur when two same named entities exist in the same region. The name clashing problem is resolved by programming languages in a number of ways. The solution that Fortran 8X employs is to rename some of the entities as they are being imported. Ada also provides a renaming declaration but also allows for overloading of identifiers so that the name clash can often be resolved by context. Some languages like CLU, Eiffel and Oberon avoid the problem of name clashes by insisting that all imported entities be accessed via qualified reference. Modula-2 does not impose this restriction except when a name clash occurs if qualified reference is not used. Modula-2 does not provide facilities for renaming or overloading identifiers, so if a name clash occurs by selectively importing entities from different modules, the client module must import the entities in such a way that only qualified reference is possible. This is done by using an import statement of the form: IMPORT

Module-Name;

which imports all the entities exported by the named module but requires that all of these imported entities be accessed by using qualified reference. Alternatively, if the name clash is the a result of an entity's being "injected" into a module by being exported by a local module, then the injected entity must be

41

exported by the local module using an E X P O R T Q U A L I F I E D statement. An entity is said to be injected if the exporting module is local to the importing module. There are some difficulties in determining when qualified reference is valid in Modula-2 [5]. The name clashing problem described above is due primarily to the new scope rules that modules have introduced into programming languages. The other situation in which name clashes occur is when languages provide multiple inheritance. Not all the solutions used to overcome the name clashing problem described above are applicable to name clashing due to multiple inheritance. Client modules of an child module should be able to access an inherited entity, and not have to be aware that it is inherited. If a name clash occurs because a module inherits entities with the same identifier, how can a client module know which entity it is importing? Qualified reference is not appropriate, as this would require that the client module knows from which parent module the supplier module has inherited the entity. Overloading of identifiers does not guarantee that the problem of name clashes can be resolved, as it is not always possible to resolve contextually, any ambiguities over which entity is being used. For example, if a module C h i l d , imports parameterless functions from the modules P a r e n t l and P a r e n t 2 and the functions have the same identifier, it is not possible to resolve the name clash by context. In order to adhere to the pnnciples of information hiding, it is important that child modules resolve all name clashes that result from inheriting entities in such a way that client modules are oblivious to the to the existence of a name clash problem. Eiffel does this by means of a renames statement. Inherited entities are renamed if they would result in a name clash.

5

Visibility of Types

This section deals primarily with languages like Ada, Modula-2 and Oberon which provide general purpose modules. The module constructs in languages like C++, CLU and Eiffel aimed at implementing abstract data types are themselves types and as such do not need to provide type entities. Ross [14] gives a taxonomy of Ada packages based on the degree of visibility of the types declared within the package. Ross classifys the different packages as follows:

The "open" package A package whose constant, variable and type declarations are fully visible.

The "private" package A package which prevents client packages from seeing the structure of an exported type, but which allows client packages to perform assignments and test for equality/inequality on entities of that type.

The "limited" package A package which prevents client packages from seeing the structure of an exported type, and which

42

does not allow client packages to perform assignments and test for equality/inequality on entities of that type.

The "opaque" package A package in which limited private types are implemented as pointers, but the type of the bound element is not revealed to users of that package.

The "closed" package The type declaration is visible only within that package. This implies that all entities of this type are visible only in this package. Modula-2 allows the supplier module less control over the visibility of an exported type. With Modula-2, an exported type can be either transparent or opaque. Modula-2's transparent type is similar to Ada's open type allowing client modules to make use of knowledge of the structure of the imported type. Modula-2's opaque type allows client modules to test for equality and to perform assignment statements on variables of this type. The only detail that is available to client modules about the structure of an opaque type is that the type is a pointer type. The structure of the bound type is invisible to the client module. In addition to the exported types, Modula-2 allows a supplier module to declare types that are visible only to it. These are similar to Ada's closed type. Oberon allows supplier modules to declare open and closed types similar to those in Modula-2 and Ada. However, Oberon provides a new form of type visibility in which a supplier module can make part of a type's structure visible to client modules while at the same time making part of the type private to the supplier module and hence invisible to all client modules. This form of type visibility is called °'semi-open". To declare a semi-open type in Oberon, the specification part of a module must contain a type declaration giving information on the visible part of the type. In the associated implementation part, the full type declaration is given including a redeclaration of the visible part. Wirth [17] indicates that to use semi-open types, compiler hints are necessary. (Some modifications to Oberon have been proposed to overcome the need for compiler hints [20].) The semi-open type makes the provision of private and opaque types unnecessary, as the semi-open type can be used in their stead. To simulate a private type by means of a semi-open type, the specification part of the supplier module declares a type with no visible part; in the associated implementation part the type is fully declared. Oberon only provides semi-open types for record types. If the exported type is an array, then it is an open type. In order to further restrict the visibility of an exported array type, it would have to be made into an invisible field of a semi-open record. Oberon provides a facility by which new types can be created as extensions to existing types [17]. If the base type on which the new type is created has invisible fields, then the new type will also have these invisible fields. In order to access these fields, the client module creating the new type must rely on the supplier module to provide the necessary routines. Not all languages allow the kind of control over a type's visibility that Ada, Modula-2 and Oberon do. Fortran 8X for example, only allows types to have one of two forms of visibility; if the type is exported it is open, otherwise it is closed and private to the supplier module.

43

6

Visibility of Variables

Modules are often used as a means of encapsulating data. Some languages like CLU, do not allow a variable to be exported from a module, but other languages do. Ada and Modula-2 allow supplier modules to conceal variables or to export them. If a variable is exported, then its visibility is the visibility of its type. This makes it possible for a client module to modify the variable if its type is open or is of a predefined type. This easy access to exported variables coupled with a client module's right to modify the variable violates the information hiding criterion advocated by Parnas [13 ]. C++ and Eiffel addressed this problem. Eiffel allows its variables to be exported, but an exported variable is 'read-only'. Client modules cannot modify an imported variable except via routines that the supplier module provides. A module in C++ can share its variables by either making them public or by declaring a f r i e n d function. If a variable is made public its visibility is open, and all the dangers of the Ada/Modula-2 approach exist. C++ allows selected functions to have access to the private variables of a module, even though those functions are not part of the module. These functions are called f r i e n d functions. A f r i e n d funcdon has to be named in a module prefixed by the keyword f r i e n d , this then allows that function to use the private variables of that module. The f r i e n d function provide a means of implementing abstract data types with shared operations that has been suggested by Osterbye [11 ].

7

Principle of Uniform Reference

An important aspect of information hiding is that client modules should be oblivious to algorithmic details of the supplier module. This implies that client modules should not be aware of whether a given entity is implemented as a constant, variable or parameterless function. The principle of uniform reference states that client modules should reference all imported entities in the same way, thereby hiding as much as possible knowledge about how a particular entity has been declared. C++ and Modula-2 use a different notation to reference a function than they do to reference a variable or constant. In Ada, the method of referring to variables, constants and parameterless functions is the same. However, i.f a client module imports a variable it is likely to make use of the kaaowledge that it is a variable. Eiffel is the only language discussed here that adheres to the principle of uniform reference. The method of referencing an imported entity is the same regardless of the type of entity. In addition, all imported entities can only be accessed and not modified, so even if the imported entity is a variable, it can only be used in the same way as a constant or function.

44

8

Conclusion

The module construct provided by Eiffel is good from the point Of view of information hiding, supporting as it does restricted export and allowing imported variables to be read-only. C++'s module is good for providing abstract data types with shared operations. The module construct in these languages is aimed at providing abstract data types, but it is not so good as a general module. Languages like Ada and Modula-2, that provide a module in two physical parts (specification and implementation), emphasise the client and supplier roles. The implementor (supplier) of a module need only write the implementation part, the specification part can be written by other programmers and can constitute the specification document for a module. This is not possible in the languages that provide modules that is only in one part, as the specification part of such modules generally only consists of the names of the exported entities. The module construct in data abstraction languages has been shown to be of value in the objectoriented programming languages, where inheritance is used. Oberon has shown that the general module can also be used to extend an existing type to create a new type, but as the routines are not part of the type, as they are in the data abstraction languages, it is not as useful from the information hiding view point. Clients of a module that used an extended type would have to be aware of all the modules in which the parent types are declared in order to access or modify the private part of the type.

9

Acknowledgements

I would like to thank Barry Cornelius and Linda Rising for helpful comments on earlier versions of this paper.

References [1] Reference Manual for the Ada Programming Language. 1983. ANSI/MIL-STD 1815A. [2] Working Draft: Programming Language Extended Pascal. October 1986. ISO/TC97/SC22/WG2 N100. [3] Fortran 8X Draft. May 1989. Also appears as Fortran Forum, vol. 8, no. 4, December 1989. [4] Data Processing - - Programming Languages - - SIMULA. May 1987. Svensk Standard SS 63 61 14. [5] Cornelius, B.J., "Problems with the Language Modula-2," Sofw,,are- Practice and Experience, vol. 18, no. 6, pp. 529-543, June 1988. [6] Dahl, O.-J. and Hoare, C.A.R., "Hierarchical Program Structures," in Structured Programming, London: Academic Press Inc., 1972.

45

[7] Dahl, O.J., Myhrhaug, B., and Nygaard, K., The Simula 67 Common Base Language. Forskningsveien, Oslo, 1970.

[8] Goldberg, A. and Robson, D., Smalltalk-80: The Language and its Implementation. Reading, Massachusetts: Addison-Wesley Publishing Company, 1983. [9] Liskov, B., Atkinson, R., Bloom, T., Moss, E., Schaffert, J.C., Scheifler, R., and Snyder, A., CLU Reference Manual. New York: Springer-Verlag, 1980. [10] Meyer, B., Object-Oriented Sofn~'are Construction. Prentice Hall International, 1988.

[11] Osterbye, K., "Abstract Data Types with Shared Operations," SIGPLAN Notices, vol. 23, no. 6, pp. 91-96, June 1988. [12] Parnas, D.L., "In.formation Distribution Aspects of Design Methodology," in Proceedings of the IFIP Congress- 1971, pp. 339-344, North-Holland, 1972.

[13] Parnas, D.L., "On the Criteria to be used in Decomposing Systems into Modules," Communications of the ACM, vol. 15, no. 12, pp. 1053-1058, December 1972. [14] Ross, D.L., "Classifying Ada Packages," ACM SIGAda Ada Letters, vol. VI, no. 4, no. 4, pp. 53--65, 1986. [15] Stroustrup, B., The C++ Programming Language. Reading, Massachusetts: Addison-Wesley, 1986. [16] Wirth, N., Programming in Modula-2. New York: Springer-Verlag, third corrected ed., 1985.

[17] Wirth, N., "Type Extensions," ACM Transactions on Programming Languages and Systems, vol. 10, no. 2, pp. 204-215, April 1988.

[18] Wirth, N., "From Modula to Oberon," Software--Practice and Experience, vol. 18, no. 7, pp. 661670, July 1988. Also appears as a technical report from ETH in September 1987. [19] Wirth, N., '`The Programming Language Oberon," Software - - Practice and E.179erience, vol. 18, no. 7, pp. 671--690, July 1988. Also appears as a technical report from ETH in September 1987.

[20] van Delft, A.J.E., "Comments on Oberon," SigpIan Notices, vol. 24, no. 3, pp. 23-30, March 1989. [?t] van Kiet, L., The Module: A Tool for Structured Programming. PhD thesis, Swiss Federal Institute of Technology, Zurich, 1978. ETH Diss. Nr. 6153.

46

Suggest Documents