diff --git a/content/courses/ada-idioms/chapters/abstract_data_machines.rst b/content/courses/ada-idioms/chapters/abstract_data_machines.rst index 5d86fbf21..26a7b9853 100644 --- a/content/courses/ada-idioms/chapters/abstract_data_machines.rst +++ b/content/courses/ada-idioms/chapters/abstract_data_machines.rst @@ -83,7 +83,7 @@ following: - Operations The package declaration's private part and the package body may contain all -the above, but one or the other (or both) will contain object declarations +the above, but one or the other (or both) will contain variable declarations representing the abstraction's state. Consider the following ADM version of the package :ada:`Integer_Stacks`, now @@ -115,14 +115,14 @@ version we declare the state in the package body. end Integer_Stack; Now there is no type presenting a :ada:`Stack` abstraction and the operations -do not take a stack parameter because the package and its data is the instance +do not take a stack parameter because the package and its data together are the instance of the abstraction. When using this idiom, there is only one stack of integers. That's why we changed the name of the package from :ada:`Integer_Stacks`, i.e., from the plural form to the singular. As with the ADT idiom, clients of an ADM can only manipulate the encapsulated state indirectly, via the visible operations. The difference is that the state -to be manipulated is no longer a formal parameter. For example: +to be manipulated is no longer a formal parameter of the operations. For example: .. code-block:: ada @@ -138,7 +138,7 @@ type :ada:`Stack` are manipulated: -- ... Push (Answers, 42); -That call places the value 42 in the array :ada:`Answers.Values`, i.e., within +That call places the value 42 in the (hidden) array :ada:`Answers.Values`, i.e., within the :ada:`Answers` variable. Clients can declare as many :ada:`Stack` objects as they require, each containing a distinct copy of the state defined by the type. In the ADM version, there is only one stack and therefore only one instance @@ -170,13 +170,13 @@ The private section wasn't otherwise required when we chose to declare the data the package body. The ADM idiom applies information hiding to the internal state, like the -ADT idiom, except that the state is not in objects. Also, like the +ADT idiom, except that the state is not in objects declared in the client. Also, like the :ref:`Groups of Related Program Units `, the implementations of the visible subprograms are hidden in the package body, along with any non-visible entities required for their implementation. There are no constructor functions returning a value of the abstraction -type because there is no such type with the ADM. However, there could be one or +type because there is no such type within the ADM. However, there could be one or more initialization procedures, operating directly on the hidden state in the package private part or package body. In the :ada:`Stack` ADM there is no need because of the reasonable initial state, as is true with the ADT version. diff --git a/content/courses/ada-idioms/chapters/abstract_data_types.rst b/content/courses/ada-idioms/chapters/abstract_data_types.rst index e6e972502..9f4878a9d 100644 --- a/content/courses/ada-idioms/chapters/abstract_data_types.rst +++ b/content/courses/ada-idioms/chapters/abstract_data_types.rst @@ -14,8 +14,9 @@ client compile-time visibility to the type's representation is both an advantage and a disadvantage. Visibility to the representation makes available the expressiveness of low-level syntax, such as array indexing and aggregates, but in so doing allows client source code to be dependent on the -representation. In the vast majority of cases, the resulting economic and -engineering disadvantages far outweigh the expressiveness advantages. +representation. In many cases, the resulting economic and +engineering disadvantages of visibility on the representation will +outweigh the expressiveness advantages. For the sake of illustration, let's create a *stack* type that can contain values of type :ada:`Integer`. (We use type :ada:`Integer` purely for the sake @@ -111,7 +112,7 @@ The ADT may also be abstract in the sense of object-oriented programming but that is an unrelated issue. In Ada we use *private types* to define abstract data types because private -types make the type's name, but not the representation, visible to clients. +types make the type's name, but not its representation, visible to clients. These types are composed using syntactical building blocks: a package declaration, separated into two parts, containing a type declared in two parts, and containing declarations for subprograms to manipulate objects of the type @@ -203,7 +204,7 @@ The full type definition is in the package private part. Therefore, for any given object of the type, the representation details |mdash| the two record components in this example |mdash| can't be referenced in client code. Clients must instead only use the operations defined by the package, passing -the client objects to the formal parameters. Only the bodies of these operations +the client objects as the actual parameters. Only the bodies of these operations have compile-time visibility to the representation of the :ada:`Stack` parameters, so only they can implement the functionality for those parameters. @@ -216,7 +217,7 @@ mentioned, basic operations such as assignment are allowed, unless the ADT is abstraction. You may, of course, also require other ancillary type declarations in the -package, either for the implementation or as additional parameters for the +package, either for the implementation or as types for additional parameters for the visible operations. The array type :ada:`Content` is an example of the former case. When it is strictly an implementation artifact, as in this case, it should be in the private part so that it's hidden from clients. @@ -279,7 +280,7 @@ There may be cases when what looks like an accessor function is provided, when in fact the function computes the return value. Similarly, there may be functions that simply return the value of a component but are part of the abstraction and happen to be implementable by returning the value of a -component. For example, a real stacks ADT package would include a function +component. For example, a real stack's ADT package would include a function indicating the extent of the object |mdash| that is, the number of values currently contained. In our example implementation the :ada:`Top` component happens to indicate that value, in addition to indicating the current top of the stack. The body diff --git a/content/courses/ada-idioms/chapters/component_access_to_rec_objs.rst b/content/courses/ada-idioms/chapters/component_access_to_rec_objs.rst index ffe74dfbe..a9d9397be 100644 --- a/content/courses/ada-idioms/chapters/component_access_to_rec_objs.rst +++ b/content/courses/ada-idioms/chapters/component_access_to_rec_objs.rst @@ -23,8 +23,7 @@ We would want a task type or protected type record component when: object. The record object and contained task/PO object pair is a functional unit, independent of all other such units. -This idiom applies to both enclosed task types and protected types, but for the -sake of concision let's assume the record component will be of a protected +This idiom applies to both enclosed task types and protected types, but for simplicity let's assume the record component will be of a protected type. As part of a functional unit, the PO component will almost certainly be @@ -99,65 +98,65 @@ two parts: .. code-block:: ada package P is - type Enclosing is tagged limited private; + type Device is tagged limited private; private - protected type Controller (Instance : not null access Enclosing) is + protected type Controller (Encloser : not null access Device) is -- Part 1 procedure Increment_X; end Controller; - type Enclosing is tagged limited record + type Device is tagged limited record X : Integer; -- arbitrary type - C : Controller (Instance => ...); + C : Controller (Encloser => ...); -- Part 2, not fully shown yet end record; end P; -The record type named :ada:`Enclosing` contains a component named :ada:`X`, +The record type named :ada:`Device` contains a component named :ada:`X`, arbitrarily of type :ada:`Integer`, and another component :ada:`C` that is of protected type :ada:`Controller`. Part #1 of the solution is the access discriminant on the declaration of the protected type :ada:`Controller`: .. code-block:: ada - protected type Controller (Instance : not null access Enclosing) is + protected type Controller (Encloser : not null access Device) is -Given a value for the discriminant :ada:`Instance`, the code within the spec -and body of type :ada:`Controller` can then reference some :ada:`Enclosing` +Given a value for the discriminant :ada:`Encloser`, the code within the spec +and body of type :ada:`Controller` can then reference some :ada:`Device` object via that discriminant. -But not just any object of type :ada:`Enclosing` will suffice. For Part #2, we -must give the :ada:`Instance` discriminant a value that refers to the current +But not just any object of type :ada:`Device` will suffice. For Part #2, we +must give the :ada:`Encloser` discriminant a value that refers to the current instance of the record object containing this same PO object. In the package -declaration above, the value passed to :ada:`Instance` is elided. The following -is that code again, now showing just the declaration for :ada:`Enclosing`, but +declaration above, the value passed to :ada:`Encloser` is elided. The following +is that code again, now showing just the declaration for :ada:`Device`, but also including the construct that is actually passed. This is where the subtlety comes into play: .. code-block:: ada - type Enclosing is tagged limited record + type Device is tagged limited record ... - C : Controller (Instance => Enclosing'Access); + C : Controller (Encloser => Device'Access); end record; -The subtlety is the expression :ada:`Enclosing'Access`. Within a type +The subtlety is the expression :ada:`Device'Access`. Within a type declaration, usage of the type name denotes the current instance of that type. The current instance of a type is the object of the type that is associated with the execution that evaluates the type name. For example, during execution, -when an object of type :ada:`Enclosing` is elaborated, the name -:ada:`Enclosing` refers to that object. +when an object of type :ada:`Device` is elaborated, the name +:ada:`Device` refers to that object. It isn't compiler-defined magic, the semantics are defined by the Ada standard so it is completely portable. (There are other cases for expressing the current instance of task types, protected types, and generics.) -Therefore, within the declaration of type :ada:`Enclosing`, the expression -:ada:`Enclosing'Access` provides an access value designating the current +Therefore, within the declaration of type :ada:`Device`, the expression +:ada:`Device'Access` provides an access value designating the current instance of that type. This is exactly what we want and is the crux of the idiom expression. With that discriminant value, the enclosed PO spec and body can reference the other record components of the same object that contains the @@ -174,7 +173,7 @@ value referenced in the body of procedure :ada:`Increment_X`: procedure Increment_X is begin - Instance.X := Instance.X + 1; + Encloser.X := Encloser.X + 1; end Increment_X; end Controller; @@ -182,49 +181,54 @@ value referenced in the body of procedure :ada:`Increment_X`: end P; Specifically, the body of procedure :ada:`Increment_X` can use the access -discriminant :ada:`Instance` to get to the current instance's :ada:`X` -component. (We could express it as :ada:`Instance.all.X` but why bother. +discriminant :ada:`Encloser` to get to the current instance's :ada:`X` +component. (We could express it as :ada:`Encloser.all.X` but why bother. Implicit dereferencing is a wonderful thing.) That's the solution. Now for some necessary details. -Note that we declared type :ada:`Enclosing` as a limited type, first in the +Note that we declared type :ada:`Device` as a limited type, first in the visible part of the package: .. code-block:: ada - type Enclosing is tagged limited private; + type Device is tagged limited private; and again in the type completion in the package private part: .. code-block:: ada - type Enclosing is tagged limited record ... end record; + type Device is tagged limited record ... end record; -We declare :ada:`Enclosing` as a limited type because we want to preclude +We declare :ada:`Device` as a limited type because we want to preclude assignment statements for client objects of the type. Assignment of the -enclosing record object would leave the PO Instance discriminant +enclosing record object would leave the PO Encloser discriminant designating the prior (right-hand side) enclosing object. If the PO is written with the assumption that the enclosing object is always the one identified during creation of the PO, that assumption will no longer hold. We didn't state it up-front, but that is the assumption underlying -the idiom as described. +the idiom as described, and in fact, only limited types may have +a component that uses the :ada:`Access` attribute in this way. +Also note that any type that includes a protected or task object +is limited, so a type like Device will necessarily be limited in any case. -The type need not be tagged for this idiom solution, but if you do make it -tagged, the partial and full views must always match. That is, a tagged type +The type need not be tagged for this idiom solution, but it must be +limited in both its partial view and its full view. More generally, a tagged type must be limited in both views if it is limited in either view. For the idiom solution to be legal, the type's completion in the private part -must always be *immutably limited*, meaning that it is always truly limited. +must not merely be limited, but actually *immutably limited*, meaning that it is always truly limited. There are various ways to make that happen (see :aarm22:`AARM22 7.5 (8.1/3) <7-5>` ) but the easiest way to is to include the reserved word :ada:`limited` in the type definition within the full view, as we -did above. That is known as making the type *explicitly limited*. +did above. That is known as making the type *explicitly limited*. It turns out +having a task or protected component also makes it immutably limited, so +this requirement is naturally satisfied in this use case. Why does the compiler require the type to be immutably limited? Recall that a (non-tagged) private type need not be limited in both views. It -can be limited in the partial client view but non-limited in the private full +can be limited in the partial client view but non-limited in its full view: .. code-block:: ada @@ -243,15 +247,16 @@ Clients must treat type :ada:`Q.T` as if it is limited, but :ada:`Q.T` isn't really limited because the full view defines reality. Clients simply have a more restricted view of the type than is really the case. -Types that are explicitly limited really are limited, and always have a view as -a limited type. That's important because the type given in :ada:`type_name'Access` must be -aliased for :ada:`'Access` to be meaningful and possible on the corresponding -objects. But if the type's view could change between limited and not limited, -it would be aliased in some contexts and not aliased in others. To prevent that -complexity, the language requires the type's view to be permanently limited so -that the type will be permanently aliased. An immutably limited type is -permanently aliased. In practice, we're working with record types and type -extensions, so just make the type definition explicitly limited and all will be +Types that are immutably limited are necessarily +limited in all views. +That's important because the current instance of the +type given in :ada:`type_name'Access` must be +aliased for :ada:`'Access` to be legal. But if the type's view could change between limited and not limited, +its current instance would be aliased in some contexts and not aliased in others. To prevent that +complexity, the language requires the type to be immutably limited so +that the current instance of the type will be aliased in every view. +In practice, we're working with record types and type +extensions, so just make the full type definition explicitly limited and all will be well: .. code-block:: ada diff --git a/content/courses/ada-idioms/chapters/constructor_functions_for_abstract_data_types.rst b/content/courses/ada-idioms/chapters/constructor_functions_for_abstract_data_types.rst index d0dff1523..37e045a0e 100644 --- a/content/courses/ada-idioms/chapters/constructor_functions_for_abstract_data_types.rst +++ b/content/courses/ada-idioms/chapters/constructor_functions_for_abstract_data_types.rst @@ -70,7 +70,7 @@ the type declaration itself. For procedures, that means they have formal parameters of the type. For functions, that means they either have formal parameters of the type, or return a value of the type, or both. -Declaration with the same package as the type itself provides the +Declaration within the same package as the type itself provides the compile-time visibility to the type's representation required to implement the subprograms. @@ -136,7 +136,7 @@ Therefore, unless the extended child type is itself abstract, the type extension will be illegal. The compiler will reject the declaration of the child type, thus preventing this inappropriate constructor inheritance. -For an example, both for the code and the Ada rules, consider this simple +For an example, both to illustrate the code and the Ada rules, consider this simple package declaration that presents the tagged private type :ada:`Graphics.Shape`: @@ -223,8 +223,7 @@ compile-time visibility that primitive operations have. Therefore, the specific solution is to declare constructor functions in a separate package that is a *child* of the package declaring the tagged type. -The actual term is a *hierarchical library package* but *child* conveys the -concept and is less of a mouthful. +This takes advantage of the *hierarchical library units* capability introduced in Ada 95. Operations declared in a child package are not primitive operations for the type in the parent package, so they are not inherited when that type is @@ -317,7 +316,8 @@ locating individual entities of interest, any decent IDE will make doing so trivial.) The alternative also loses the distinction between clients that use objects of -the type and clients that create those objects, because the latter will have +the type and clients that create those objects, because, with the child package +approach, the latter will be the only clients that have context clauses for the constructor packages. diff --git a/content/courses/ada-idioms/chapters/controlling_obj_initialization_creation.rst b/content/courses/ada-idioms/chapters/controlling_obj_initialization_creation.rst index fee8fef68..6e3273f99 100644 --- a/content/courses/ada-idioms/chapters/controlling_obj_initialization_creation.rst +++ b/content/courses/ada-idioms/chapters/controlling_obj_initialization_creation.rst @@ -24,7 +24,7 @@ most commonly used non-numeric type in the language. Sometimes a given type was initialization, e.g., numeric types. That wrapping approach is less common than in earlier versions of the language, given the comparatively more recent aspect :ada:`Default_Value` for scalar types, and :ada:`Default_Component_Value` for -scalar components. +scalar array components. These facilities are often sufficient to express an abstraction's initial state. For example, we can expect that container objects will be initially diff --git a/content/courses/ada-idioms/chapters/introduction.rst b/content/courses/ada-idioms/chapters/introduction.rst index eb154ab42..e5958899b 100644 --- a/content/courses/ada-idioms/chapters/introduction.rst +++ b/content/courses/ada-idioms/chapters/introduction.rst @@ -154,7 +154,7 @@ assignment statements. However, if we pass that variable as the argument to a procedure call, within that subprogram (for that call) the view specifies a different name for the argument, i.e., the formal parameter name. Moreover, if that formal parameter is a mode-in parameter, within that procedure body the -view of the actual parameter is as if it is a constant rather than a variable. +view of the actual parameter is as if it were a constant rather than a variable. No assignments via the formal parameter name are allowed because the view at that point in the text |mdash| within that procedure body |mdash| doesn't allow them, unlike the view available at the point of the call. diff --git a/content/courses/ada-idioms/chapters/programming_by_extension.rst b/content/courses/ada-idioms/chapters/programming_by_extension.rst index 22346c73f..c7747400b 100644 --- a/content/courses/ada-idioms/chapters/programming_by_extension.rst +++ b/content/courses/ada-idioms/chapters/programming_by_extension.rst @@ -227,12 +227,12 @@ Notes ----- This guideline will already be used when developing a subsystem (a set of -related packages in a common hierarchy) as a structuring approach during +related packages in an overall hierarchy) as a structuring approach during initial development. The idiom discussed here is yet another reason to use the private part, but in this case for the sake of the future, rather than initial, development. -The very first version of Ada (Ada 83) did not have hierarchical packages so, +The very first version of Ada (Ada 83) did not have hierarchical library units so, typically, anything not required in the private part was declared in the package body. Declaring them in the private part would only clutter the code that had to be there, without any benefit. The author's personal experience and