The Acme design language's type system provides a mechanism that designers can use to capture abstract design vocabulary specifications. These specifications can be used both to create instances of design elements and to verify that an instance of a design element satisfies the design constraints specified by the type.
The Acme type system serves a significantly different purpose than the type systems typically provided by programming languages. Programming language type systems are generally designed to provide statically-checkable guarantees of run-time program behavior (e.g. to insure that a function will not accidentally attempt to add a floating point value to an array of strings). Acme's type system, on the other hand, provides a form of checkable redundancy that assures the design constraints for a given type of design vocabulary are satisfied where that vocabulary is used. The type system provides a mechanism for ensuring that the system’s fundamental design constraints are not violated as a design evolves over time (e.g. through system maintenance, upgrades, etc.).
To achieve these goals, Acme uses a predicate-based type system that supports the expression of complex type constraints and invariants. Type expressions are predicates that elements can satisfy. A type definition determines a set of design elements¾those that satisfy the type's predicate. An element that satisfies type T’s predicate is said to satisfy type T. A computationally-decidable predicate language (described in detail in chapter 4) is used to ensure that complex type constraints can be mechanically checked.
The Acme type system supports two broad categories of type expressions¾design element types (component, connector, port and role types) and property types (primitive, compound, and aliased property types). The type system used for design elements supports both a subtyping facility and the specification of rich constraints on the structure and properties of design elements. The property type system, on the other hand, is significantly simpler than the design element type system. The property type system allows only simple predicates whose primary purpose is to specify the structure used for storing property values. It does not support subtyping or rich constraints on property values.
This section provides an overview of the syntax, common usage, and semantics for the design element type system. The following section provides a similar overview of the property type system.
A design element type can specify two broad kinds of constraints on design elements. First, it can specify required structure and properties, possibly with default values. Second, it can specify explicit invariants and heuristics (predicates) that describe legal property and structure values of an element of that type. This section describes the syntax and semantics of component, connector, port and role types (referred to as design element types or just element types). Acme system types are referred to as architectural styles (or simply styles), and are discussed in a later section. Styles extend the capabilities of the design element types described in this section.
A detailed and rigorous specification of the syntax and semantics of element type declarations is given later in this chapter. For purposes of immediate discussion, the following informal description of the syntax and semantics of the Acme element type system is provided.
The informal syntax for declaring a design element type is:
<Category> Type <TypeName> = {
<Sequence of: required structure and values
| properties
| explicit invariants
| explicit heuristics >
}
In the informal syntax given above, <Category> can be any of the literals Component, Connector, Port, or Role, and <TypeName> specifies a valid identifier. The body of the type declaration consists of a sequence of constraints by which instances of this type must abide. Informally, the meaning of the four kinds of constraint declarations that can be made within a type declaration are described below:
· Required Structure. The structural declarations in a type description T define the substructure that an element e of type T (written e : T) must have. Informally, for every port, role, or representation defined in T, an instance e : T must have a corresponding port, role, or representation. The port, role, or representation defined in the instance must be defined with at least as much detail as its corresponding port, role, or representation in the type declaration. A more detailed specification of the semantics of required structure statements is given in table 3.1.
· Required Properties. A property pi declared in a type declaration T specifies that an element e : T must define the property pI. Further, if pI is declared to have a type and/or a value in T, pI declared in e : T must also have the same type and/or value. As with required structure, a more detailed specification of the semantics of property declarations is given in table 3.1.
· Explicit Invariants. In addition to the required structure and properties of a type, additional invariant constraints can be specified using Acme's Predicate Language (described in chapter 4). These invariants can specify ranges of valid values for properties, constraints on the types and number of substructure elements that an element of type T can have, and any other constraint that can be specified with the Acme Predicate Language. An element e : T must satisfy all of the invariant constraints defined in T in order to satisfy T's predicate (and thus satisfy type T).
· Explicit Heuristics use the same predicate specification language as explicit invariants. Unlike invariants, though, heuristics are not considered in determining whether an element e satisfies a type T. Violations of type heuristics can be flagged during constraint analysis or analyzed by external tools, if desired, but the heuristics themselves are not part of a type's predicate. The heuristics construct provides architects and designers with a way to capture design "rules of thumb" that are less strict than invariants.
An element e : T satisfies the type T's predicate if e contains all of the required structure and properties specified in T, and e satisfies all of the invariant predicates defined in T.
The following example shows a type specification that declares constraints that must be satisfied by all instances of the type in the form of required minimal structure and predicates that must be maintained. Keywords are indicated in boldface type, comments in greyed-text.
Component Type Client = {
// Declare the minimal structure that must exist. In this case, it says that an instance
// of this type must have a port called request, and that port must have the protocol
// rpc-client.
Port Request = { Property protocol : CSProtocolT = rpc-client };
// The next declaration says that a client must have a property of type "float" called
// "request-rate." It also provides a default value for that property, which can be
// changed when an instance of this type is created.
Property request-rate : float << default = 0.0 >>;
// Now specify the invariants that all elements that claim to satisfy this type must possess.
// all ports must support the rpc-client protocol
Invariant forall p in self.Ports · p.protocol = rpc-client;
// there may be no more than 5 ports on a client
Invariant size(self.Ports) <= 5;
// The request rate must be a non-negative value less than 100
Invariant request-rate >= 0;
// Specify a heuristic indicating the request rate should not exceed 100
Heuristic request-rate < 100;
}
Example 3.1: Declaring component type “Client”
The Client type specification imposes the following structural and invariant constraints on component instance C : Client:
Structural constraints:
n A Client instance must have a port called request, with a property called protocol. The protocol property must be of type CSProtocolT and have a value of rpc-client.
n A Client instance must have a property called request-rate of type float. The default value of 0.0 can be overriden with an extended with {…} clause, but the initial value for this property on all Client instances created with the new operator will be 0.0.
Invariant constraints:
n All ports of a client must have a property named protocol, which has a value of rpc-client.
n There may be no more than 5 ports on a Client instance.
n The request-rate property of a Client component must have a value greater than 0.
The heuristic constraint that the request-rate property of an instance of a Client component have a value less than 100 is not considered in determining whether that instance satisfies the Client type.
Instances of the four basic architectural elements – components, connectors, ports, and roles, can be created with the following (informal) syntax:
<Category> <InstanceName> [ : <TypeName> ] = <Value> ;
where
<value> ::= ( {
<sequence of property and structure specs.> } | new
<TypeName> )
( extended with <value> )*
Specifying an explicit type for an instance is optional. If no type is explicitly declared for an individual instance, then the type of that instance defaults to <Category>. Consider the following example of a component declared without an explicit type declaration:
Component C = { Port input; } ;
In this instance, the value of component C is { Port input } which satisfies the constraints of the Component type, so this instance declaration is valid.
When an instance is explicitly typed, as in the following example, the value on the right hand side of the “=” token must satisfy the predicate defined by the declared type. Consider the following example:
Component C : Client = new Client;
In this example, a component C is declared to satisfy type Client. The value of C is defined using the Acme new operator. The expression new <TypeName> creates a value expression consisting of the minimal structure declared in the declaration of <TypeName> with default values applied to properties as specified in the type specification. Properties with no default value provided in the type declaration have undefined values in the instance generated.
Using the Client type defined in example 3.1, the previous example creates a component with the following canonical structure:
Component C : Client = {
Port Request = {
Property protocol : CSProtocolT =
rpc-client }
Property request-rate : float = 0.0;
}
This default Client component satisfies the invariants and heuristics declared in the Client type definition.
It is possible to associate non-default values with an element created from a given type using the extended with <value> construct. The following example illustrates a client with an additional port and an additional property.
Component C' : Client = new Client extended with {
Port ExtraPort = { Property protocol : CSProtocolT = rpc-client;
Property primary-port = true };
Property request-rate : float = 5.0;
}
This declaration would result in the creation of a new component C’ with the following structure:
Component C’ : Client = {
Port Request = { Property protocol : CSProtocolT = rpc-client } ;
Port ExtraPort = { Property protocol : CSProtocolT = rpc-client};
Property primary-port = true;};
Property request-rate : float = 5.0;
}
In this example, the default constructor is extended with new property values that either add new structure and values or override the default structure and value of the type. The value that is assigned to C’ in this case is the unification of the structure declared with the extended with {... } clause and the structure that is created with the new <TypeName> constructor. The detailed algorithm for unifying substructure of an element using the extended with {…} construct is given in Chapter 2 where the semantics of extended with are specified.
A type specification defines the minimal set of structure and property fields that elements of a given type have, along with a set of invariants that must hold for all instances that satisfy the type. Every type T can be converted to a predicate function Ft that takes a single element E as an argument. If the function Ft(E) evaluates to true for element E, then element E satisfies type T (written T(E)).
Detailed descriptions of the semantics of required structure and invariant specifications of a type declaration follow:
The semantics of structural declarations in an element type specification are described in table 3.1.
Declaration Type |
Example |
Meaning |
||
Structural element C with no type or value declaration |
Port C; |
Forall elements E s.t. E declares type T (written E:T), T(E) implies E has the element named C as a child. |
||
Structural element C with a type but no value declaration |
Port C : t’; |
Forall elements E s.t. E:T, T(E) implies E has the element named C as a child, and that C satisfies t’ (t’(C)) |
||
Structural element C with a type and a value declaration |
Port C : t’ = { |
Forall elements E s.t. E:T, T(E) implies E has the element named C as a child, and t’(C) and C has the property j:t’’ with a value of bar. |
||
Property named P with no type or value given |
Property P; |
Forall elements E st E:T, T(E) implies E has the property P of type “Property.” |
||
Property named P with a type t’ specified, but no value given |
Property P : t’; |
Forall elements E st E:T, T(E) implies E has the property P of type t’. P’s value is unconstrained beyond the requirement that the value of P satisfy type t’. |
||
Property named P with a type t’ specified and a default value v given. |
Property P : t’ |
Forall elements E st E:T, T(E) implies E has the property P of type t’. P’s value defaults to v when a new instance of type T is created but the <<default = v>> clause is simply a convenience that the type has no obligation to maintain. The << ... >> notation specifies that “default = v” is a meta-property. |
||
Property named P with a type t’ specified and a value v assigned directly to the property |
Property P:t’ = v; |
Forall elements E st E:T, T(E) implies E has the property P of type t’ and P’s value must be v. This statement declares a constant valued property for the type. |
||
|
Table 3.1 Structural Specification Semantics |
|
||
The invariant declarations of a type T define a set of predicates that must hold for all instances of type T. These invariants are specified using a subset of the predicate language described in chapter 4. The primary restriction that this subset of the language imposes is that the scope of names (and entities) visible from within a predicate in the type declaration is limited to those entities (properties, ports, roles, etc.) defined in the type definition or the definition of any of its supertypes. In order to improve modularity, the type predicates are limited to operating over values of an instance of the type. The design rule mechanism used with systems (described later in this chapter) supports constraints spanning multiple types and instances.
One implication of this design decision is that invariants placed in element type declarations are most appropriate for local constraints on all elements of the type, such as valid ranges of property values. Constraints involving the relationship between elements, such as valid system topologies or valid port/role pairs, are best put in a system or style specification.
Details of the scoping constraints for type specifications follow:
· Scope of names within a type declaration: Names are lexically (statically) scoped and the namespace for a type spans both the structural constraints and invariants of the type specification. No two property and/or structural elements of a type declaration may share names. Structural elements (e.g. ports, roles, and representations) share the name space with properties. As a result, names used in the structure section may be unambiguously dereferenced by invariant predicates, allowing the unambiguous use of dot notation to refer to substructure and properties, while reducing the complexity of dereferencing names. The root of the type namespace that is visible to invariant predicates is the identifier self which is a reference to the instance of this type that is being checked for type-compliance. Invariant predicates can reference only self and entities that are descendents of self in the AST.
· Scope of predicates: Predicates declared within a type specification may only reference properties or structural elements named within the structural specification of the type, including substructure affiliated with those elements and properties. They may not refer to anything outside of the scope of the type declaration. This strict limitation on referencing entities outside of the element improves modularity and reusability of type specifications. Rules limiting the interactions of and relationships between design elements can be expressed with style-wide or system-wide design rules (discussed later in this chapter) instead of type specifications.
The keyword self is used to refer to the instance of an element that is being type-checked. Unless otherwise explicitly fully qualified, property names in an invariant predicate have an implicit self. preceding them and refer to the instance being checked.
Heuristic predicates are specified with exactly the same language as invariants and are governed by the same scoping rules when declared within a type definition. Heuristics differ from invariants only in that they are not considered for type-checking purposes.
A type may be declared within the global design space or within a style specification. Type names are lexically scoped. Types declared outside of all style declarations have global scope. Types with global scope are visible within all systems or styles declared in that global scope. A type defined within a style specification is visible to all other declarations in the style, all of that style’s substyles, and all system that claim to be built in that style.
Acme supports a strict form of subtyping that ensures substitutability between subtypes and supertypes. That is, if type T’ is a subtype of type T (written T’ £ T), then an element that satisfies T’ may be used wherever an element of type T is required. The following informal syntax describes Acme's subtyping construct.
<Category> Type <SubTypeName> extends <SuperTypeName>+ with {
<Sequence of: required structure and values
| properties
| explicit invariants
| explicit heuristics >
}
The semantics of this construct are straightforward. The new (sub)type <SubTypeName> consists of the unification of the structural requirements of all supertypes with the new structural declarations, and the union of the invariant and heuristic predicates of all supertypes with the new invariant and heuristic declarations. The unification operation for type structure is the same as the unification operation on instances is described in chapter 2. Because types are reduced to prototypical elements for semantic evaluation, the same unification operation is applicable to both element types and instances. All instances of the subtype are also instances of the supertype, and satisfy the constraints of both the supertype and the constraints listed in the extends ... with {...} clause.
Consider the following example:
Component Type BlockingClient extends Client with {
Port BlockingRequest = {Property protocol = rpc-client};
Property blocking : boolean = true;
Property timeout-sec : float << default = 30.0 >>;
Invariant timeout-sec < 60.0;
}
An instance of a BlockingClient type component would then have all of the structure and rules to maintain that a Client type component would have, plus the additional properties and rules given in this specification. The previous type declaration is equivalent to declaring the BlockingClient type without subclassing as done below (using the Client type definition from the previous section):
Component Type BlockingClient = {
Port Request = {Property protocol = rpc-client};
Port BlockingRequest = {Property protocol = rpc-client};
Property request-rate : float << default = 0 >>;
Property blocking : boolean = true;
Invariants {
Forall p in self.Ports | p.protocol = rpc-client;
Size(Ports) <= 5;
request-rate >= 0;
timeout-sec < 60.0;
};
Heuristic request-rate < 100;
}