Skip to content

Commit

Permalink
Merge pull request #1173 from pat-rogers/master
Browse files Browse the repository at this point in the history
Change the "Solution" heading to "Implementation(s)"
  • Loading branch information
gusthoff authored Jan 17, 2025
2 parents 1000ae9 + ff629a9 commit 5e0d754
Show file tree
Hide file tree
Showing 12 changed files with 87 additions and 85 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ Machine (ADM). The Abstract Data Machine was introduced by Grady Booch [1]_
as the Abstract State Machine, but that name, though appropriate,
encompasses more in computer science than we intend to evoke.

Solution
--------
Implementation(s)
-----------------

The ADM is similar to the ADT idiom in that it presents an
abstraction that doesn't already exist in the programming language.
Expand Down
6 changes: 3 additions & 3 deletions content/courses/ada-idioms/chapters/abstract_data_types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ likely introduce new bugs.
These disadvantages argue for an alternative. That is the purpose of this next
idiom, known as the Abstract Data Type (ADT) [1]_, [2]_.

Solution
--------
Implementation(s)
-----------------

Abstraction is one of the central principles of software engineering because
it is one of the primary ways that humans manage complexity. The idea is to
Expand Down Expand Up @@ -344,7 +344,7 @@ type can be private, as illustrated by the client expectation for array
indexing in Ada prior to Ada 2012.
Not every type should be private, for example those that are
explicitly numeric. But the ADT should be the default design idiom when
decomposing a problem into a solution.
composing a solution.

Cons
----
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,14 @@ defining major types, for the good reasons given in that idiom entry. There's
no reason not to use an ADT in this case so we will.


Solution
Implementation(s)
-----------------

As mentioned, the solution applies to enclosed components of both task types
As mentioned, the implementation approach applies to enclosed components of both task types
and protected types. We will continue the discussion in terms of protected
types.

The solution has two parts:
The implementation has two parts:

1. An access discriminant on the PO type, designating the enclosing
record's type. That part is straightforward.
Expand Down Expand Up @@ -118,7 +118,7 @@ two parts:

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
protected type :ada:`Controller`. Part #1 of the implementation is the access
discriminant on the declaration of the protected type :ada:`Controller`:

.. code-block:: ada
Expand Down Expand Up @@ -185,7 +185,7 @@ 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.
That's the implementation. Now for some necessary details.

Note that we declared type :ada:`Device` as a limited type, first in the
visible part of the package:
Expand All @@ -212,11 +212,11 @@ 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 it must be
The type need not be tagged for this approach, 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
For the idiom implementation to be legal, the type's completion in the private part
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
Expand Down Expand Up @@ -532,11 +532,11 @@ inherited IO pin components.
Pros
-----------------

The solution is directly expressed, requiring only an access discriminant and
The implementation is directly expressed, requiring only an access discriminant and
the current instance semantics of :ada:`type_name'Access`.

Although the real-word example is complex |mdash| multiple discriminants are
involved, and a type extension |mdash| the idiom solution itself requires little
involved, and a type extension |mdash| the implementation itself requires little
text. Interrupt handling is relatively complex in any language.


Expand All @@ -554,7 +554,7 @@ Relationship With Other Idioms
This idiom is useful when we have a record type enclosing a PO or task object.
If the :ref:`Abstract Data Machine (ADM) <Ada_Idioms_Abstract_Data_Machines>`
would instead be appropriate, the necessary visibility can be achieved without
requiring this idiom solution because there would be no enclosing record type.
requiring this implementation approach because there would be no enclosing record type.
But as described in the ADM discussion, the
:ref:`ADT approach <Ada_Idioms_Abstract_Data_Types>` is usually superior.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,20 +208,20 @@ that sufficient constructor functions are possible.
Therefore, a general design idiom is required for defining constructor functions
for concrete tagged Abstract Data Types.

Solution
--------
Implementation(s)
-----------------

The general solution uses functions for constructing objects but prevents these
The general approach uses functions for constructing objects but prevents these
functions from being inherited. The problem is thus circumvented entirely.

To prevent their being inherited, the solution prevents the constructor
To prevent their being inherited, the implementation prevents the constructor
functions from being primitive operations. However, these functions require
compile-time visibility to the parent type's representation in order to
construct values of the type, as this typically involves assigning values to
components in the return object. The alternative approach must supply the
compile-time visibility that primitive operations have.

Therefore, the specific solution is to declare constructor functions in a
Therefore, the specific implementation is to declare constructor functions in a
separate package that is a *child* of the package declaring the tagged type.
This takes advantage of the *hierarchical library units* capability introduced in Ada 95.

Expand Down Expand Up @@ -331,7 +331,7 @@ Notes
-----

For those interested, in this section we provide a discussion of alternatives
to the solution given, and why they are inadequate.
to the implementation presented, and why they are inadequate.

Changing the behavior of an inherited operation requires an explicit conforming
subprogram declaration and therefore a new subprogram body for that operation.
Expand Down Expand Up @@ -386,7 +386,7 @@ might be called accidentally. For example:
Although the overridden :ada:`Make` does not have a :ada:`Radius` parameter and
could only assign some default to that component, if that default is reasonable
then the overridden function could be called on purpose, i.e., not
accidentally. That's not a general solution, however.
accidentally. That's not a general approach, however.

Alternatively, developers could use procedures as their constructors, with a
mode-out parameter for the result. The procedure would not become implicitly
Expand Down Expand Up @@ -437,4 +437,4 @@ But the same issues arise as with functions. Clients might accidentally call
the wrong procedure, i.e., the inherited routine that doesn't have a parameter
for the :ada:`Radius`. That routine would not even mention the :ada:`Radius`
component, much less assign a default value, so it would have to be overridden
in order to do so. This too is not a general solution.
in order to do so. This too is not a general approach.
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,12 @@ determined by the function, and need not start at :ada:`Positive'First`.

An object cannot be used before it is declared. Since this explicit initial
value is part of the declaration, the object cannot be read before it is
initialized. That fact is the key to the solutions.
initialized. That fact is the key to the implementation approaches.

However, although the object declaration is guaranteed to occur, explicit
initialization is optional. But unlike a procedure call, we can force the
initial value to be given. There are two ways to force it, so there are two
solutions presented.
implementations presented.

In addition, a specific form of explicit initialization may be required because
not all forms of initialization are necessarily appropriate for a given
Expand Down Expand Up @@ -164,20 +164,20 @@ is an example, in which a type is defined but corresponding object creation by
clients is not intended. Instead, the abstraction implementation creates a
single object of the type. The abstraction is a type, rather than an
:ref:`ADM <Ada_Idioms_Abstract_Data_Machines>`, for the sake of potential
extension via inheritance. We will illustrate this design pattern and solution
extension via inheritance. We will illustrate this design pattern and implementation
using a real-world hardware device.


Requiring Initialization by Clients
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Implementation(s)
-----------------

There are two ways to force an explicit initial value as part of an object
declaration. One is a matter of legality at compile-time so it is enforced by
the compiler. The other is enforced by a run-time check.

Note that both solutions are type-specific, so when we say *objects* we mean
objects of a type that has been designed with one of these two idiom solutions.
Neither solution applies to every object of every type used in the client code.
Note that both approaches are type-specific, so when we say *objects* we mean
objects of a type that has been designed with one of these two idiom implementations.
Neither implementation applies to every object of every type used in the client code.
(SPARK, a formal language based closely on Ada, statically ensures all objects
are initialized before read.)

Expand All @@ -198,13 +198,13 @@ creating a type that is both private and limited.
Throughout this discussion we will assume that these designs are based on
:ref:`Abstract Data Types <Ada_Idioms_Abstract_Data_Types>`, hence we assume
the use of private types. That's a general, initial design assumption but in
this case private types are required by the two idiom solutions. The types are
this case private types are required by the two idiom implementations. The types are
not necessarily limited as well, but in one situation they will be limited too.
But in both solutions the primary types will be private types.
But in both implementations the primary types will be private types.


Solution 1: Compile-Time Legality
---------------------------------
Compile-Time Legality
~~~~~~~~~~~~~~~~~~~~~

We can combine the private type and limited type building blocks with another,
known as *unknown discriminants*, to force explicit object initialization by
Expand Down Expand Up @@ -381,7 +381,7 @@ built in place instead of copied.)

To recap, the primary purpose of the idiom, for a given type, is to ensure that
clients initialize objects of that type as part of the object declarations. In
this first solution we meet the requirement by composing the type via building
this first implementation we meet the requirement by composing the type via building
blocks that:

#. require a constraint to be given when declaring any object of the type,
Expand Down Expand Up @@ -430,10 +430,10 @@ a viable alternative, but that's also beyond the scope of this document.)

Therefore, default initialization doesn't really suffice for this ADT. We need
to force initialization (configuration) during object creation so that enabling
the ADT output will always be safe. This idiom solution does exactly that.
the ADT output will always be safe. This idiom implementation does exactly that.

The following is a cut-down version of the package declaration using this idiom
solution, with some operations and record components elided for the sake of
implementation, with some operations and record components elided for the sake of
simplicity. In the full version the unit is a generic package for the sake of
not hard-coding the floating point types. We use a regular package and type
:ada:`Float` here for convenience. The full version is here:
Expand Down Expand Up @@ -573,8 +573,8 @@ parameter, the call to configure the object's state necessarily precedes any
other operation (e.g., :ada:`Enable`).


Solution 2: Run-Time Checks
----------------------------------------------------------
Run-Time Checks
~~~~~~~~~~~~~~~

Ada 2022 adds another building block, :ada:`Default_Initial_Condition` (DIC),
that can be used as an alternative to the unknown discriminants used above. We
Expand Down Expand Up @@ -668,7 +668,7 @@ all object declarations of that type that rely on default initialization. But
suppose the type does not define any default initialization. We can detect
these uninitialized objects at run-time if we set the DIC Boolean expression
to indicate that there is no default initialization defined for this type. The
checks will then fail for those objects. That's the second solution to the
checks will then fail for those objects. That's the second implementation approach to the
initialization requirement.

Specifically, we can express the lack of default initialization by a DIC
Expand Down Expand Up @@ -782,7 +782,7 @@ program a chance to execute a handler? If so, is there sufficient storage
remaining to execute the exception handler's statements? In any case you can
see the problem that the declaration failure semantics preclude.

Therefore, although the DIC solution is not enforced at compile-time, it is
Therefore, although the DIC approach is not enforced at compile-time, it is
nevertheless sufficient to ensure no uninitialized object of the type can be
used.

Expand All @@ -791,11 +791,11 @@ Preventing Object Creation by Clients
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The other idiom requirement is the ability to control object creation itself.
The solution is trivially achieved using an indefinite limited private type:
The implementation is trivially achieved using an indefinite limited private type:
we can prevent client object creation simply by not providing any constructor
functions. Doing so removes any means for initializing objects of the type, and
since the type is indefinite there is then no way for clients to declare
objects at all. The compiler again enforces this solution.
objects at all. The compiler again enforces this implementation.

For a concrete example, we can apply the Singleton design pattern to represent
the *time stamp counter* (:wikipedia:`TSC <Time_Stamp_Counter>`) provided by
Expand All @@ -804,7 +804,7 @@ clock cycle, starting from zero at power-up. We can use it to make a timestamp
abstraction. As explained by
:wikipedia:`Wikipedia page <Time_Stamp_Counter>`, some care is required when
using the register for that purpose on modern hardware, but it will suffice to
illustrate the idiom solution. Note that the Singleton pattern is itself
illustrate the idiom implementation. Note that the Singleton pattern is itself
somewhat controversial in the OOP community, but that's beyond the scope of
this document.

Expand All @@ -825,7 +825,7 @@ times. We'll use the design approach of indefinite limited private types
without any constructor functions in order to ensure clients cannot create
objects of the type. The type will also be tagged for the sake of allowing type
extensions. Adding the tagged characteristic doesn't change anything regarding
the idiom solution.
the idiom implementation.

.. code-block:: ada

Expand Down Expand Up @@ -1263,7 +1263,7 @@ The types above will either have a corresponding completion or a generic actual
parameter to either define the discriminants or specify that there are none.

As we mentioned, :ada:`Default_Initial_Condition` is new in Ada 2022. The other
solution, based on indefinite private types, is supported by Ada 2022 but also
implementation, based on indefinite private types, is supported by Ada 2022 but also
by earlier versions of the language. However, if the type is also limited, Ada
2005 is the earliest version allowing that solution. Prior to that version an
2005 is the earliest version allowing that implementation. Prior to that version an
object of a limited type could not be initialized in the object's declaration.
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ module in relative isolation. Coupling and cohesion are interrelated:
higher cohesion tends to result in less coupling.


Solution
--------
Implementation(s)
-----------------

Three idioms for packages were envisioned when the language was first designed.
They were introduced and described in detail in the Rationale document for the
Expand Down
14 changes: 7 additions & 7 deletions content/courses/ada-idioms/chapters/inheritance_idioms.rst
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ types.
For example, in Ada, full dynamic OOP capabilities require type declarations to
be decorated with the reserved word :ada:`tagged`. However, from its earliest days,
Ada has also supported a static form of inheritance, using types that are not
tagged. The solution we describe below works with both forms of inheritance.
tagged. The implementation we describe below works with both forms of inheritance.

The developer also has a choice of whether the parent type and/or the child type
is a private type. Using private types is the default design choice, for the
Expand Down Expand Up @@ -178,10 +178,10 @@ with a completion in the package private part. The difference is the client
visibility to the parent type.


Solution
--------
Implementation(s)
-----------------

There are two *solutions* in this entry, one for each of the two inheritance
There are two implementations presented, one for each of the two inheritance
idioms under discussion. First, we will specify our building block choices,
then show the two idiom expressions in separate subsections.

Expand All @@ -200,16 +200,16 @@ then show the two idiom expressions in separate subsections.

- We're going to declare the child type in a distinct, dedicated package,
following the :ref:`ADT idiom <Ada_Idioms_Abstract_Data_Types>`. This package
may or may not be a child of the parent package. This solution's
may or may not be a child of the parent package. This implementation's
approach does not require a child package's special compile-time visibility,
although a child package is often necessary for the sake of that visibility.

- Whether the child type is visibly derived will vary with the
:ref:`inheritance idiom <Ada_Idioms_Implementation_Inheritance>` solution.
:ref:`inheritance idiom <Ada_Idioms_Implementation_Inheritance>` implementation.

To avoid unnecessary code duplication, we use
the same parent type, declared as a simple tagged private type, in the examples
for the two idiom solutions. The parent type
for the two idiom implementations. The parent type
could itself be derived from some other tagged type, but that changes nothing
conceptually significant. We declare parent type in package :ada:`P` as
follows:
Expand Down
Loading

0 comments on commit 5e0d754

Please sign in to comment.