diff --git a/content/courses/ada-idioms/chapters/abstract_data_machines.rst b/content/courses/ada-idioms/chapters/abstract_data_machines.rst index 09c852000..5d86fbf21 100644 --- a/content/courses/ada-idioms/chapters/abstract_data_machines.rst +++ b/content/courses/ada-idioms/chapters/abstract_data_machines.rst @@ -16,29 +16,31 @@ Motivation Solution -------- -The Abstract Data Machine (ADM) is similar to the -:ref:`Abstract Data Type ` in that it -presents an abstraction, something that doesn't already exist in the +The Abstract Data Machine (ADM) idiom is similar to the +:ref:`Abstract Data Type ` idiom in that it +presents an abstraction that doesn't already exist in the programming language. Furthermore, like the ADT, operations are provided to -manipulate the abstraction data, state that is not otherwise compile-time +manipulate the abstraction state, which is not otherwise compile-time visible to client code. These operations are thus enforced as the only manipulation possible, as per the designer's intent. (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.) +name, though appropriate, encompasses more in computer science than we intend +to evoke.) Unlike the ADT, however, the ADM does not define the abstraction as a type. To understand this point, recall that type declarations are descriptions for -objects that will contain data. For example, our earlier :ada:`Stack` type was +objects that will contain data (the state). For example, +our earlier :ada:`Stack` type was defined as a record containing two components: an array to hold the values -logically contained by the :ada:`Stack`, and an integer indicating the logical -top of that array. No data actually exist, i.e., are allocated storage, until +logically contained by the :ada:`Stack` and an integer indicating the logical +top of that array. No data actually exists, i.e., is allocated storage, until objects are declared. Clients can declare as many objects of type :ada:`Stack` -as they require, and each object has a distinct, separate copy of those two +as they require and each object has a distinct, separate copy of those two components. Clients can, of course, choose to declare only one object of a given type, in which case only one instance of the data described by the type will exist. But -in that case, other than convenience there is no functional difference from +in that case, other than convenience, there is no functional difference from declaring objects of the component types directly, rather than indirectly via some enclosing type. Instead of using the :ada:`Stack` type to declare a single composite object, for example, the developer could have instead declared @@ -59,17 +61,17 @@ or even this, using an anonymously-typed array: Values : array (1 .. Capacity) of Integer; Top : Integer range 0 .. Capacity := 0; -If there is only one *stack* these two objects will suffice. +If there is only one *stack*, these two objects will suffice. -That's what the ADM does. The necessary state for a single abstraction instance -is declared in a package, usually a library package. But as an abstraction, +That's what the ADM does. The package, usually a library package, declares +the necessary state for a single abstraction instance. But, as an abstraction, those data declarations must not be compile-time visible to clients. Therefore, the state is declared in either the package private part or the package body. -Doing so requires that visible operations be made available to clients, as any -abstraction would require. Hence the package is the abstraction instance, as -opposed to one or more objects of a type. +Doing so requires that visible operations be made available to clients, like any +other abstraction. Hence the package is the one instance of the abstraction, as +opposed to defining one or more objects of a type. -Therefore, the package declaration's visible section will contain only the +Therefore, the package declaration's visible section contains only the following: - Constants (but almost certainly not variables) @@ -80,9 +82,9 @@ following: - Operations -The package declaration's private part and the package body may contain all the -above, but especially one or the other (or both) will contain object -declarations representing the abstraction's state. +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 +representing the abstraction's state. Consider the following ADM version of the package :ada:`Integer_Stacks`, now renamed to :ada:`Integer_Stack` for reasons we will discuss shortly. In this @@ -112,11 +114,11 @@ version we declare the state in the package body. function Empty return Boolean is (Top = 0); 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 -abstraction instance. There is only one stack of integers with this idiom. That -is why the name of the package is changed from :ada:`Integer_Stacks`, i.e., -from the plural form. +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 +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 @@ -138,8 +140,8 @@ type :ada:`Stack` are manipulated: That call places the value 42 in the array :ada:`Answers.Values`, i.e., within the :ada:`Answers` variable. Clients can declare as many :ada:`Stack` objects -as they require, and each will contain a distinct copy of the state defined by -the type. In the ADM version there is only one stack and therefore one instance +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 of the state. Rather than declare the abstraction state in the package body, we could just as @@ -157,24 +159,23 @@ easily declare it in the package's private section: Top : Integer range 0 .. Capacity := 0; end Integer_Stack; -Doing so doesn't change anything from the client code point of view. Just as +Doing so doesn't change anything from the client code point of view; just as clients have no compile-time visibility to declarations in the package body, they have no compile-time visibility to the items in the package private part. This placement also doesn't change the fact that there is only one instance of the data. We've only changed where the data are declared. (We will discuss the effect of child packages separately.) -The private section wasn't required when the data were declared in the package -body. That's typical when using this idiom but is not a necessary -characteristic. +The private section wasn't otherwise required when we chose to declare the data in +the package body. -The ADM idiom applies information hiding to the internal state, similar to the -ADT idiom, except that the state is not in objects. As well, like the +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 :ref:`Groups of Related Program Units `, -the implementations of the visible subprograms are hidden by the package body, +the implementations of the visible subprograms are hidden in the package body, along with any non-visible entities required for their implementation. -There will be no constructor functions returning a value of the abstraction +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 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 @@ -188,8 +189,9 @@ idiom by default. Pros ---- -In terms of abstraction and information hiding, the ADM provides the same -advantages as the ADT idiom: clients have no representation details visible and +In terms of abstraction and information hiding, the ADM idiom provides the same +advantages as the ADT idiom: clients have no visibility to +representation details and must use the operations declared in the package to manipulate the state. The compiler enforces this abstract view. The ADM also has the ADT benefit of knowing where any bugs could possibly be located. If there is a bug in the @@ -197,10 +199,10 @@ manipulation, it must be in the one package defining the abstraction itself. No other code would have the compile-time visibility necessary. This idiom can be applied to any situation requiring abstraction, including -hardware. For example, a particular microprocessor had an on-board rotary -switch for arbitrary use by system designers. The switch value was available to -the software via an 8-bit integer located at a dedicated memory address, mapped -like so: +hardware. For example, consider a microprocessor that has an on-board rotary +switch for arbitrary use by system designers. The switch value is +available to the software via an 8-bit integer located at a dedicated +memory address, mapped like so: .. code-block:: ada diff --git a/content/courses/ada-idioms/chapters/abstract_data_types.rst b/content/courses/ada-idioms/chapters/abstract_data_types.rst index 261ddd716..4075383e8 100644 --- a/content/courses/ada-idioms/chapters/abstract_data_types.rst +++ b/content/courses/ada-idioms/chapters/abstract_data_types.rst @@ -15,11 +15,11 @@ 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 advantages. +engineering disadvantages far outweigh the expressiveness advantages. -For the sake of illustration, let's make up a *stack* type that can contain +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 -of convenience.) Let's also say that any given :ada:`Stack` object will contain +of convenience.) Let's also say that any given :ada:`Stack` object can contain at most a fixed number of values, and arbitrarily pick 100 for that upper bound. The likely representation for the :ada:`Stack` type will require both an array for the contained values and a *stack pointer* indicating the *top* of @@ -84,14 +84,14 @@ fuel injectors, the spark plugs, the steering shaft, the tie rods, and everything else |mdash| we'd certainly crash. We use abstraction in programming for the same reason. In higher-level -languages an array is an abstraction for the combination of a base address and +languages, an array is an abstraction for the combination of a base address and offset. A file system is composed of a number of layered abstractions, -including at the top files, then tracks, then sectors, then blocks, ultimately +including files (at the top), then tracks, then sectors, then blocks, and ultimately down to individual bytes. A data structure, such as a stack, a queue, or a -linked list is an example of an abstraction, as is a valve, an air-lock, and +linked list, is an example of an abstraction, as is a valve, an air-lock, and an engine when represented in software. Even procedures and functions are abstractions for lower-level operations. Decomposing via abstractions allows us -to manage complexity because at any given layer we can focus on what is being +to manage complexity because at any given layer we can focus on *what* is being done, rather than how. Therefore, an abstract data type is a type that is abstract in the sense that @@ -129,10 +129,12 @@ Therefore, an ADT package declaration may contain any of the following: - Exceptions - Operations -In general, at most one private type should be declared per ADT package, for -the sake of simplicity. Note that the *limited-with* construct directly -facilitates declaring mutually-dependent private types declared in their own -dedicated packages. However, it is not unreasonable to declare more than one +If possible, you should declare at most one private type per ADT package. +This keeps things simple and follows the "cohesive" principle. (Note that +the *limited-with* construct directly +facilitates declaring mutually-dependent private types that are each declared +in their own dedicated packages). However, it's not unreasonable to +declare more than one private type in the same package, especially if one of the types is clearly the primary type and the other private type is related to the first. For example, in defining an ADT for a maze, we could declare a private type named @@ -141,22 +143,22 @@ them, and as clients have no business knowing how positions are represented, both :ada:`Maze` and :ada:`Position` could reasonably be declared as private types in the same package. -Any form of private type is allowed with this idiom: basic private types, +You may use any form of private type with this idiom: basic private types, tagged/abstract/limited private types, private type extensions, and so forth. What's important is that the representation occurs in the private part so that -it is not compile-time visible to clients. +it's not compile-time visible to clients. -The abstraction's operations will consist of subprograms that each have a -formal parameter of the type. Clients will declare objects of the type and pass -these objects to the formal parameters in order to manipulate those objects. +The abstraction's operations consist of subprograms that each have one or more +formal parameters of the type. Clients will declare objects of the type and pass +these objects as formal parameters to manipulate those objects. The operations are known as *primitive operations* because they have the compile-time visibility to the private type's representation necessary to implement the required behavior. Clients can create their own operations by calling the type's primitive -operations, but client's cannot compile any operation that manipulates the -internal representation directly. +operations, but client's can't compile any operation that manipulates the +internal representation. Consider the following revision to the package :ada:`Integer_Stacks`, now as an ADT: @@ -187,36 +189,37 @@ code that references anything in the private part will compile successfully. The declaration for the type :ada:`Stack` now has two pieces, one in the package visible part and one in the package private part. The visible piece -introduces the type name, ending with the word :ada:`private` to indicate that -the representation is not provided to clients. +introduces the type name and ends with the keyword :ada:`private` to +indicate that its representation is not provided to clients. Client code can use the type name to declare objects because the name is visible. Likewise, clients can declare their own subprograms with parameters of type :ada:`Stack`, or use type :ada:`Stack` as the component type in a -composite type declaration. Clients can use a private type in any way -consistent with the visible type declaration. They just cannot reference the -type's representation details. +composite type declaration. Clients can use a private type in any way that's +consistent with the rest of the visible type declaration, except they can't see +anything representation-dependent. -The full type definition occurs in the package private part. Therefore, for +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| cannot be referenced in client code. -Clients must instead use the operations defined by the package, passing the -client objects to the formal parameters. Only the bodies of these operations +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 have compile-time visibility to the representation of the :ada:`Stack` parameters, so only they can implement the functionality for those parameters. -Because the package-defined subprograms are the only code that can access the +Because package-defined subprograms are the only code that can access the internals of objects of the type, the designer's intended abstract operations -are strictly enforced as the only direct manipulation possible. As we +are strictly enforced. They are the only manipulations that a client can +perform. As we mentioned, basic operations such as assignment are allowed, unless the ADT is -limited as well as private, but these basic operations do not violate the +*limited* as well as private, but these basic operations do not violate the abstraction. -Other ancillary type declarations may of course also be required in the +You may, of course, also require other ancillary type declarations in the package, either for the implementation or as additional parameters for the -visible operations. The array type Content is an example of such an ancillary -type. In this case it is hidden from clients because it is strictly an -implementation artifact. +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. The ADT idiom extends the information hiding applied by the :ref:`Groups of Related Program Units ` @@ -254,18 +257,18 @@ the following ADT approach: In the above, the function :ada:`Make` is a constructor that replaces the use of aggregates for constructing :ada:`Complex_Number` values. Callers pass two floating-point values to be assigned to the components of the resulting record -type. In the :ada:`Stack` ADT a constructor for :ada:`Stack` objects wasn't +type. In the :ada:`Stack` ADT, a constructor for :ada:`Stack` objects wasn't required because any stack has a known initial state, i.e., empty, and the component default initialization is sufficient to achieve that state. Complex -numbers don't have any predeterminable state, it's up to clients, so the -constructor is required. +numbers don't have any predeterminable state so the constructor is required. Likewise, functions :ada:`Real_Part` and :ada:`Imaginary_Part` are selector/accessor functions that return the corresponding individual component -values, given an argument of type :ada:`Complex_Number`. They are provided -because complex numbers have those two parts by definition in mathematics. -Clients can reasonably expect to be able to get such values from a given -object. (The function names need not be distinct from the component names but +values of an argument of type :ada:`Complex_Number`. They are +needed because the mathematical definition of complex numbers has those two +parts, so +clients can reasonably expect to be able to get such values from a given +object. (The function names need not be distinct from the component names, but can be if desired.) However, by default, selector/accessor functions are not included in the ADT @@ -278,22 +281,23 @@ 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 indicating the extent of the object |mdash| that is, the number of values -currently contained. In our example implementation the Top component happens to -indicate that value, beyond indicating the current top of the stack. The body +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 of the :ada:`Extent` function can then be as follows: .. code-block:: ada function Extent (This : Stack) return Natural is (This.Top); -But a different representation might not have a :ada:`Top` component so the -function would be implemented in some other way. (We would have declared a -subtype of :ada:`Natural`, using :ada:`Capacity` as the upper bound, for the -function result type.) +But a different representation might not have a :ada:`Top` component, in +which case the function would be implemented in some other way. (For example, +we could have declared a subtype of :ada:`Natural`, using :ada:`Capacity` +as the upper bound, for the function result type.) -True *getter* functions that do not meet an abstraction-defined requirement and -exist purely to provide client access to the otherwise hidden representation -components should not be included. Their usage makes the client code dependent +You should not include true *getter* functions that do not meet an +abstraction-defined requirement and exist purely to provide client access +to the otherwise hidden representation components included. +Their usage makes the client code dependent on the representation, just as if the client had direct access. For the same reason, by default there are no *setter* procedures for the representation components. Both kinds of operations should be considered highly suspect. @@ -303,15 +307,16 @@ available to clients, albeit indirectly. Pros ---- -The advantages of an ADT result from the strong interface presented, with -guaranteed enforcement by the compiler rather than by reliance on client good +The advantages of an ADT are due to the strong interface presented, with +guaranteed enforcement by the compiler rather than by reliance on clients' good behavior. The ADT designer can rely on client adherence to the intended -abstraction because client code that violates the designer's abstraction -|mdash| directly manipulating the internals of the type |mdash| will not -compile. Clients must call the designer's operations to manipulate the objects. +abstraction because client code that violates the designer's abstraction by +directly manipulating the internals of the type will not +compile; clients must call the designer's operations to manipulate the objects. A package defining a strong interface will exhibit high cohesion, thereby -aiding comprehension and, consequently, both development and maintenance. +aiding comprehension and consequently easing both development and +maintenance. An ADT enhances maintainability because a bug in the ADT implementation must be in the package that defines the ADT itself. The rest of the application need @@ -335,7 +340,8 @@ The private type is the fundamental approach to creating abstractions in Ada, just as the use of the *public*, *private*, and *protected* parts of classes is fundamental to creating abstractions in class-oriented languages. Not every type can be private, as illustrated by the client expectation for array -indexing in Ada. Not every type should be private, for example those that are +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. @@ -361,13 +367,16 @@ The package-oriented idioms described here and are the foundational program composition idioms because packages are the primary structuring unit in Ada. That is especially true of the :ref:`Abstract Data Type ` idiom, which is the -primary type specification facility in Ada. Additional package-oriented idioms -will be described, especially regarding hierarchical packages, but those kinds -of packages are optional. The basic package is not optional in Ada for a +primary type specification facility in Ada. We will +describe additional package-oriented idioms, +especially regarding hierarchical packages, but those kinds +of packages are optional. + +The basic package is not optional in Ada for a program of any significant size or complexity. (One could have a program consisting entirely of the main program, but either that program is relatively -simple and small, or it is badly structured.) As a consequence, other idioms -will exist within packages designed using one of these idioms, or some other +simple and small or it is badly structured.) As a consequence, other idioms +will exist within packages designed using one of these idioms or some other package idiom. 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 b06c8178b..d0dff1523 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 @@ -12,7 +12,9 @@ Motivation In languages supporting object-oriented programming (OOP), including Ada, *constructors* are not inherited when one type is derived from another. That's appropriate because, in general, they would be unable to fully construct values -for the new type. +for the new type. The purpose of this idiom is to explain how Ada defines +constructors, how the language rules prevent constructor inheritance, and how +to design the constructor code in light of those rules. Ada uses tagged types to fully support dynamic OOP. Therefore, in the following, a *derived type* refers to a tagged type that is declared as a @@ -23,9 +25,12 @@ existing parent type. This discussion assumes these tagged types are declared in packages designed using the -:ref:`Abstract Data Type ` (ADT) idiom. In -particular, the parent type is a private type, and the derived type is declared -as a *private extension*. A private extension is a type extension declaration +:ref:`Abstract Data Type ` (ADT) idiom. +We strongly recommend the reader be comfortable with that idiom before +proceeding. + +As abstract data types, the parent type is a private type, and the derived type is +a *private extension*. A private extension is a type extension declaration that does not reveal the components added, if any. The parent type could itself be an extended type, but the point is that these types will all be private types one way or another. Declarations as private types and private extensions @@ -42,24 +47,53 @@ extension: type Circle is new Graphics.Shape with private; -Although this declaration will occur in the public part of a package, as a -private type extension the additional components will not be compile-time -visible to client code, conforming to ADT requirements. That's what the word +This declaration will be in the public part of a package, but, as a private +type extension, the additional components are not compile-time visible to +client code, conforming to ADT requirements. That's what the reserved word :ada:`private` indicates in the type declaration. +Instead of a distinct constructor syntax, Ada uses regular functions to +construct objects. Specifically, so-called *constructor functions* are +functions that return an object of the type. + +.. code-block:: ada + + type Circle is new Graphics.Shape with private; + + function New_Circle (Radius : Float) return Circle; + +Like any function there may be formal parameters specified, but not necessarily. + Functions and procedures that manipulate objects of the private type are *primitive operations* for the type if they are declared in the same package as -the type declaration itself. (That location provides the compile-time visibility -to the type's representation that is required to implement the subprograms.) -Only the primitive operations are inherited during type derivation. +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. -Instead of a distinct constructor syntax, Ada uses regular functions to -construct objects of types but the issues are the same. By definition, these so-called -*constructor functions* return an object of the type in question. Other primitive -functions are not constructors and can be inherited without difficulty. +Declaration with the same package as the type itself provides the +compile-time visibility to the type's representation required to +implement the subprograms. + +Other operations might be declared in the same package too, but if they +do not manipulate or return values of the type they are not primitive +operations for the type. (Their location in that package is somewhat +suspect and should be reviewed explicitly.) + +Primitive operations, and only primitive operations, are inherited during +type derivation. + +If you think in terms of Abstract Data Types all these rules make sense. -Therefore, Ada language rules prevent constructor functions from being legally -inherited, even though they are primitive operations for the type. +Now, here's the rub. + +Constructor functions require that same compile-time visibility so the +intuitive approach will be to declare them in the same package declaration +as the type. As a result, they will be primitive operations for that type. + +However, that means that the constructor functions will be inherited, +contrary to the expectation for constructors. Therefore, Ada has rules +specific to primitive constructor functions that have the effect of preventing +their inheritance. The explanation and illustration for these rules first requires explanation of the word *abstract*. We mentioned above that the package enclosing the @@ -87,17 +121,20 @@ example: procedure Do_Something (This : in out Foo) is abstract; -In contrast, *concrete* types have an actual representation and can be used to -declare objects. Likewise, concrete subprograms are fully implemented, callable -units. In the following discussion, *abstract* has this OOP sense unless stated -otherwise. +Now we can explain how Ada prevents constructor inheritance. + +Whenever a +tagged type is extended, all inherited constructor functions +automatically become abstract functions for the extended type, just as +if they were explicitly declared abstract. + +However, only abstract types can legally have abstract primitive +operations. Concrete types may not, so that we can never dynamically +dispatch to a subprogram without an actual implementation. -With that definition in place, we can explain how Ada prevents constructor inheritance: -whenever a tagged type is extended, all inherited constructor functions automatically -become abstract functions for the extended type. Assuming the extended child -type is not abstract, the type extension will be illegal because only abstract -types can have abstract subprograms. Thus, the compiler prevents this -inappropriate constructor inheritance. +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 package declaration that presents the tagged private type @@ -116,9 +153,8 @@ package declaration that presents the tagged private type end record; end Graphics; -Note in particular the primitive function named :ada:`Make` that constructs a -value of type :ada:`Shape`. The two formal parameters are assigned to the -corresponding two components of the object returned by :ada:`Make`. +Note in particular the primitive constructor function named :ada:`Make` +that constructs a value of type :ada:`Shape`. Because type :ada:`Shape` is tagged, other types can extend it: 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 1abdf5a41..fee8fef68 100644 --- a/content/courses/ada-idioms/chapters/controlling_obj_initialization_creation.rst +++ b/content/courses/ada-idioms/chapters/controlling_obj_initialization_creation.rst @@ -50,24 +50,23 @@ is an access type, the automatic default value :ada:`null` initializes package Binary_Trees is type Tree is limited private; - Null_Tree : constant Tree; ... private type Leaf_and_Branch is record ... type Tree is access Leaf_and_Branch; - Null_Tree : constant Tree := null; + ... end Binary_Trees; In both cases, simply declaring an object in the client code is sufficient to ensure it is initially empty. However, not all abstractions have a meaningful default initial state. Default -initialization will not suffice to fully initialize objects in these cases. -Explicit initialization is required. +initialization will not suffice to fully initialize objects in these cases, so +explicit initialization is required. An explicit procedure call could be used to set the initial state of an object (passed to a mode-out parameter), but there is no guarantee that the call will -occur and no way to force it. +occur and no way to force a client to make it. In contrast, the declaration of the object is guaranteed to occur, and as part of the declaration the object can be given an explicit initial value. The @@ -91,7 +90,7 @@ In the code above, the object to the length of the literal. The specific bounds of :ada:`Reply` are determined by the function, and need not start at :ada:`Positive'First`. -An object cannot be used before it is declared, and since this explicit initial +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. diff --git a/content/courses/ada-idioms/chapters/essential_idioms_for_packages.rst b/content/courses/ada-idioms/chapters/essential_idioms_for_packages.rst index cb8b6442e..ddd6314da 100644 --- a/content/courses/ada-idioms/chapters/essential_idioms_for_packages.rst +++ b/content/courses/ada-idioms/chapters/essential_idioms_for_packages.rst @@ -9,19 +9,20 @@ Essential Design Idioms for Packages Motivation ---------- -Packages, especially library packages, are modules, and as such they are the +Packages, especially library packages, are modules, and as such are the fundamental building blocks of Ada programs. There is no language-prescribed way to use packages when designing an application, the language just specifies what is legal. However, some legal approaches are more advisable than others. -In particular, packages should exhibit high cohesion and loose coupling +Specifically, packages should exhibit high cohesion and loose coupling [1]_. Cohesion is the degree to which the declarations within a module are -related to one another, in terms of the problem being solved. In short, -unrelated entities should not be declared in the same module so that the reader -can focus on one primary concept. Coupling is the degree to which a module +related to one another, in the context of the problem being solved. +Unrelated entities should not be declared in the same module. This allows +the reader to focus on one primary concept, which should be the subject of +the package. Coupling is the degree to which a module depends upon other modules. Loose coupling enhances comprehension and -maintenance because it allows the developer/reader to examine and modify the -module in relative isolation. Coupling and cohesion are interrelated, in that +maintenance because it allows readers and future developers to examine and modify the +module in relative isolation. Coupling and cohesion are interrelated: higher cohesion tends to result in less coupling. @@ -29,25 +30,26 @@ Solution -------- Three idioms for packages were envisioned when the language was first designed. -They were introduced and described in detail by the Rationale document for the -initial language design [2]_. They were then further developed in Grady Booch's +They were introduced and described in detail in the Rationale document for the +initial language design [2]_ and were further developed in Grady Booch's book *Software Engineering with Ada* [3]_, a foundational work on design with the (sequential part of the) language. Booch added a fourth idiom, the Abstract Data Machine, to the three described by the Rationale. These four idioms have proven themselves capable of producing packages that exhibit high cohesion and loose coupling, resulting in more comprehensible and -more maintainable source code. +maintainable source code. -These idioms pre-date later package facilities, such as private packages and -hierarchical packages. Idioms for those packages will be described separately. +These idioms pre-date later package facilities, such as private packages +and hierarchical packages. We describe idioms for those kinds of packages +separately. -Although generic packages are not actually packages, their instantiations are -packages so these design idioms apply to generic packages as well. +Generic packages are not actually packages, but their instantiations are, so +these design idioms apply to generic packages as well. -Because these are idioms for modules, they are differentiated by what the -package declarations can contain. But as you will see, what they can contain is -a reflection of the degree of information hiding applied. +Because these are idioms for modules, we differentiate them by what the +package declarations will contain. But as you will see, what they can +contain is a reflection of the degree of information hiding involved. .. _Ada_Idioms_Named_Collection_Of_Declarations: @@ -55,14 +57,14 @@ a reflection of the degree of information hiding applied. Essential Idiom 1: Named Collection of Declarations --------------------------------------------------- -In the first idiom the package declaration can contain other declarations only -for the following: +In the first idiom, the package declaration can contain other declarations +only for the following: - Objects (constants and variables) - Types - Exceptions -The idea is to factor out the common content required by multiple clients. +The idea is to factor out common content required by multiple clients. Declaring common content in one place and letting clients reference the one unit makes the most sense. @@ -84,7 +86,7 @@ needed: Tropopause_Temperature : constant := -56.5; -- degrees-C end Physical_Constants; -No information hiding is applied with this idiom. +No information hiding is occurring when using this idiom. Pros ~~~~ @@ -92,19 +94,21 @@ Pros Packages designed with this idiom will have high cohesion and low coupling. The idiom also enhances maintainability because changes to the values, if -necessary, need only be made in one place. +necessary, need only be made in one place, although in this particular +example, we would hope that no such changes will be made. Cons ~~~~ When a library package contains variable declarations, these variables comprise -global data. In this sense *global* means potential visibility to multiple +global data. In this sense, *global* means potential visibility to multiple clients. Global data should be avoided by default, because the effects of changes are potentially pervasive, throughout the entire set of clients that have visibility to it. In effect the developer must understand everything before changing anything. The introduction of new bugs is a common result. But if, for some compelling reason, the design really called for global data, this -idiom provides the way to declare it. +idiom provides the way to declare it. Note also that global *constants* +are less problematic than variables because they can't be changed. .. _Ada_Idioms_Groups_Of_Related_Program_Units: @@ -112,19 +116,20 @@ idiom provides the way to declare it. Essential Idiom 2: Groups of Related Program Units -------------------------------------------------- -In this idiom, the package can contain all of the declarations allowed by the -first idiom, but will also contain declarations for operations, usually -subprograms but other units are also allowed, e.g., protected types/objects. -Hence: +In this idiom, the package can contain all of the declarations allowed by +the first idiom, but also contains declarations for operations. These are +usually subprograms but other kinds of declarations are also allowed such +as protected types and objects. Hence these packages can contain: - Objects (constants and variables) - Types - Exceptions - Operations -The intent is that the types declared in the package are used by the -operations, e.g., in the formal parameters and/or function return types. In -particular, though, the types are not private types. +Our intent is that the types declared in the package are used by the +operations declared in the package, typically in their formal parameters +and/or function return types. In this idiom, however, the types are not +private. For example: @@ -138,18 +143,19 @@ For example: -- ... end Linear_Algebra; -In this code, :ada:`Vector` and :ada:`Matrix` are the types under +In this example, :ada:`Vector` and :ada:`Matrix` are the types under consideration. The type :ada:`Real` might be declared here too, but it might be better declared in a :ref:`Named Collection of Declarations ` -package referenced in a with_clause. In any case this package declares types -and subprograms that manipulate values of the types via parameters. +package referenced in a with_clause. In any case, this package declares types +and subprograms that manipulate values of those types. -Variables might also be declared in the package, but not as the central purpose -of the package. Perhaps we want to have a variable whose value is used as the +One might also declare variables in the package, but those should not be +the central purpose of the package. For example, perhaps we want to have a +variable whose value is used as the default for some formal parameters. Clients can change the default for subsequent calls by first assigning a different value to the variable, unlike a -hardcoded literal chosen by the developer. For example: +hardcoded literal chosen by the developer. It would look like this: .. code-block:: ada @@ -161,12 +167,13 @@ hardcoded literal chosen by the developer. For example: (This : Discrete_Input; Debounce_Time : Time_Span := Default_Debounce_Time); -With this idiom, information hiding applies to the implementations of the -visible subprograms in the package body, as well as any internal entities -declared in the body for the sake of implementing the visible subprograms. +With this idiom, information hiding applies to the implementation of the +visible subprograms in the package body as well as any internal entities +declared in the body and used in implementing the visible subprograms. -As mentioned, these idioms apply to generic packages as well. For example, the -more realistic approach would be to make type Real be a generic formal type: +As mentioned, these idioms apply to generic packages as well. For example, +a more realistic approach would be to make type :ada:`Real` be a generic +formal type: .. code-block:: ada @@ -183,15 +190,15 @@ more realistic approach would be to make type Real be a generic formal type: Pros ~~~~ -The types and the associated operations are grouped together, hence highly -cohesive. Such packages usually can be loosely coupled as well. +The types and the associated operations are grouped together and are hence +highly cohesive. Such packages usually can be loosely coupled as well. Clients have all the language-defined operations available that the type representations provide. In the case of :ada:`Vector` and :ada:`Matrix`, clients have compile-time visibility to the fact they are array types. Therefore, clients can manipulate :ada:`Vector` and :ada:`Matrix` values as -arrays: they can create values via aggregates, for example, and can use array -indexing to get to specific components. +arrays: for example, they can create values via aggregates and use array +indexing to access specific components. Cons ~~~~ @@ -199,7 +206,7 @@ Cons Clients can write code that depends on the type's representation, and can be relied upon to do so. Consequently, a change in the representation will potentially require redeveloping the client code, which could be extensive and -expensive. +expensive. That is a serious disadvantage. However, compile-time visibility to the type representations may be necessary to meet client expectations. For example, engineers expect to use indexing @@ -213,7 +220,6 @@ Notes iron-clad; hybrids are possible but should be considered initially suspect and reviewed accordingly. - Bibliography ------------ diff --git a/content/courses/ada-idioms/chapters/inheritance_idioms.rst b/content/courses/ada-idioms/chapters/inheritance_idioms.rst index 764ea9fca..c6b869aa7 100644 --- a/content/courses/ada-idioms/chapters/inheritance_idioms.rst +++ b/content/courses/ada-idioms/chapters/inheritance_idioms.rst @@ -19,7 +19,7 @@ from) some existing type. We will informally refer to the existing ancestor type as the *parent* type, and the new type as the *child* type. The term *Subtype* in the idiom name refers to the child type. -Subtype Inheritance is the most well-known idiom for inheritance because it is +Subtype Inheritance is the most well-known idiom for inheritance because it's based on the notion of a taxonomy, in which categories and disjoint subcategories are identified. For example, we can say that dogs, cats, and dolphins are mammals, and that all mammals are animals: @@ -40,16 +40,16 @@ dolphins are mammals, and that all mammals are animals: Mammal <|-- Dolphin By saying that the subcategories are disjoint we mean that, for example, dogs -are neither cats nor dolphins and cannot be treated as if they are the same. +are neither cats nor dolphins and cannot be treated as if they are. In software, we use various constructs to represent the categories and -subcategories, and use inheritance to organize them. As mentioned above, in Ada +subcategories and use inheritance to organize them. As mentioned above, in Ada, we express that inheritance via derived types representing the categories and subcategories. Ada's strong typing ensures they are treated as disjoint entities. Although the derived child type is distinct from the parent type, the child is -the same kind as the parent type. Some authors use *kind of* as the name for the +the same *kind* as the parent type. Some authors use *kind of* as the name for the relationship between the child and parent. Meyer uses the term *is-a* [1]_, a popular term that we will use too. For example, a cat *is a* mammal, and also is an animal. @@ -58,15 +58,15 @@ The fundamental difference between :ref:`Subtype Inheritance ` and :ref:`Implementation Inheritance ` is whether clients have compile-time visibility to the *is-a* relationship between -the parent and child types. The relationship exists in both idioms but is not -visible to clients in both. In Subtype Inheritance clients do have compile-time -visibility to the relationship, whereas in Implementation Inheritance clients -do not have that visibility. +the parent and child types. The relationship exists in both idioms but is only +visible to clients in one. In Subtype Inheritance, clients do have compile-time +visibility to the relationship, while in Implementation Inheritance, clients +don't have that visibility. Consequently, with Subtype Inheritance, all of the inherited operations become part of the child type's visible interface. In contrast, with Implementation -Inheritance none of those parent capabilities are part of the visible interface. -The inherited parent capabilities are only available internally, to implement +Inheritance, none of those parent capabilities are part of the visible interface: +the inherited parent capabilities are only available internally, to implement the child type's representation and its primitive operations. @@ -88,7 +88,7 @@ expressive power for controlling the client's view of the child and parent types. For example, in Ada, full dynamic OOP capabilities require type declarations to -be decorated with the reserved word :ada:`tagged`. However, from the beginning +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. @@ -183,34 +183,35 @@ Solution There are two *solutions* in this entry, one for each of the two inheritance idioms under discussion. First, we will specify our building block choices, -then we will show the two idiom expressions in separate subsections. +then show the two idiom expressions in separate subsections. -- We will use tagged types for the sake of the full OOP capabilities. That is +- We use tagged types for the sake of providing full OOP capabilities. That is the most common choice when inheritance is involved. The static form of - inheritance has cases in which it is useful, however those cases are very + inheritance has cases in which it is useful, but those cases are very narrow in applicability. -- We will assume that the parent type and the child type are both private +- We assume that the parent type and the child type are both private types, i.e., abstract data types, because that is the best practice. See the :ref:`Abstract Data Type idiom ` for justification and details. -- For the most general capabilities, we will assume that the parent type is +- To provide the most general capabilities, we assume the parent type is visibly tagged. -- We are going to declare the child type in a distinct, dedicated package, +- We're going to declare the child type in a distinct, dedicated package, following the :ref:`ADT idiom `. This package - may or may not be a hierarchical child of the parent package. The solution + may or may not be a child of the parent package. This solution'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 ` solution. -To avoid unnecessary code duplication, examples for the two idiom solutions use -the same parent type, declared as a simple tagged private type. The parent type +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 could itself be derived from some other tagged type, but that changes nothing -conceptually significant. The parent type is declared in package :ada:`P`, as +conceptually significant. We declare parent type in package :ada:`P` as follows: .. code-block:: ada @@ -247,11 +248,11 @@ part of the package: type Child is new Parent with record ... end record; end Q; -The primitive operations from the parent type are implicitly, automatically +The primitive operations from the parent type are implicitly declared immediately after the private extension declaration. That means those operations are in the visible part of the package, hence clients can invoke them. Any additional operations for the client interface will be explicitly -declared in the visible part as well, as will be any overriding declarations for +declared in the visible part as well, as will any overriding declarations for those inherited operations that are to be changed. For example, here is a basic bank account @@ -368,7 +369,7 @@ added to the client API. The package private part and the body of package :ada:`Bank.Interest_Bearing` have visibility to the private part of package :ada:`Bank` because the new -package is a hierarchical child of package :ada:`Bank`. That makes the private +package is a child of package :ada:`Bank`. That makes the private function :ada:`Bank.Total` visible in the child package, along with the components of the record type :ada:`Basic_Account`. @@ -453,7 +454,7 @@ part: -- as the controlling formal parameter end Q; -The primitive operations from the parent type are implicitly, automatically +The primitive operations from the parent type are implicitly declared immediately after the type extension, but these declarations are now located in the package private part. Therefore, the inherited primitive operations are not compile-time visible to clients. Hence clients cannot invoke @@ -462,10 +463,12 @@ package private part and the package body, for use with the implementation of the explicitly declared primitive operations. For example, we might use a *controlled type* in the implementation of a -tagged private type. Clients generally don't have any business directly calling -the operations defined by the two language-defined controlled types so we -usually use implementation inheritance. But if clients did have the need, we -would use Subtype Inheritance instead. +tagged private type. These types have procedures :ada:`Initialize` and +:ada:`Finalize` defined as primitive operations. Both are called automatically +by the compiler. Clients generally don't have any business directly calling +them so we usually use implementation inheritance with controlled types. But if +clients did have the need to call them we +would use Subtype Inheritance instead, to make them visible to clients. For example, the following is a generic package providing an abstract data type for unbounded queues. As such, the :ada:`Queue` type uses dynamic allocation @@ -520,7 +523,7 @@ when objects of the :ada:`Queue` type cease to exist: end Unbounded_Sequential_Queues; The basic operation of assignment usually does not make sense for an abstraction -represented as a linked list, so we declare the private type as limited, in +represented as a linked list, so we declare the private type as *limited*, in addition to tagged and private, and then use the language-defined limited controlled type for the type extension completion in the private part. @@ -529,14 +532,14 @@ the type extension. Both are null procedures that do nothing. We can leave :ada:`Initialize` as-is because initialization is already accomplished via the default values for the :ada:`Queue` components. On the other hand, we want finalization to reclaim all allocated storage so we cannot leave -:ada:`Finalize` as a null procedure. By overriding the procedure we can change -the implementation. That change is usually accomplished by writing the +:ada:`Finalize` as a null procedure. By overriding the procedure, we can change +the implementation. That change is usually accomplished by placing the corresponding procedure body in the package body. However, in this case we have an existing procedure named :ada:`Reset` that is part of the visible (client) API. :ada:`Reset` does exactly what we want :ada:`Finalize` to do, so we implement the overridden :ada:`Finalize` by saying that it is just another name for :ada:`Reset`. No completion body for :ada:`Finalize` is then required or -allowed. This approach has the same semantics as if we did explicitly write a +allowed. This approach has the same semantics as if we explicitly wrote a body for :ada:`Finalize` that simply called :ada:`Reset`, but this is more succinct. Clients can call :ada:`Reset` whenever they want, but the procedure will also be called automatically, via @@ -548,14 +551,14 @@ Pros The two idioms are easily composed simply by controlling where in the enclosing package the parent type is mentioned: either in the declaration of the private -child type in the package visible part, or in the child type's completion in the +child type in the package visible part or in the child type's completion in the package private type. Cons ---- -Although the inheritance expressions are simple in themselves, the many +Although the inheritance expressions are simple by themselves, the many ancillary design choices can make the design effort seem more complicated than it really is. diff --git a/content/courses/ada-idioms/chapters/introduction.rst b/content/courses/ada-idioms/chapters/introduction.rst index 8a6b676eb..eb154ab42 100644 --- a/content/courses/ada-idioms/chapters/introduction.rst +++ b/content/courses/ada-idioms/chapters/introduction.rst @@ -36,15 +36,10 @@ purpose and/or implementation. For example, in the idiom for controlling object creation and initialization, the implementation approach happens to be the same as for expressing a Singleton [1]_. -That's not to say that we could not have a design pattern as an idiom -solution. But if so, the purpose of the idiom entry would be to -illustrate some Ada programming technique, rather than the expression of -the design pattern itself. - In addition to language-independent situations, we also include solutions for situations specific to the Ada language. These idioms are *best practices* in situations that -arise given the capabilities and semantics in the language. +arise given the extensive capabilities of the language. For example, Ada directly supports tasks (threads) via a dedicated construct consisting of local objects and a sequence of statements. @@ -64,8 +59,6 @@ abstractions that manage global data (Abstract Data Machines). Most of the idioms' solutions will be defined using these abstraction techniques as their starting point. -Perhaps instead of *idiom* we should have used the term *cookbook,* but -although appropriate, that term didn't completely convey the intent either. Assumptions ----------- diff --git a/content/courses/ada-idioms/chapters/programming_by_extension.rst b/content/courses/ada-idioms/chapters/programming_by_extension.rst index 7d58f9fac..22346c73f 100644 --- a/content/courses/ada-idioms/chapters/programming_by_extension.rst +++ b/content/courses/ada-idioms/chapters/programming_by_extension.rst @@ -45,7 +45,8 @@ required for the completion of the private type. That was the case with the The array type :ada:`Content` is required for the :ada:`Stack` record component because anonymously-typed array components are illegal. Clients have no business using the type :ada:`Content` directly so although it would be legal -to declare it in the public part, declaration in the private part is proper. +to declare it in the public part, declaration in the private part is more +appropriate. Likewise, a function called to provide the default initial value for a private type's component must be declared prior to the reference. If the function is @@ -83,23 +84,23 @@ with the stack state declared in the package body: function Empty return Boolean is ... end Integer_Stack; -We could add the package private part to the package declaration and move the +We could add the private part to the package declaration and move the state of the :ref:`ADM ` |mdash| the two variables in this case |mdash| up there without any other changes. The subprogram bodies have the same visibility to the two variables either way. (There is no requirement for the :ada:`Content` type because :ada:`Values` is not a record -component. Anonymously-typed array objects are legal.) From the viewpoint of -the language and the abstraction the location is purely up to the developer. +component; anonymously-typed array objects are legal.) From the viewpoint of +the language and the abstraction, the location is purely up to the developer. Solution -------- -When you have a choice, placement in either the package private part or the -package body is reasonable, but only one of the two locations is amenable to -future requirements. +When you have a choice of placement, putting the state in either the package +private part or the package body is reasonable, but only one of the two is +amenable to future requirements. -Specifically, placement in the private part of the package will allow +Specifically, placement in the private part of the package allows *programming by extension* [1]_ via hierarchical *child* packages. Child packages can be written immediately after the *parent* package but can also be written years later, thus accommodating changes due to new requirements. @@ -107,7 +108,7 @@ written years later, thus accommodating changes due to new requirements. Programming by extension allows us to extend an existing package's facilities without having to change the existing package at all. Avoiding source code changes to the existing package is important because doing so might be very -expensive. In certified systems the changed package would require +expensive. In certified systems, the changed package would require re-certification, for example. Changes to the parent package are avoidable because child packages have compile-time visibility to the private part of the ancestor package (actually the entire ancestor package hierarchy, any of which diff --git a/content/courses/ada-idioms/index.rst b/content/courses/ada-idioms/index.rst index fedf387be..3019f7a7d 100644 --- a/content/courses/ada-idioms/index.rst +++ b/content/courses/ada-idioms/index.rst @@ -41,10 +41,9 @@ Ada Idioms .. container:: content-description - This course will teach you the basics of the Ada programming language and - is intended for those who already have a basic understanding of programming - techniques. You will learn how to apply those techniques to programming in - Ada. + This course describes how to implement selected programming idioms in + the Ada language. Prior knowledge of Ada is required, although some + explanations of the underlying semantics are provided when appropriate. This document was written by Patrick Rogers.