-*- Mode: Text; Fonts: Ct18b -*- Chapter 13.3.2: GROUPS OF RELATED PROGRAM UNITS In the previous examples, we were able to group logically related data. It is also possible to group program units, namely, subprograms, tasks, or even other packages. For example, since Ada does not have predefined trigonometric functions, we might need to specify such a package. (Actually, a programmer would not normally have to write such a package; he or she would most likely just reference it as a predefined library unit.) We could specify the package as follows: package transcendental_FUNCTION is type RADIANS is digits 5; type RESULT is digits 7; function COS (ANGLE : in RADIANS) return RESULT range -1.0..1.0; function SIN (ANGLE : in RADIANS) return RESULT range -1.0..1.0; function TAN (ANGLE : in RADIANS) return RESULT; end TRANSCENDENTAL_FUNCTIONS; Notice that we have defined RADIANS and RESULT as different types, both with relative precision, rather than using predefined numerics like FLOAT. Actually, in a production environment, we could increase the utility of the package by adding a generic part so that the package could operate at accuracies defined by the user. We shall do so in the next chapter. Since this specification includes entities other than objects, constants, and types, a package body is required. Of course, a user of the package need not see the body -- how the functions execute is an implementation detail. It would be good practice for the package implementer to compile the package separately. This has the added benefit that if the transcendental algorithms were ever modified (for reasons of efficiency), the change would not affect any program units that use the package. We provide one implementation of a package body in the following example, in which a trigonometric series is used to calculate values. The implementation is not rigorously designed to account for all accuracy considerations (we leave that up to the numerical analysts), but it is sufficient to again illustrate Ada's control features: package body TRANSCENDENTAL_FUNCTIONS is SERIES_LENGTH : constant :=5; function ODD (INDEX : in INTEGER) return BOOLEAN is -- return TRUE if INDEX has an odd value begin return ((INDEX mod 2)/=0); end ODD; function FACTORIAL (VALUE : in INTEGER) return INTEGER is -- determine VALUE! using a recursive function begin if VALUE = 1 then return 1; else return (VALUE * FACTORIAL(VALUE - 1)); end if; end FACTORIAL; function TERM (ANGLE : in RADIANS, POWER : in INTEGER, NUMBER : IN INTEGER) return RESULT is -- calculate a term of the trigonometric series begin if ODD (NUMBER) then return RESULT(-((ANGLE)**POWER)/(RADIANS(FACTORIAL(POWER)))); else return RESULT (+((ANGLE)**POWER)/(RADIANS(FACTORIAL(POWER)))); end if; end TERM; function COS (ANGLE : in RADIANS) return RESULT range -1.0..1.0 is -- calculate the cosine of ANGLE ANSWER : RESULT; POWER : INTEGER; begin ANSWER := 1.0 for | in reverse 1 .. SERIES_LENGTH loop POWER := | * 2; ANSWER := ANSWER + TERM(ANGLE, POWER, NUMBER); end loop; return ANSWER; end COS; function SIN (ANGLE : in RADIANS) return RESULT range -1.0..1.0 is -- calculate the sine of ANGLE ANSWER : RESULT; POWER : INTEGER; begin ANSWER:= RESULT(ANGLE); for | in reverse 1 .. SERIES_LENGTH loop POWER := (|*2) + 1; ANSWER := ANSWER + TERM(ANGLE, POWER, NUMBER); end loop; return ANSWER; end SIN; function TAN (ANGLE : in RADIANS) return RESULT is -- calculate the tangent of ANGLE begin return (SIN(ANGLE)/COS(ANGLE)); end TAN end TRANSCENDENTAL_FUNCTIONS; In this implementation, note particularly the use of the SERIES_ LENGTH constant that determines the length of the trigonometric series. By declaring this value as a constant, it is easier for a programmer to modify the package later. Furthermore, the body does not have the optional sequence of statements for initialization, but three local subprograms are declared to complete the implementation. These functions, ODD, FACTORIAL, and TERM, are hidden in the body and may therefore not be accessed outside the package. Within COS and SIN, we step through the series from the smallest term to the largest (to minimize roundoff errors). For the TAN subprogram, the facilities provided by COS and SIN are used. Graphics packages demonstrate another application of Ada packages as collections of subprograms. Since transformations (ROTATE, SCALE, and TRANSLATE) are common graphics algorithms, a user might be provided with the following package specification: package TWO_D_TRANSFORM is type COORDINATE is record X : FLOAT; Y : FLOAT; end record; procedure ROTATE (POINT : in out COORDINATE; ANGLE : in FLOAT) procedure SCALE (POINT : in out COORDINATE; X,Y : in FLOAT); procedure TRANSLATE (POINT : in out COORDINATE; X,Y ; in FLOAT); end TWO_D_TRANSFORM; We have somewhat violated our philosophy of never using predefined types such as FLOAT, but we have done so here to simplify our solution and also to show some more applications of type transformations. Our package will need the resources of the TRANSCENDENTAL_FUNCTIONS, but we can hide the reference in the TWO_D_TRANSFORM body. We apply the use clause to permit shorter names in the implementation; since the package specification is small, there is little chance of ambiguity. One possible body is as follows: with TRANSCENDENTAL_FUNCTIONS; use TRANSCENDENTAL_FUNCTIONS; package body TWO_D_TRANSFORM is procedure ROTATE (POINT : in out COORDINATE; ANGLE : in FLOAT) is --rotate the POINT by ANGLE radians about the origin TEMP: COORDINATE ;= POINT begin POINT.X:= (POINT.X*FLOAT(COS(RADIANS(ANGLE)))) + (POINT.Y*FLOAT(SIN(RADIANS(ANGLE)))); POINT.Y:= -(POINT.X*FLOAT(SIN(RADIANS(ANGLE)))) + (POINT.Y*FLOAT(COS(RADIANS(ANGLE)))); end ROTATE; procedure SCALE(POINT : in out COORDINATE; X,Y : in FLOAT) is --scale the POINT by a factor of X and Y begin POINT.X:=POINT.X*Y; POINT.Y:=POINT.Y*Y; end SCALE; procedure TRANSLATE(POINT : in out COORDINATE; X,Y : in FLOAT) is --translate the POINT by a distance of X and Y begin POINT.X:=POINT.X + X; POINT.Y:=POINT.Y + Y; end TRANSLATE; end TWO_D_TRANSFORM; As before, package initialization is not required. So far, we have shown applications of packages as collections of subprograms, but it is also reasonable to use packages to collect other packages or problem.