8 views

Skip to first unread message

Jul 19, 1995, 3:00:00 AM7/19/95

to

Archive-name: computer-lang/Ada/programming/part2

Comp-lang-ada-archive-name: programming/part2

Posting-Frequency: monthly

Last-modified: 30 June 1995

Last-posted: 21 May 1995

Comp-lang-ada-archive-name: programming/part2

Posting-Frequency: monthly

Last-modified: 30 June 1995

Last-posted: 21 May 1995

Ada Programmer's

Frequently Asked Questions (FAQ)

IMPORTANT NOTE: No FAQ can substitute for real teaching and

documentation. There is an annotated list of Ada books in the

companion comp.lang.ada FAQ.

Recent changes to this FAQ are listed in the first section after the table

of contents. This document is under explicit copyright.

This is part 2 of a 3-part posting.

Part 3 begins with question 8.4; it should be the next posting in this thread.

Part 1 should be the previous posting in this thread.

5.6: What do "covariance" and "contravariance" mean, and does Ada support

either or both?

(From Robert Martin) [This is C++ stuff, it must be completely

re-written for Ada. --MK]

R> covariance: "changes with"

R> contravariance: "changes against"

R> class A

R> {

R> public:

R> A* f(A*); // method of class A, takes A argument and returns A

R> A* g(A*); // same.

R> };

R> class B : public A // class B is a subclass of class A

R> {

R> public:

R> B* f(B*); // method of class B overrides f and is covariant.

R> A* g(A*); // method of class B overrides g and is contravariant.

R> };

R> The function f is covariant because the type of its return value and

R> argument changes with the class it belongs to. The function g is

R> contravariant because the types of its return value and arguments does not

R> change with the class it belongs to.

Actually, I would call g() invariant. If you look in Sather, (one of

the principle languages with contravariance), you will see that the

method in the decendent class actually can have aruments that are

superclasses of the arguments of its parent. So for example:

class A : public ROOT

{

public:

A* f(A*); // method of class A, takes A argument and returns A

A* g(A*); // same.

};

class B : public A // class B is a subclass of class A

{

public:

B* f(B*); // method of class B overrides f and is covariant.

ROOT* g(ROOT*); // method of class B overrides g and is contravariant.

};

To my knowledge the uses for contravariance are rare or nonexistent.

(Anyone?). It just makes the rules easy for the compiler to type

check. On the other hand, co-variance is extremely useful. Suppose you

want to test for equality, or create a new object of the same type as

the one in hand:

class A

{

public:

BOOLEAN equal(A*);

A* create();

}

class B: public A

{

public:

BOOLEAN equal(B*);

B* create();

}

Here covariance is exactly what you want. Eiffel gives this to you,

but the cost is giving up 100% compile time type safety. This seem

necessary in cases like these.

In fact, Eiffel gives you automatic ways to make a method covariant,

called "anchored types". So you could declare, (in C++/eiffese):

class A

{

public:

BOOLEAN equal(like Current *);

like Current * create();

}

Which says equal takes an argument the same type as the current

object, and create returns an object of the same type as current. Now,

there is not even any need to redeclare these in class B. Those

transformations happen for free!

5.7: What is meant by upcasting/expanding and downcasting/narrowing?

(Tucker Taft replies):

Here is the symmetric case to illustrate upcasting and downcasting.

type A is tagged ...; -- one parent type

type B is tagged ...; -- another parent type

...

type C; -- the new type, to be a mixture of A and B

type AC (Obj : access C'Class) is

new A

with ...;

-- an extension of A to be mixed into C

type BC (Obj : access C'Class) is

new B

with ...;

-- an extension of B to be mixed into C

type C is

tagged limited record

A : AC (C'Access);

B : BC (C'Access);

... -- other stuff if desired

end record;

We can now pass an object of type C to anything that takes an A or B

as follows (this presumes that Foobar and QBert are primitives of A

and B, respectively, so they are inherited; if not, then an explicit

conversion (upcast) to A and B could be used to call the original

Foobar and QBert).

XC : C;

...

Foobar (XC.A);

QBert (XC.B);

If we want to override what Foobar does, then we override Foobar on

AC. If we want to override what QBert does, then we override QBert on

BC.

Note that there are no naming conflicts, since AC and BC are distinct

types, so even if A and B have same-named components or operations, we

can talk about them and/or override them individually using AC and BC.

Upcasting (from C to A or C to B) is trivial -- A(XC.A) upcasts to A;

B(XC.B) upcasts to B.

Downcasting (narrowing) is also straightforward and safe. Presuming XA

of type A'Class, and XB of type B'Class:

AC(XA).Obj.all downcasts to C'Class (and verifies XA in AC'Class)

BC(XB).Obj.all downcasts to C'Class (and verifies XB in BC'Class)

You can check before the downcast to avoid a Constraint_Error:

if XA not in AC'Class then -- appropriate complaint

if XB not in BC'Class then -- ditto

The approach is slightly simpler (though less symmetric) if we choose

to make A the "primary" parent and B a "secondary" parent:

type A is ...

type B is ...

type C;

type BC (Obj : access C'Class) is

new B

with ...

type C is

new A

with record

B : BC (C'Access);

... -- other stuff if desired

end record;

Now C is a "normal" extension of A, and upcasting from C to A and

(checked) downcasting from C'Class to A (or A'Class) is done with

simple type conversions. The relationship between C and B is as above

in the symmetric approach.

Not surprisingly, using building blocks is more work than using a

"builtin" approach for simple cases that happen to match the builtin

approach, but having building blocks does ultimately mean more

flexibility for the programmer -- there are many other structures that

are possible in addition to the two illustrated above, using the

access discriminant building block.

For example, for mixins, each mixin "flavor" would have an access

discriminant already:

type Window is ... -- The basic "vanilla" window

-- Various mixins

type Win_Mixin_1 (W : access Window'Class) is ...

type Win_Mixin_2 (W : access Window'Class) is ...

type Win_Mixin_3 (W : access Window'Class) is ...

Given the above vanilla window, plus any number of window mixins, one

can construct a desired window by including as many mixins as wanted:

type My_Window is

new Window

with record

M1 : Win_Mixin_1 (My_Window'access);

M3 : Win_Mixin_3 (My_Window'access);

M11 : Win_Mixin_1(My_Window'access);

... -- plus additional stuff, as desired.

end record;

As illustrated above, you can incorporate the same "mixin" multiple

times, with no naming conflicts. Every mixin can get access to the

enclosing object. Operations of individual mixins can be overridden by

creating an extension of the mixin first, overriding the operation in

that, and then incorporating that tweaked mixin into the ultimate

window.

I hope the above helps better illustrate the use and flexibility of

the Ada 9X type composition building blocks.

5.8: How does Ada do "narrowing"?

Dave Griffith said

. . . Nonetheless, The Ada9x committee chose a structure-based

subtyping, with all of the problems that that is known to cause. As

the problems of structure based subtyping usually manifest only in

large projects maintained by large groups, this is _precisely_ the

subtype paradigm that Ada9x should have avoided. Ada9x's model is,

as Tucker Taft pointed out, quite easy to use for simple OO

programming. There is, however, no good reason to _do_ simple OO

programming. OO programmings gains click in somewhere around 10,000

LOC, with greatest gains at over 100,000. At these sizes, "just

declare it tagged" will result in unmaintainable messes. OO

programming in the large rapidly gets difficult with structure based

subtyping. Allowing by-value semantics for objects compounds these

problems. All of this is known. All of this was, seemingly, ignored

by Ada9x.

(Tucker Taft answers)

As explained in a previous note, Ada 9X supports the ability to hide

the implementation heritage of a type, and only expose the desired

interface heritage. So we are not stuck with strictly "structure-based

subtyping." Secondly, by-reference semantics have many "well known"

problems as well, and the designers of Modula-3 chose to, seemingly,

ignore those ;-) ;-). Of course, in reality, neither set of language

designers ignored either of these issues. Language design involves

tradeoffs. You can complain we made the wrong tradeoff, but to

continue to harp on the claim that we "ignored" things is silly. We

studied every OOP language under the sun on which we could find any

written or electronic material. We chose value-based semantics for

what we believe are good reasons, based on reasonable tradeoffs.

First of all, in the absence of an integrated garbage collector,

by-reference semantics doesn't make much sense. Based on various

tradeoffs, we decided against requiring an integrated garbage

collector for Ada 9X.

Secondly, many of the "known" problems with by-value semantics we

avoided, by eliminating essentially all cases of "implicit

truncation." One of the problems with the C++ version of "value

semantics" is that on assignment and parameter passing, implicit

truncation can take place mysteriously, meaning that a value that

started its life representing one kind of thing gets truncated

unintentionally so that it looks like a value of some ancestor type.

This is largely because the name of a C++ class means differnt things

depending on the context. When you declare an object, the name of the

class determines the "exact class" of the object. The same thing

applies to a by-value parameter. However, for references and pointers,

the name of a class stands for that class and all of its derivatives.

But since, in C++, a value of a subclass is always acceptable where a

value of a given class is expected, you can get implicit truncation as

part of assignment and by-value parameter passing. In Ada 9X, we avoid

the implicit truncation because we support assignment for "class-wide"

types, which never implicitly truncates, and one must do an explicit

conversion to do an assignment that truncates. Parameter passing never

implicitly truncates, even if an implicit conversion is performed as

part of calling an inherited subprogram.

In any case, why not either ignore Ada 9X or give it a fair shot? It

is easy to criticize any particular design decision, but it is much

harder to actually put together a complete integrated language design

that meets the requirements of its user community, doesn't bankrupt

the vendor community, and provides interesting fodder for the academic

community ;-).

5.9: What is the difference between a class-wide access type and a "general"

class-wide access type?

What is exactly the difference between

type A is access Object'Class;

and

type B is access all Object'Class;

In the RM and Rationale only definitions like B are used. What's the

use for A-like definitions ?

(Tucker Taft answers)

The only difference is that A is more restrictive, and so presumably

might catch bugs that B would not. A is a "pool-specific" access type,

and as such, you cannot convert values of other access types to it,

nor can you use 'Access to create values of type A. Values of type A

may only point into its "own" pool; that is only to objects created by

allocators of type A. This means that unchecked-deallocation is

somewhat safer when used with a pool-specific type like A.

B is a "general" access type, and you can allocate in one storage

pool, and then convert the access value to type B and store it into a

variable of type B. Similarly, values of type B may point at objects

declared "aliased."

When using class-wide pointer types, type conversion is sometimes used

for "narrowing." This would not in general be possible if you had left

out the "all" in the declaration, as in the declaration of A. So, as a

general rule, access-to-classwide types usually need to be general

access types. However, there is no real harm in starting out with a

pool-specific type, and then if you find you need to do a conversion

or use 'Access, the compiler should notify you that you need to add

the "all" in the declaration of the type. This way you get the added

safety of using a pool-specific access type, until you decide

explicitly that you need the flexibility of general access types.

In some implementations, pool-specific access types might have a

shorter representation, since they only need to be able to point at

objects in a single storage pool. As we move toward 64-bit address

spaces, this might be a significant issue. I could imagine that

pool-specific access types might remain 32-bits in some

implementations, while general access types would necessarily be

64-bits.

_________________________________________________________________

6: Ada Numerics

6.1: Where can I find anonymous ftp sites for Ada math packages? In particular

where are the random number generators?

ftp.rational.com

Freeware version of the ISO math packages on Rational's FTP

server. It's a binding over the C Math library, in

public/apex/freeware/math_lib.tar.Z

archimedes.nosc.mil

Stuff of high quality in pub/ada The random number generator

and random deviates are recommended. These are mirrored at the

next site, wuarchive.

wuarchive.wustl.edu

Site of PAL, the Public Ada Library: math routines scattered

about in the directories under languages/ada in particular, in

subdirectory swcomps

source.asset.com

This is not an anonymous ftp site for math software. What you

should do is log on anonymously under ftp, and download the

file asset.faq from the directory pub. This will tell you how

to get an account.

ftp.cs.kuleuven.ac.be

Go to directory pub/Ada-Belgium/cdrom. There's a collection of

math intensive software in directory swcomps. Mirrors some of

PAL at wuarchive.wustl.edu.

sw-eng.falls-church.va.us

Go to directory public/adaic/tools/atip/adar to find

extended-precision decimal arithmetic (up to 18 digits).

Includes facilities for COBOL-like formatted output.

6.2: How can I write portable code in Ada 83 using predefined types like Float

and Long_Float? Likewise, how can I write portable code that uses Math

functions like Sin and Log that are defined for Float and Long_Float?

(from Jonathan Parker)

Ada 83 was slow to arrive at a standard naming convention for

elementary math functions and complex numbers. Furthermore, you'll

find that some compilers call the 64-bit floating point type

Long_Float; other compilers call it Float. Fortunately, it is easy to

write programs in Ada that are independent of the naming conventions

for floating point types and independent of the naming conventions of

math functions defined on those types.

One of the cleanest ways is to make the program generic:

generic

type Real is digits <>;

with function Arcsin (X : Real) return Real is <>;

with function Log (X : Real) return Real is <>;

-- This is the natural log, inverse of Exp(X), sometimes written Ln(X).

package Example_1 is

...

end Example_1;

So the above package doesn't care what the name of the floating point

type is, or what package the Math functions are defined in, just as

long as the floating point type has the right attributes (precision

and range) for the algorithm, and likewise the functions. Everything

in the body of Example_1 is written in terms of the abstract names,

Real, Arcsin, and Log, even though you instantiate it with compiler

specific names that can look very different:

package Special_Case is new Example_1 (Long_Float, Asin, Ln);

The numerical algorithms implemented by generics like Example_1 can

usually be made to work for a range of floating point precisions. A

well written program will perform tests on Real to reject

instantiations of Example_1 if the floating points type is judged

inadequate. The tests may check the number of digits of precision in

Real (Real'Digits) or the range of Real (Real'First, Real'Last) or the

largest exponent of the set of safe numbers (Real'Safe_Emax), etc.

These tests are often placed after the begin statement of package

body, as in:

package body Example_1 is

...

begin

if (Real'Machine_Mantissa > 60) or (Real'Machine_Emax < 256) then

raise Program_Error;

end if;

end Example_1;

Making an algorithm as abstract as possible, (independent of data

types as much as possible) can do a lot to improve the quality of the

code. Support for abstraction is one of the many things Ada-philes

find so attractive about the language. The designers of Ada 95

recognized the value of abstraction in the design of numeric

algorithms and have generalized many of the features of the '83 model.

For example, no matter what floating point type you instantiate

Example_1 with, Ada 95 provides you with functions for examining the

exponent and the mantissas of the numbers, for truncating, determining

exact remainders, scaling exponents, and so on. (In the body of

Example_1, and in its spec also of course, these functions are

written, respectively: Real'Exponent(X), Real'Fraction(X),

Real'Truncation(X), Real'Remainder(X,Y), Real'Scaling(X, N). There are

others.) Also, in package Example_1, Ada 95 lets you do the arithmetic

on the base type of Real (called Real'Base) which is liable to have

greater precision and range than type Real.

It is rare to see a performance loss when using generics like this.

However, if there is an unacceptable performance hit, or if generics

cannot be used for some other reason, then subtyping and renaming will

do the job. Here is an example of renaming:

with Someones_Math_Lib;

procedure Example_2 is

subtype Real is Long_Float;

package Math renames Someones_Math_Lib;

function Arcsin(X : Real) return Real renames Math.Asin

function Log (X : Real) return Real renames Math. Ln;

-- Everything beyond this point is abstract with respect to

-- the names of the floating point (Real), the functions (Arcsin

-- and Log), and the package that exported them (Math).

...

end Example_2;

I prefer to make every package and subprogram (even test procedures)

as compiler independent and machine portable as possible. To do this

you move all of the renaming of compiler dependent functions and all

of the "withing" of compiler dependent packages to a single package.

In the example that follows, its called Math_Lib_8. Math_Lib_8 renames

the 8-byte floating point type to Real_8, and makes sure the math

functions follow the Ada 95 standard, at least in name. In this

approach Math_Lib_8 is the only compiler dependent component.

There are other, perhaps better, ways also. See for example, "Ada In

Action", by Do-While Jones for a generic solution.

Here's the spec of Math_Lib_8, which is a perfect subset of package

Math_Env_8, available by FTP in file

ftp://lglftp.epfl.ch/pub/Ada/FAQ/math_env_8.ada

--***************************************************************

-- Package Math_Lib_8

--

-- A minimal math package for Ada 83: creates a standard interface to vendor

-- specific double-precision (8-byte) math libraries. It renames the 8 byte

-- Floating point type to Real_8, and uses renaming to create

-- (Ada 95) standard names for Sin, Cos, Log, Sqrt, Arcsin, Exp,

-- and Real_8_Floor, all defined for Real_8.

--

-- A more ambitious but perhaps less efficient

-- package would wrap the compiler specific functions in function calls, and

-- do error handling on the arguments to Ada 95 standards.

--

-- The package assumes that Real_8'Digits > 13, and that

-- Real_8'Machine_Mantissa < 61. These are asserted after the

-- begin statement in the body.

--

-- Some Ada 83 compilers don't provide Arcsin, so a rational-polynomial+

-- Newton-Raphson method Arcsin and Arccos pair are provided in the body.

--

-- Some Ada 83 compilers don't provide for truncation of 8 byte floats.

-- Truncation is provided here in software for Compilers that don't have it.

-- The Ada 95 function for truncating (toward neg infinity) is called 'Floor.

--

-- The names of the functions exported below agree with the Ada9X standard,

-- but not, in all likelihood the semantics. It is up to the user to

-- be careful...to do his own error handling on the arguments, etc.

-- The performance of these function can be non-portable,

-- but in practice they have their usual meanings unless you choose

-- weird arguments. The issues are the same with most math libraries.

--***************************************************************

--with Math_Lib; -- Meridian DOS Ada.

with Long_Float_Math_Lib; -- Dec VMS

--with Ada.Numerics.Generic_Elementary_Functions; -- Ada9X

package Math_Lib_8 is

--subtype Real_8 is Float; -- Meridian 8-byte Real

subtype Real_8 is Long_Float; -- Dec VMS 8-byte Real

--package Math renames Math_Lib; -- Meridian DOS Ada

package Math renames Long_Float_Math_Lib; -- Dec VMS

--package Math is new Ada.Numerics.Generic_Elementary_Functions(Real_8);

-- The above instantiation of the Ada.Numerics child package works on

-- GNAT, or any other Ada 95 compiler. Its here if you want to use

-- an Ada 95 compiler to compile Ada 83 programs based on this package.

function Cos (X : Real_8) return Real_8 renames Math.Cos;

function Sin (X : Real_8) return Real_8 renames Math.Sin;

function Sqrt(X : Real_8) return Real_8 renames Math.Sqrt;

function Exp (X : Real_8) return Real_8 renames Math.Exp;

--function Log (X : Real_8) return Real_8 renames Math.Ln; -- Meridian

function Log (X : Real_8) return Real_8 renames Math.Log; -- Dec VMS

--function Log (X : Real_8) return Real_8 renames Math.Log; -- Ada 95

--function Arcsin (X : Real_8) return Real_8 renames Math.Asin; -- Dec VMS

--function Arcsin (X : Real_8) return Real_8 renames Math.Arcsin; -- Ada 95

function Arcsin (X : Real_8) return Real_8;

-- Implemented in the body. Should work with any compiler.

--function Arccos (X : Real_8) return Real_8 renames Math.Acos; -- Dec VMS

--function Arccos (X : Real_8) return Real_8 renames Math.Arccos; -- Ada 95

function Arccos (X : Real_8) return Real_8;

-- Implemented in the body. Should work with any compiler.

--function Real_8_Floor (X : Real_8) return Real_8 renames Real_8'Floor;-- 95

function Real_8_Floor (X : Real_8) return Real_8;

-- Implemented in the body. Should work with any compiler.

end Math_Lib_8;

6.3: Is Ada any good at numerics, and where can I learn more about it?

First of all, a lot of people find the general Ada philosophy

(modularity, strong-typing, readable syntax, rigorous definition and

standardization, etc.) to be a real benefit in numerical programming,

as well as in many other types of programming. But Ada --and

especially Ada 95-- was also designed to meet the special requirements

of number-crunching applications.

The following sketches out some of these features. Hopefully a little

of the flavor of the Ada philosophy will get through, but the best

thing you can do at present is to read the two standard reference

documents, the Ada 95 Rationale and Reference Manual. Below the GNU

Ada 95 compiler is referred to several times. This compiler can be

obtained by anonymous FTP from cs.nyu.edu, and at mirror sites

declared in the README file of directory pub/gnat.

1. Machine portable floating point declarations. (Ada 83 and Ada 95)

If you declare "type Real is digits 14", then type Real will

guarantee you (at least) 14 digits of precision independently

of machine or compiler. In this case the base type of type Real

will usually be the machine's 8-byte floating point type. If an

appropriate base type is unavailable (very rare), then the

declaration is rejected by the compiler.

2. Extended precision for initialization of floating point. (Ada 83

and Ada 95)

Compilers are required to employ

extended-precision/rational-arithmetic routines so that

floating point variables and constants can be correctly

initialized to their full precision.

3. Generic packages and subprograms. (Ada 83 and Ada 95)

Algorithms can be written so that they perform on abstract

representations of the data structure. Support for this is

provided by Ada's generic facilities (what C++ programmers

would call templates).

4. User-defined operators and overloaded subprograms. (Ada 83 and Ada

95)

The programmer can define his own operators (functions like

"*", "+", "abs", "xor", "or", etc.) and define any number of

subprograms with the same name (provided they have different

argument profiles).

5. Multitasking. (Ada 83 and Ada 95)

Ada facilities for concurrent programming (multitasking) have

traditionally found application in simulations and

distributed/parallel programming. Ada tasking is an especially

useful ingredient in the Ada 95 distributed programming model,

and the combination of the two makes it possible to design

parallel applications that have a high degree of operating

system independence and portability. (More on this in item 6

below.)

6. Direct support for distributed/parallel computing in the language.

(Ada 95)

Ada 95 is probably the first internationally standardized

language to combine in the same design complete facilities for

multitasking and parallel programming. Communication between

the distributed partitions is via synchronous and asynchronous

remote procedure calls.

Good discussion, along with code examples, is found in the

Rationale, Part III E, and in the Ada 95 Reference Manual,

Annex E. See also "Ada Letters", Vol. 13, No. 2 (1993), pp. 54

and 78, and Vol. 14, No. 2 (1994), p. 80. (Full support for

these features is provided by compilers that conform to the Ada

95 distributed computing Annex. This conformance is optional,

but for instance GNAT, the Gnu Ada 95 compiler, will meet these

requirements.)

7. Attributes of floating point types. (Ada 83 and Ada 95)

For every floating point type (including user defined types),

there are built-in functions that return the essential

characteristics of the type. For example, if you declare "type

Real is digits 15" then you can get the max exponent of objects

of type Real from Real'Machine_Emax. Similarly, the size of the

Mantissa, the Radix, the largest Real, and the Rounding policy

of the arithmetic are given by Real'Machine_Mantissa,

Real'Machine_Radix, Real'Last, and Real'Machine_Rounds. There

are many others.

(See Ada 95 Reference Manual, clause 3.5, subclause 3.5.8 and

A.5.3, as well as Part III sections G.2 and G.4.1 of the Ada 95

Rationale.)

8. Attribute functions for floating point types. (Ada 95)

For every floating point type (including user defined types),

there are built-in functions that operate on objects of that

type. For example, if you declare "type Real is digits 15" then

Real'Remainder (X, Y) returns the exact remainder of X and Y: X

- n*Y where n is the integer nearest X/Y. Real'Truncation(X),

Real'Max(X,Y), Real'Rounding(X) have the usual meanings.

Real'Fraction(X) and Real'Exponent(X) break X into mantissa and

exponent; Real'Scaling(X, N) is exact scaling: multiplies X by

Radix**N, which can be done by incrementing the exponent by N,

etc. (See citations in item 7.)

9. Modular arithmetic on integer types. (Ada 95)

If you declare "type My_Unsigned is mod N", for arbitrary N,

then arithmetic ("*", "+", etc.) on objects of type My_Unsigned

returns the results modulo N. Boolean operators "and", "or",

"xor", and "not" are defined on the objects as though they were

arrays of bits (and likewise return results modulo N). For N a

power of 2, the semantics are similar to those of C unsigned

types.

10. Generic elementary math functions for floating point types. (Ada

95)

Required of all compilers, and provided for any floating point

type: Sqrt, Cos, Sin, Tan, Cot, Exp, Sinh, Cosh, Tanh, Coth,

and the inverse functions of each of these, Arctan, Log,

Arcsinh, etc. Also, X**Y for floating point X and Y. Compilers

that conform to the Numerics Annex meet additional accuracy

requirements.

(See subclause A.5.1 of the Ada 95 RM, and Part III, Section

A.3 of the Ada 95 Rationale.)

11. Complex numbers. (Ada 95)

Fortran-like, but with a new type called Imaginary. Type

"Imaginary" allows programmers to write expressions in such a

way that they are easier to optimize, more readable and appear

in code as they appear on paper. Also, the ability to declare

object of pure imaginary type reduces the number of cases in

which premature type conversion of real numbers to complex

causes floating point exceptions to occur. (Provided by

compilers that conform to the Numerics Annex. The Gnu Ada 95

compiler supports this annex, so the source code is freely

available.)

12. Generic elementary math functions for complex number types. (Ada

95)

Same functions supported for real types, but with complex

arguments. Standard IO is provided for floating point types and

Complex types. (Only required of compilers that support the

Numerics Annex, like Gnu Ada.)

13. Pseudo-random numbers for discrete and floating point types. (Ada

95)

A floating point pseudo-random number generator (PRNG) provides

output in the range 0.0 .. 1.0. Discrete: A generic PRNG

package is provided that can be instantiated with any discrete

type: Boolean, Integer, Modular etc. The floating point PRNG

package and instances of the (discrete) PRNG package are

individually capable of producing independent streams of random

numbers. Streams may be interrupted, stored, and resumed at

later times (generally an important requirement in

simulations). In Ada it is considered important that multiple

tasks, engaged for example in simulations, have easy access to

independent streams of pseudo random numbers. The Gnu Ada 95

compiler provides the cryptographically secure X**2 mod N

generator of Blum, Blum and Shub.

(See subclause A.5.2 of the Ada 95 Reference Manual, and part

III, section A.3.2 of the Ada Rationale.)

14. Well-defined interfaces to Fortran and other languages. (Ada 83

and Ada 95)

It has always been a basic requirement of the language that it

provide users a way to interface Ada programs with foreign

languages, operating system services, GUI's, etc. Ada can be

viewed as an interfacing language: its module system is

composed of package specifications and separate package bodies.

The package specifications can be used as strongly-type

interfaces to libraries implemented in foreign languages, as

well as to package bodies written in Ada. Ada 95 extends on

these facilities with package interfaces to the basic data

structures of C, Fortran, and COBOL and with new pragmas. For

example, "pragma Convention(Fortran, M)" tells the compiler to

store the elements of matrices of type M in the Fortran

column-major order. (This pragma has already been implemented

in the Gnu Ada 95 compiler. Multi- lingual programming is also

a basic element of the Gnu compiler project.) As a result,

assembly language BLAS and other high performance linear

algebra and communications libraries will be accessible to Ada

programs.

(See Ada 95 Reference Manual: clause B.1 and B.5 of Annex B,

and Ada 95 Rationale: Part III B.)

6.4: How do I get Real valued and Complex valued math functions in Ada 95?

(from Jonathan Parker)

Complex type and functions are provided by compilers that support the

numerics Annex. The packages that use Float for the Real number and

for the Complex number are:

Ada.Numerics.Elementary_Functions;

Ada.Numerics.Complex_Types;

Ada.Numerics.Complex_Elementary_Functions;

The packages that use Long_Float for the Real number and for the

Complex number are:

Ada.Numerics.Long_Elementary_Functions;

Ada.Numerics.Long_Complex_Types;

Ada.Numerics.Long_Complex_Elementary_Functions;

The generic versions are demonstrated in the following example. Keep

in mind that the non-generic packages may have been better tuned for

speed or accuracy. In practice you won't always instantiate all three

packages at the same time, but here is how you do it:

with Ada.Numerics.Generic_Complex_Types;

with Ada.Numerics.Generic_Elementary_Functions;

with Ada.Numerics.Generic_Complex_Elementary_Functions;

procedure Do_Something_Numerical is

type Real_8 is digits 15;

package Real_Functions_8 is

new Ada.Numerics.Generic_Elementary_Functions (Real_8);

package Complex_Nums_8 is

new Ada.Numerics.Generic_Complex_Types (Real_8);

package Complex_Functions_8 is

new Ada.Numerics.Generic_Complex_Elementary_Functions

(Complex_Nums_8);

use Real_Functions_8, Complex_Nums_8, Complex_Functions_8;

...

... -- Do something

...

end Do_Something_Numerical;

6.5: What libraries or public algorithms exist for Ada?

An Ada version of Fast Fourier Transform is available. It's in

journal "Computers & Mathematics with Applications," vol. 26, no. 2,

pp. 61-65, 1993, with the title:

"Analysis of an Ada Based Version of Glassman's General N Point Fast

Fourier Transform"

The package is now available in the AdaNET repository, object #: 6728,

in collection: Transforms. If you're not an AdaNET user, contact Peggy

Lacey (la...@rbse.mountain.net).

_________________________________________________________________

7: Efficiency of Ada Constructs

7.1: How much extra overhead do generics have?

If you overgeneralize the generic, there will be more work to do for

the compiler. How do you know when you have overgeneralized? For

instance, passing arithmetic operations as parameters is a bad sign.

So are boolean or enumeration type generic formal parameters. If you

never override the defaults for a parameter, you probably

overengineered.

Code sharing (if implemented and requested) will cause an additional

overhead on some calls, which will be partially offset by improved

locality of reference. (Translation, code sharing may win most when

cache misses cost most.) If a generic unit is only used once in a

program, code sharing always loses.

R.R. Software chose code sharing as the implementation for generics

because 2 or more instantiations of Float_Io in a macro implementation

would have made a program too large to run in the amount of memory

available on the PC machines that existed in 1983 (usually a 128k or

256k machine).

Generics in Ada can also result in loss of information which could

have helped the optimizer. Since the compiler is not restricted by Ada

staticness rules within a single module, you can often avoid penalties

by declaring (or redeclaring) bounds so that they are local:

package Global is

subtype Global_Int is

Integer range X..Y;

...

end Global;

with Global;

package Local is

subtype Global_Int is

Global.Global_Int;

package Some_Instance is

new Foo (Global_Int);

...

end Local;

Ada rules say that having the subtype redeclared locally does not

affect staticness, but on a few occasions optimizers have been caught

doing a much better job. Since optimizers are constantly changing,

they may have been caught just at the wrong time.

_________________________________________________________________

8: Advanced Programming Techniques with Ada

8.1: Does Ada have automatic constructors and destructors?

(Tucker Taft replies)

At least in Ada 9X, functions with controlling results are inherited

(even if overriding is required), allowing their use with dynamic

binding and class-wide types. In most other OOPs, constructors can

only be called if you know at compile time the "tag" (or equivalent)

of the result you want. In Ada 9X, you can use the tag determined by

the context to control dispatching to a function with a controlling

result. For example:

type Set is abstract tagged private;

function Empty return Set is abstract;

function Unit_Set(Element : Element_Type) return Set is abstract;

procedure Remove(S : in out Set; Element : out Element_Type) is abstract;

function Union(Left, Right : Set) return Set is abstract;

...

procedure Convert(Source : Set'Class; Target : out Set'Class) is

-- class-wide "convert" routine, can convert one representation

-- of a set into another, so long as both set types are

-- derived from "Set," either directly or indirectly.

-- Algorithm: Initialize Target to the empty set, and then

-- copy all elements from Source set to Target set.

Copy_Of_Source : Set'Class := Source;

Element : Element_Type;

begin

Target := Empty; -- Dispatching for Empty determined by Target'Tag.

while Copy_Of_Source /= Empty loop

-- Dispatching for Empty based on Copy_Of_Source'Tag

Remove_Element(Copy_Of_Source, Element);

Target := Union(Target, Unit_Set(Element));

-- Dispatching for Unit_Set based on Target'Tag

end loop;

end Convert;

The functions Unit_Set and Empty are essentially "constructors" and

hence must be overridden in every extension of the abstract type Set.

However, these operations can still be called with a class-wide

expected type, and the controlling tag for the function calls will be

determined at run-time by the context, analogous to the kind of

(compile-time) overload resolution that uses context to disambiguate

enumeration literals and aggregates.

8.2: How can I redefine assignment operations?

See "Tips and Tidbits #1: User Defined Assignment" by Brad Balfour

(where is this located?)

8.3: Should I stick to a one package, one type approach while writing Ada

software?

(Robb Nebbe responds)

Offhand I can think of a couple of advantages arising from Ada's

separation of the concepts of type and module.

Separation of visibility and inheritance allows a programmer to

isolate a derived type from the implementation details of its parent.

To put it another way information hiding becomes a design decision

instead of a decision that the programming language has already made

for you.

Another advantage that came "for free" is the distinction between

subtyping and implementation inheritance. Since modules and types are

independent concepts the interaction of the facilities for information

hiding already present in Ada83 with inheritance provide an elegant

solution to separating subtyping from implementation inheritance. (In

my opinion more elegant than providing multiple forms of inheritance

or two distinct language constructs.)

Reply all

Reply to author

Forward

0 new messages

Search

Clear search

Close search

Google apps

Main menu