An algebraic approach to mixins and modularity? Davide Ancona and Elena Zucca DISI - Universita di Genova, Via Dodecaneso, 35, 16146 Genova (Italy) email: fdavide,
[email protected] Abstract. We present an algebraic formalization of the notion of mixin
module , i.e. a module where the de nition of some components is deferred . Moreover, we de ne a set of basic operators for composing mixin modules, intended to be a kernel language with clean semantics in which to express more complex operators of existing modular languages, including variants of inheritance in object oriented programming. The semantics of the operators is given in an \institution independent" way, i.e. is parameterized on the semantic framework modeling the underlying core language.
Introduction One of the major contributions of object oriented programming has been the discover of inheritance as primary mean for incremental software development. In object oriented languages, an heir class can extend the de nition of the parent class adding new methods, as well as rede ning old methods, overriding their preceding de nitions (sometimes the precedence is given to the parent, see [7]). Note that, since method de nitions can depend on other methods of the class, rede ning one method may actually change the whole class behavior (in other words, all the components must be \evaluated again"). That gives a very powerful tool from the point of view of software development, providing \modi ability" together with the more classical \composability". The problem of giving a clean semantics to this mechanism has been called the \self-reference" problem, and solved, roughly speaking, by interpreting a class as a function from objects (modeled as records) into objects; an instance is then obtained as the xed point of the class (see e.g. [8]). More recently, two new considerations about overriding have emerged, important for identifying the essence of this mechanism and the relationship between inheritance in the object oriented paradigm and the more general (and much older) notion of composition of modules. The rst consideration is that a dierent view of overriding can be considered, introducing the notion of abstract heir class , sometimes called mixin : intuitively, a class where some methods are not de ned (deferred ), and which can be eectively used for instantiation (i.e. become concrete ) only when applied to some parent class, which supplies an implementation for the deferred methods. ? This work has been partially supported by WG n.6112 COMPASS, Murst 40% -
Modelli della computazione e dei linguaggi di programmazione and CNR, Italy.
Introducing mixins, overriding a method de nition in a class by a new de nition can be seen as the composition of two dierent operations: rst, the old de nition is canceled, obtaining an abstract class (mixin) in which the corresponding method is deferred; then, the abstract class is instantiated over the new de nition. This view of overriding has been rstly introduced, at our knowledge, in [6], where the operator which \cancels" a de nition is called restrict . Note that in this way it is possible to replace an asymmetric (non commutative) binary operator, i.e. overriding, by restrict plus a commutative merge operator between two mixins. This is the approach we adopt in this paper. The second, more important consideration is that the notion of mixin is completely independent of the object and class notions, and can be formulated in a much more general way in the context of module composition. In other words, it is possible to extend the usual notion of module (a collection of de nitions) to the case where some de nitions are deferred (components are declared but not de ned), obtaining what we call in this paper a mixin module (or simply mixin). Following this intuition, [6] proposes a set of module operators (called Jigsaw ) which provides a framework for modularity independent of a particular computational paradigm. In other words, Jigsaw de nes a language of mixin modules parameterized by the programming language used for de ning module components, i.e. the core language following the terminology of Standard ML [13]. In this paper, we aim at providing rigorous semantic foundations for this approach. We believe the semantics of a language of mixin modules should be analogously given in a two-level way, hence we de ne an interpretation of the module operators independent of the semantic nature of a single module, which depends on the (semantics of) the underlying core language. We think a simple and natural way for achieving that is to work within the theory of institutions ([11]), simply extending to the case of mixins the well-established algebraic treatment of module composition (see, e.g., [4, 9]). In this approach, syntactic interfaces of modules are modeled by signatures of some institution , and denotations of program modules are models of ; for instance, in a standard case, many-sorted algebras (collections of related functions together with the data domains they operate on). More complicate models are needed for dealing with features like e.g. polymorphism, higher-order functions, in nite behavior, state (see [3] for this last case). We refer to the recent informal overview in [14] for a detailed discussion about that. We do not deal explicitly in this paper, which is only concerned with programming modules, with the logical part of the institution concept (a language of sentences for expressing properties that models are required to satisfy); anyway, it should be clear to the reader that the possibility of integration with a speci cation language is an important motivation behind our approach. In order to extend the algebraic approach to mixins (modules with deferred components), we follow the idea in [8] and model a mixin as a function from models into models. Thus our work can be seen in two symmetric ways: from one side, we generalize the model of inheritance in [8] from objects (modeled as records) to modules denoting arbitrary semantic structures; from the other side, I
I
we present a kernel module language with algebraic semantics, in the spirit of the seminal paper [4], but considering, together with classical operators like export and renaming, new operators allowing module modi cation which would make no sense in the traditional model. This work is part of some ongoing research on algebraic foundations of \advanced" features in modularity. In [3], we have faced the problem of handling imperative features. The paper is organized as follows. In sect. 1 we present informally the most interesting operators of our module language giving some examples written in an ML-like language. In sect. 2 we de ne the semantic ingredients we need in the underlying semantic framework, and present our formal model of mixins. In sect. 3 we give typing rules and semantic clauses for the module operators with corresponding soundness results. Finally, in sect. 4 we outline our further work on this subject. An extended presentation of the ideas in this paper, including algebraic laws specifying properties of the composition operations, is given in [2].
1 Mixins and mixin operators: an informal introduction In this section, we introduce the notion of mixin module and the most relevant composition operators, by means of some examples written in Standard ML ([13]). We recall that ML supports the de nition of module (structure ), interface (signature ) and parameterized (or generic) module (functor ). We have adopted Standard ML for its simplicity; however, it should be clear to the reader that our intention here is not to extend a particular modular language with mixins, but to propose a basic set of operators as in [3]. From concrete modules to mixins. Assume that we want to implement in ML
nite maps and nite sets of integers, with some usual operations (empty map, application, updating a map by a new association, getting the domain of a map, restricting the domain of a map, empty set, adding an element to a set, membership test). A structure Map implementing nite maps should match the following signature MAP: signature MAP = sig type map; type set; val empty map:map; val apply: map int ! int; val update: map int int ! map; val def dom: map ! set; val restrict: map set ! map end;
For a modular development, the implementationof the type set should be provided separately by a structure Set over the signature SET: signature SET = sig type set;
val empty set:set; val add: int set ! set; val is in: int set ! bool end;
That can be achieved in ML de ning Map no longer as a structure, but as a functor from structures matching SET to structures matching MAP. However, this solution is still inadequate, since there is no way to implement the function restrict in terms of the functions in SET; intuitively, we miss the possibility of \iterating" an action over all the elements of a set. Conversely, it is not possible to de ne Set as a functor taking Map as parameter, since there is no way to implement the function def dom in terms of the functions in Map. Of course we could handle the problem adding new primitives, but that solution implies extra code and could lead to an inecient implementations of restrict (def dom, respectively). On the other side, the restrict and def dom functions could be eciently de ned inside a unique structure, loosing modularity. A solution which keeps both modularity and eciency would be to move restrict from Map to Set. However, in this way the two structures become mutually dependent, while functors in ML are only able to express one-way dependencies. This problem is overcome by the introduction of mixin modules . A mixin, like a functor, is a module which depends on other modules; the crucial dierence is that by mixins it is possible to express mutual dependency. In an hypothetical extension of ML with mixins, the solution of our problem is the following: mixin Map = mix datatype map = empty map j update of map int int; type set; val empty set:set; val add: int set ! set; val restrict: map set ! map; fun apply(update(f,x,y),z) = fun def dom(empty map) = empty set j def dom(update(f,x, )) = add(x,def dom(f)) end;
:::
mixin Set = mix datatype set = empty set j add of int set; type map; val empty map:map; val apply: map int ! int; val update: map int int ! map; val def dom: map ! set; fun is in( ,empty set) = j fun restrict( ,empty set) = empty map j restrict(f,add(x,s)) = if is in(x,def dom(f)) then update(restrict(f,s),x,apply(f,x)) else restrict(f,s) end;
::: :::
As shown by the example, a mixin diers from a standard module, which is a collection of de nitions, since the implementation of some components may be deferred , as it happens for the type set and the functions empty set, add and restrict in Map. In this paper, we call de ned a component whose de nition is given inside the module, declared a component whose de nition is deferred. Correspondingly, a mixin signature diers from a standard signature since each component is labeled as either de ned or declared. Note that in general it is not possible to split the signature of a mixin in two disjoint signatures corresponding to the de ned and declared components, respectively. For instance, in Set, the declared components (map, empty map, apply, update, def dom) do not form a signature, since the functionality of def dom contains the de ned type set. Modules in the usual sense can be viewed as particular mixin modules in which all the components are de ned. We call them concrete mixin modules. Of course a mixin module, e.g. Map, which is not concrete, cannot be used in isolation, exactly like a parameterized module which needs to be instantiated over an actual parameter. A way to combine mixins, in order to obtain eventually a concrete module, is provided by the merge operator [6]: if M1 and M2 are two mixins, then M1 + M2 is a mixin where some de nitions of M1 are associated with corresponding declarations in M2 and conversely. This operator is commutative and is de ned whenever no components are de ned on both sides. In the example above, we can de ne a mixin SetAndMap = Set+Map matching the signature SET AND MAP = SET+MAP; in SetAndMap every component has a de nition, hence we have obtained a concrete module (an usual ML structure). This is not always the case: if some declared component on one side has no corresponding de nition on the other side, then the module resulting from merging is still not concrete. Overriding de nitions. In object oriented languages, inheritance allows overriding of de nitions with precedence of either the heir (as e.g. in Smalltalk) or the parent class (as in Beta), as has been analyzed in [7]. Introducing the notion of mixin, overriding the de nition of a component, say f = expr , in a module M , by a new de nition f = expr 0 , can be seen as the composition of two operations: rst, the old de nition is canceled, getting a module M 0 where f is declared (deferred); then, M 0 is merged with f = expr 0 . This view allows to consider a more general version of overriding. Consider, for instance, the following mixin, which leaves deferred the implementation of lists, and only de nes two derived functions: mixin AbsList = mix type int list; val head: int list ! int; val tail: int list ! int list; val is empty: int list ! bool; fun length(l) = if is empty(l) then 0 else length(tail(l))+1; fun eq(l1,l2) = if is empty(l1) then is empty(l2) else if is empty(l2) then false else
head(l1)=head(l2) andalso eq(tail(l1),tail(l2)) end;
and assume that we want to combine it with another mixin where an implementation for the type int list is provided, together with a more ecient version of length based on this implementation. mixin List = mix type int list = int int list; fun length(n, ) = n;
:::
end;
The sum AbsList + List is not correct, since the function length is de ned in both the modules. We need a way of explicitly specifying which of the two de nitions of length must take the precedence. This can be achieved by using a restrict operator (see [6]), which allows to cancel de nitions inside a mixin. This operator takes two arguments, a mixin M and a mixin signature obtained from that of M changing the label of some component from de ned to declared. Let RESTRICT be the mixin signature obtained from that of AbsList relabeling length as declared; then AbsList and List can be combined as follows: mixin AbsListAndList = (restrict AbsList to RESTRICT)+List.
In general the restrict operation allows to specify, for each pair of con icting de nitions in two mixins M1 and M2 , which of the two de nitions must take the precedence. In this way we are able to de ne more combinations of M1 and M2 than simply using an overriding operator which gives precedence to either M1 or M2. This is essential, for instance, in object oriented languages supporting multiple inheritance where an heir class may inherit a method de nition from several parent classes (see [6]). The inheritance operators of Smalltalk and Beta (without considering the pseudovariables super and inner) can be de ned by an appropriate combination of the merge and restrict operations: H = (restrict P to P ) + M (in Smalltalk), H = P + (restrict M to M ) (in Beta), where H and P are the heir and the parent class, respectively, M is the mixin corresponding to the new de nitions, P is obtained from the signature of P making declared the components rede ned in M , and analogously for M . From the semantic point of view, the possibility of overriding de nitions leads one to consider two dierent interpretations of a concrete module, which we call closed and open semantics , respectively. Consider for instance the structure: structure S = struct val i1=1; val i2=2*i1; val i3=i1+i2 end;
The closed semantics of S associates a semantic value with each component (in this case the values 1, 2, 3 with i1, i2, i3, respectively); formally, a model over a signature def in some institution. Anyway, this semantics does not take into account the possibility of later rede nitions of components. For instance, rede ning i1 to 2 changes the values of i2 and i3, too. In the general case
de nitions can be mutually recursive. In order to model that, it is necessary to see S as a function from models over def to models over def (in this case, for any triple of values for i1, i2, i3, new values for them are de ned). The closed semantics is intuitively the \ xed point" of the open semantics, in a sense to be made precise depending on the framework (institution) we consider. The open semantics can be naturally extended to non concrete mixins (where some components are declared), seeing them as functions from models over a larger signature to models over def . Of course in this case it makes no sense to consider the closed semantics, correspondingly to the fact that the module cannot be eectively \used" as it stands. Hiding and freeze. The restrict operation allows to transform a component from
de ned into declared, but does not allow to forget components. In order to do that, we need an export/hiding operation. Hiding a de ned function f in a mixin intuitively means to consider f as a locally declared function. For instance, assume to hide i2 in the structure S shown above. We expect to obtain a structure equivalent to the following2: structure S1 = struct val i1=1; local val i2=2*i1 in val i3=i1+i2 end end;
Since the de nition of i2 is now local to S1, no operation on S1 can modify its value; if we merge S1 with a mixin which de nes a component i2, then the value of i3 does not change. As a matter of fact, we have canceled the dependence of i3 on the visible component i2. In some cases, it is desirable to achieve the same eect without hiding i2 (see, for instance, non virtual member functions in C++) by means of a freeze operation [6]. The freeze operation does not change the signature of a mixin, but allows to eliminate the dependence on some de ned component. As an example assume to freeze i2 in S; then the structure S2 we obtain is equivalent to the following: structure S2 = struct val i1=1; val i2=2*i1; local val i2=2*i1 in val i3=i1+i2 end end;
In other words, we get a structure whose open semantics is a function constant w.r.t. the component i2. Freezing all the three components, the open semantics becomes a constant function (returning the closed semantics). Note that it would make no sense to freeze a declared component.
2 Mixin signatures and models In our language of mixin modules, any expression has a type, called a mixin signature , modeling the syntactic interface of the module, and is interpreted as a semantic structure called a mixin model . The de nition of mixin signatures and 2
To be precise, i2 should be a local variable visible to all de nitions in S; this cannot be expressed in ML, since the de nition of i2 depends on i1. However, in this case, i2 can equivalently be local to the de nition of i3, since i1 does not depend on i2.
models and the interpretation of the operators of the language is parameterized by the semantic framework modeling the core level. We call this framework core framework and in this section we give its formal de nition. Roughly speaking, a core framework is a specialization of the notion of model part of an institution (i.e. a category of signatures and a model functor, see [11]); the added features are listed below. { There is a notion of inclusion between signatures with related operations of union, intersection and dierence. That is needed for modeling composition of interfaces, like e.g. merging two interfaces or hiding some components; formally, we adopt a variant of the requirements de ned in [3], based on the notion of inclusion systems [9]. { We consider concrete model parts in the sense that models are (sorted) sets enriched by some structure, like operations in the case of standard algebras; correspondingly, signatures have a set of sorts . In other words, the category of -models, for any signature , is a concrete model category (see [5]), i.e. a particular case of concrete category (see [1]); the same notion has been called static framework in a context where the aim was to enrich such a framework by dynamic features (see [3, 15]). The choice of working with concrete model parts corresponds to consider modular languages in which there is a distinguished subset of the components of a module, the type components, which need to be handled in a dierent way from other components. For instance, type de nitions cannot be overridden, since the well-formedness of other de nitions may rely on type information given by them. We refer to [2] for a formulation of our approach working with arbitrary model parts. { Finally, given a mixin module in which all the components are de ned (a concrete module), we need an operator which extracts, from the open semantics of this module (roughly speaking, a function from models into models), the corresponding closed semantics (the model which intuitively corresponds to the \ xed point" of this function).
Notation. If C is a category, then jCj denotes the class of its objects. De nition1. A signature category is a pair <Sig; I > where { Sig is a category whose objects are called signatures and I is a subcategory of Sig with jIj = jSigj which is a distributive lattice; we call the morphisms in I inclusions , use the notation if there is an inclusion from 1
2
1
into 2 , and denote this (unique) inclusion by i ; ; moreover, we call union (denoted by 1 [ 2 ) and intersection (denoted by 1 \ 2 ), respectively, the join and the meet of 1 and 2 in I ; { for any 1 ; 2 2 jSigj s.t. 1 2, there exists the g.l.b. of the set f j 1 [ = 2 g, denoted by 2 n 1 and called the dierence of 2 and 1 . The pair <Sig; I > is denoted simply by Sig when there is no ambiguity. In the de nition of inclusive categories (see [9, 3]), which is the basis of the above de nition, a property of unique factorization of any morphism as an epimorphism 1
2
composed with an inclusion is required, which corresponds to the intuition that any morphism de nes an \image" object included in the codomain; anyway, this property is unnecessary for the following technical treatment. The category Set of small sets, with I the set inclusions, is a trivial example of signature category. We de ne now a sorted signature category as a particular kind of signature category, where a signature has a set of sorts , and conversely a set of sorts can be seen as a particular (empty) signature.
De nition2. A sorted signature category is a pair <Sig; Sorts >, where Sig is a signature category and Sorts : Sig ! Set is a functor s.t. { Sorts preserves inclusions, union and intersection; { Sorts has a right inverse denoted by ;? ; { for any 2 jSigj, if S Sorts () then ;S ; { for any 1 ; 2 2 jSigj s.t. 1 2 , 1 \ (2n1) = ;Sorts( )\Sorts( n ) . If 2 jSigj with Sorts ( ) = S , then we say that is over the set of sorts S ; ;S is called the empty signature over S . 1
2
1
The category of algebraic many-sorted signatures (a signature over S is a S S family of symbols), with the obvious de nitions of inclusions, union, intersection, dierence, sort functor and empty signature is an example of sorted signature category. Note that Sorts is not required to preserve dierence: indeed in usual cases such as algebraic many-sorted signatures mentioned above it is not true that Sorts (2 n1 ) = Sorts (2 )nSorts (1 ), since operations which are not in 1 may be over sorts in Sorts (1 ). As a consequence, 2 is not the disjoint union of 1 and 2 n1 , as for sets, but the union with sharing of the common sorts.
De nition3. A concrete model part is a 4-tuple <Sig; Sorts; Mod ; j?j> where { <Sig; Sorts> is a sorted signature category; { Mod is a functor, Mod : Sigop ! Cat; for any signature , objects in Mod () are called models over or -models ; for any signature morphism : ! 0 , Mod () is called the reduct functor and denoted by ?j ; we write ?j for ?ji ; if ; { j?j is a natural transformation, j?j: Mod ! SSet Sorts op , s.t., for any , the functor j?j is faithful; for any -algebra A, jAj is called the carrier of A, and denoted by jAj or even A when there is no ambiguity. 1
1
2
1
2
Above and in what follows, SSet denotes the functor giving, for any set of sorts S , the category of S -sorted sets. The assumption that j?j is faithful (i.e. for all parallel morphisms f; g: A ! B , if jf j = jgj then f = g) models the fact that morphisms of models are maps which respect some condition. De nition4. A concrete model part is regular i
1. for any 2 jSigj, j?j is surjective on objects; moreover, for any S 2 jSetj, j?j;S is an embedding (hence is bijective on objects); 2. for any A1 2 jMod (1 )j; A2 2 jMod (2 )j, whenever A1j \ = A2 j \ , there exists a unique A 2 jMod (1 [ 2 )j, denoted by A1 + A2, s.t. Aji = Ai ; i = 1; 2. These two conditions are necessary for the well-de nedness of the interpretation of module operators (see the proofs in [2]). The rst condition ensures that, for any signature over S , for any S -sorted set X there exists a -model having X as carrier and that models over an empty signature are essentially sorted sets. The second condition ensures that it is possible to \sum" two models which coincide over the common signature. Note that A1 + A2 is what is usually called the amalgamated sum of A1 and A2 (see e.g. [10]); i.e., a regular concrete model part must satisfy the amalgamation property restricted to inclusions. Note also that SSet can be considered as a regular concrete model part where Sig = Set, Sorts is the identity functor, Mod = SSet and j?j is the natural identity; in fact, it is easy to verify that in this case the properties 1 and 2 hold. De nition5. A core framework is a tuple <Sig; Sorts ; Mod ; j?j; x > where { <Sig; Sorts; Mod ; j?j> is a regular concrete model part; { x is a family of functions xX ; : (Mod X ( ) ! Mod X ( )) ! Mod X ( ) indexed over pairs <X; > with signature over S , X 2 jSSet (S )j, where Mod X ( ) denotes the class of -models A s.t. jAj = X . The name x has been chosen to suggest that, in concrete cases, each xX ; should correspond to some kind of \ xed point" operator. Anyway, no properties on x are required for the aims in this paper. In order to prove algebraic laws, (universal) properties should be attached to the xpoint construction (see [2]). A concrete example of core framework is given by many-sorted partial algebras over algebraic many-sorted signatures. Indeed, partial algebras are pairs consisting of a sorted set together with the interpretation of the operation symbols, hence the carrier is simply obtained taking the rst component; moreover, an homomorphism between algebras is a map between their carriers compatible with operations. Then, a function F : Mod X ( ) ! Mod X ( ) transforms tuples of partial functions into tuples of partial functions, and xX ; (F ) is expected to be the least xed point of F , whenever F is continuous. We give now the formal de nition of mixin signatures and models. From now on, we assume a xed core framework <Sig; Sorts ; Mod ; j?j; x >. De nition6. A mixin signature is a triple , where and def are two signatures over the same set of sorts, say S , with def , and S def S . We call and def the full and the de nition signature, respectively, and S def the set of de ned sorts . Moreover, we call n def and S nS def the declaration signature and the set of declared sorts , and denote them by dec and S dec , respectively. 1
2
1
2
Intuitively, a mixin signature models the syntactic interface of a mixin module. The full signature gives all the components which are visible to the outside, either de ned or declared. The de ned components (modeled by def ) are either type components (elements of S def ) or other components, e.g. functions. Note that de ned components may also involve declared types, as it has been shown in the mixin Map in sect. 1; formally, def is a signature over the full set of sorts S .
De nition7. A mixin model over a mixin interface is a pair
<X; F>, where { X 2 jSSet (S def )j; { F is a family of functions, FY : Mod X +Y ( ) ! Mod X +Y ( def ), indexed over Y 2 jSSet (S dec )j. Intuitively, the rst component is the semantic counterpart of the type de nitions in a module. For sake of simplicity, in this paper we do not consider type de nitions depending on declared type components; the extension to mutually recursive types is straightforward (see [2]). The second component is the semantic counterpart of de nitions of other components (e.g. functions); these de nitions give a semantic value to the de ned components (formally, a model over def ), once a semantic value has been provided for declared types (formally, a sorted set Y over S dec ) and for other declared components (formally, a model over dec ). Anyway, since the de ned symbols may be rede ned, it is necessary to keep the information on how each de nition depends on the others; hence, a model over the full signature = def [ dec must be provided as parameter. In the particular case in which S dec = ;; = def (a concrete module, where all the components are de ned), it is possible to get the -model corresponding to the closed semantics of the module by means of the x operator.
3 Operations on mixins In this section, we give the formal de nition of the operators for composing mixin modules. For each operator, we rst give a typing rule specifying compatibility conditions between the types (interfaces) of the arguments, and the resulting type of the result; then, we give the semantic interpretation of the operator and we prove that, if the arguments satisfy type conditions, then the semantics is well-de ned and gives a result of the expected type. Merge. The merge operation allows one to \sum" two mixin modules, say M1 and M2 , obtaining a new module where some declared component of M1 is concreted by the de nition given in M2 and conversely. Two mixin modules can be merged together only if no components are de ned in both. The de ned components of the sum are the components de ned in one mixin and either declared or not present in the other; the declared components of the sum are the components
declared in one (but not de ned in the other) or both the arguments. Note that thedefcondition which forbids con icting de nitions cannot simply be expressed by 1 \ 2def = ;; ; referring to the example in sect. 1, the de ned signatures of Map and Set share the sorts map and set. (M)
S1def \ S2def = ; 1def \ 2def = ;S S = Sorts (1 )\ Sorts (2 )
Mi : i = 1; 2 M1 + M2 : def
def
De nition8. The semantics of the merge operation is de ned as follows: if
[ Mi ] = <Xi ; Fi>, i = 1; 2, then [ M1 + M2 ] = <X; F> where { X = X 1 + X2 ; { FY = A 2 Mod X+Y (1 [ 2):F1(X+Y )jSdec (Aj ) + F2(X+Y )jSdec (Aj ), for any Y 2 jSSet (S dec )j. 1
1
2
2
Intuitively, the de nitions in M1 + M2 are obtained taking the union of the de nitions of M1 and M2 ; no con ict can arise since no components can be de ned simultaneously in the two modules.
Proposition9. The semantics of merge is well-de ned w.r.t. typing rule (M). Restrict. The restrict operation allows one to \cancel" some de nitions in a module, making the corresponding components declared. Note that restrict is completely dierent from hiding (see below), since a component whose de nition is deferred remains in the interface of the module and could be rede ned later (indeed overriding can be obtained by composing restrict and merge), while an hidden component becomes not visible from the outside, hence cannot be rede ned. Type de nitions cannot be canceled, since the static correctness of other de nitions may rely on them. (RS)
M : M : j
0
def def Sorts ( def ) = Sorts ( def ) 0
0
De nition10. The semantics of the restrict operation is de ned as follows: if [ M ] = <X; F>, then [ Mj ] = <X; F 0> where FY0 = A 2 Mod X +Y ( ):(FY (A))j 0 def , for any Y 2 jSSet (S dec )j.
Intuitively, some de nitions are \forgotten" (this is formally expressed by the reduct functor). Note that, anyway, the corresponding components still are in the interface of the module as declared components.
Proposition11. The semantics of restrict is well-de ned w.r.t. typing rule (RS).
Freeze. The freeze operation allows one to make a module independent from
the rede nition of some de ned components, say fr . Freezing type components makes no sense since they are frozen by default (since in this paper we assume that type de nitions cannot depend on deferred types; for a more general approach see [2]). M : fr def (F) Sorts ( fr ) = Sorts ( def ) freeze fr in M : De nition12. The semantics of the freeze operation is de ned as follows: if [ M ] = <X; F>, then [ freeze fr in M ] = <X; F 0> where FY0 = A 2 Mod X +Y ( ):FY (Ajn fr + C ), for any Y 2 jSSet (S dec )j, where C is xX +Y ; fr (B 2 Mod X +Y ( fr ):(FY (Ajn fr + B ))j fr ). The semantics of a mixin module where the fr -components have been frozen is a function constant w.r.t. to these components (as stated in prop. 13); indeed, all other components refer to the values of fr -components as they are determined by the current de nitions (formally, the fr -model C in the de nition).
Proposition13. The semantics of freeze is well-de ned w.r.t. typing rule (F). Moreover, for any Y 2 jSSet (S dec )j, for any A; B 2 Mod X Y ( ), if Ajn fr = +
Bjn fr then FY0 (A) = FY0 (B ). Export. The export operation allows one to hide some de ned components from the outside. Hiding declared components makes no sense since de nitions of other components could depend on them and in this way it would be impossible to obtain eventually a concrete module. This is formally expressed by the last two side conditions, which state that the new interface diers from the original only for de ned components. ;S def S def def def M : (E) def def ;S dec = S dec M : n = def n def 0
0
0
0
jj
0
0
0
0
0
where is a mixin signature. De nition14. The semantics of the export operation is de ned as follows: if [ freeze def n 0 def in M ] = <X; F>, then [ Mjj ] = <X 0 ; F 0> where { X 0 = XjS 0def ; { FY0 = A 2 Mod X 0 +Y ( 0 ):(FY (A + B))j 0def , for some B 2 Mod (X +Y )jSorts n0 ( n 0 ), for any Y 2 jSSet (S 0 dec )j. Hiding type components means just to forget them. On the contrary, hiding other components (i.e. components which other de nitions may refer to), requires rst freezing them, in such a way that all the other de nitions will refer from now on to their current de nitions. (
)
Proposition15. The semantics of export is well-de ned w.r.t. typing rule (E).
Renaming. The renaming operation allows one to change the names of both
de ned and declared components. We assume that renamings are bijective over de ned components since otherwise in the new module there would be for some component either no de nition or more con icting de nitions. def ;r: ! def def M : def def def (RN) r ? M : rr i: def != i 0 def iso0 s.t. rdef 0
0
0
0
0
0
;
;
Note that, in order to express the informal assumption above, the requirement that rdef must be an isomorphism could be relaxed since it is not necessary for the restriction of Sorts (r) to S dec to be bijective; here we prefer to consider this slightly simpli ed version.
De nition16. The semantics of the renaming operation is de ned as follows: if [ M ] = <X; F>, then [ r ? M ] = <X 0 ; F 0> where { X 0 = XjSorts(rdef )? ; { FY0 = A 2 Mod X 0+Y ( 0 ):(FYj Sorts r jSdec (Ajr ))jrdef ? , for any Y 2 jSSet (S 0 dec )j; 1
(
( )
)
1
Proposition17. The semantics of renaming is well-de ned w.r.t. typing rule
(RN).
4 Conclusion and further work We have presented a set of basic operators for composing mixin modules, intended to be a kernel language with clean semantics in which to express more complex operators of existing modular languages. The semantics of the operators is given in an \institution independent" way, i.e. is parameterized by the semantic framework modeling features of the underlying core language (called core framework in this paper). The immediate continuation of this work is in, at least, two directions. From one side, the detailed development of our semantics in a concrete case; from the other side, the formulation of the algebraic laws holding between operators (e.g. commutativity and associativity of merge), in the spirit of [4] (see [2]). That could imply some re nement of the requirements on the core framework, in order to guarantee equalities that we expect to hold (for instance, idempotency of the freeze operator holds only if the x operator actually veri es a xed point property). Finally, an interesting topic for further work is an analysis of the relation between mixin and parameterized modules (like ML functors). Indeed, at a rst look the application of a parameterized module to an argument seems to be a particular case of merge between two mixin modules M1 and M2 in which one, say M2 , is concrete, and the only deferred de nitions in M1 are concreted in M2 , making the sum in turn a concrete module (in other words, the dependency between the
two modules is only in one direction). A formalization of this point of view could clarify the relation between inheritance and genericity, which has been a topic of great debate in the object oriented community (see, e.g., [12]).
Acknowledgements. We would like to thank the anonymous referees for their useful comments and suggestions.
References 1. J. Adamek, H. Herrlich, and G. Strecker. Abstract and Concrete Categories. Pure and Applied Mathematics. Wiley Interscience, New York, 1990. 2. D. Ancona and E. Zucca. A theory of mixin modules. In preparation. 3. D. Ancona and E. Zucca. A formal framework for modules with state. In AMAST '96, LNCS, 1101, pages 148{162, 1996. Springer Verlag. 4. J.A. Bergstra, J. Heering, and P. Klint. Module algebra. Journ. ACM, 37(2):335{ 372, 1990. 5. M. Bidoit and A. Tarlecki. Behavioural satisfaction and equivalence in concrete model categories. In CAAP '96, LNCS, 1059, pages 241{256, 1996. Springer Verlag. 6. G. Bracha. The Programming Language JIGSAW: Mixins, Modularity and Multiple Inheritance. PhD thesis, Dept. Comp. Sci., Univ. Utah, 1992. 7. G. Bracha and W. Cook. Mixin-based inheritance. In ACM Symp. on ObjectOriented Programming: Systems, Languages and Applications 1990, pages 303{311. ACM Press, October 1990. SIGPLAN Notices, 25, 10. 8. W. Cook. A Denotational Semantics of Inheritance. PhD thesis, Dept. Comp. Sci. Brown University, 1989. 9. R. Diaconescu, J. Goguen, and P. Stefaneas. Logical support for modularisation. In Gerard Huet and Gordon Plotkin, editors, Logical Environments, pages 83{130, Cambridge, 1993. University Press. 10. H. Ehrig and B. Mahr. Fundamentals of Algebraic Speci cation 1. Equations and Initial Semantics, volume 6 of EATCS Monograph in Computer Science. Springer Verlag, 1985. 11. J.A. Goguen and R.M. Burstall. Institutions: Abstract model theory for computer science. Journ. ACM, 39:95{146, 1992. 12. B. Meyer. Genericity versus inheritance. In ACM Symp. on Object-Oriented Programming: Systems, Languages and Applications 1986, pages 391{405. ACM Press, November 1986. SIGPLAN Notices, 21, 11. 13. R. Milner, M. Tofte, and R. Harper. The De nition of Standard ML. The MIT Press, Cambridge, Massachussetts, 1990. 14. D. Sannella and A. Tarlecki. Model-theoretic foundations for formal program development: Basic concepts and motivation. Technical Report ICS PAS 791, Inst. Comp. Sci. PAS, Warsaw, 1995. 15. E. Zucca. From static to dynamic abstract data-types. In Mathematical Foundations of Computer Science 1996, LNCS, 1113, Berlin, 1996. Springer Verlag.