Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions content/courses/ada-idioms/chapters/abstract_data_machines.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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 <Ada_Idioms_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.
Expand Down
13 changes: 7 additions & 6 deletions content/courses/ada-idioms/chapters/abstract_data_types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.

Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -174,57 +173,62 @@ 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;

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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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`:

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion content/courses/ada-idioms/chapters/introduction.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading