In this chapter we present a very brief overview of some of the goals, concepts and features of Ada. 2.1 Key Goals Ada is a large language since it addresses many important issues relevant to the programming of practical systems in the real world. It is, for instance, much larger than Pascal which, unless extended in some way, is really only suitable for training purposes (for which it was designed) and for small personal programs. Some of the key issues in Ada are . Readability - it is recognized that professional programs are read much more often than they are written. It is important therefore to avoid an over terse notation such as in APL which although allowing a program to be written down quickly, makes it almost impossible to be read except perhaps by the original author soon after it was written. . Strong typing - this ensures that each object has a clearly defined set of values and prevents confusion between logically distinct concepts. As a consequence many errors are detected by the compiler which in other languages would have led to an executable but incorrect program. . Programming in the large - mechanisms for encapsulation, separate compilation and library management are necessary for the writing of portable and maintainable programs of any size. . Exception handling - it is a fact of life that programs of consequence are rarely correct. It is necessary to provide a means whereby a program can be constructed in a layered and partitioned way so that the consequences of errors in one part can be contained. . Data abstraction - as mentioned earlier, extra portability and maintainability can be obtained if the details of the representation of data can be kept separate from the specifications of the logical operations on the data. . Tasking - for many applications it is important that the program be conceived as a series of parallel activities rather than just a single sequence of actions. Building appropriate facilties into a language rather than adding them via calls to an operating system gives better portability and reliability. . Generic units - in many cases the logic of part of a program is independent of the types of the values being manipulated. A mechanism is therefore necessary for the creation of related pieces of program from a single template. This is particularly useful for the creation of libraries. 2.2 Overview A complete Ada program is written as a series of units. The outermost layer of units will include a main program and possible additional library units which can be thought of as providing services to the main program. The main program takes the form of a procedure of an appropriate name. The service library units can be other subprograms (procedures or functions) but they are more likely to be packages. A package is a group of related items which may be other entities as well as subprograms. Suppose we wish to write a program to print out the square root of some number such as 2.5. We can expect various library units to be available to provide us with a means of computing square roots and producing output. Our job is merely to write a main program to use these services as we wish. For the sake of argument we will suppose that the square root can be obtained by calling a function in our library whose name is SQRT. In addition we will suppose that our library includes a package called SIMPLE_IO containing various simple input-output facilities. These facilities might include procedures for reading numbers, printing numbers, printing strings of characters and so on. Our program might look like with SQRT, SIMPLE_IO; procedure PRINT_ROOT is use SIMPLE_IO; begin PUT(SQRT(2.5)); end PRINT_ROOT; The program is written as a procedure called PRINT_ROOT preceded by a with clause giving the names of the library units which it wishes to use. The body of the procedure contains the single statement PUT(SQRT(2.5)); which calls the procedure PUT in the package SIMPLE_IO with a parameter which in turn is the result of calling the function SQRT with the parameter 2.5. Writing use SIMPLE_IO; gives us immediate access to the facilities in the package SIMPLE_IO. If we had omitted this use clause we would have had to write SIMPLE_IO.PUT(SQRT(2.5)); in order to indicate where PUT was to be found. We can make our program more useful by making it read in the number whose square root we require. It might then become with SQRT, SIMPLE_IO; procedure PRINT_ROOT is use SIMPLE_IO; X: FLOAT; begin GET(X); PUT(SQRT(X)); end PRINT_ROOT; The overall structure of the procedure is now clearer. Between is and begin we can write declarations and between begin and end we write statements. Broadly speaking declarations introduce the entities we wish to manipulate and statements indicate the sequential actions to be performed. We have now introduced a variable X of type FLOAT which is a predefined language type. Values of this type are a set of certain floating point numbers and the declaration of X indicates that X can have values only from this set. In our example a value is assigned to X by calling the procedure GET which is also in our package SIMPLE_IO. Some small scale details should be noted. The various statements and declarations all terminate with a semicolon; this is unlike some other languages such as Algol and Pascal where semicolons are separators rather than terminators. The program contains various identifiers such as procedure, PUT and X. These fall into two categories. A few (62 in fact) such as procedure and is are used to indicate the structure of the program; they are reserved and can be used for no other purpose. All others, such as PUT and X, can be used for whatever purpose we desire. Some of these, notably FLOAT in our example, have a predefined meaning but we can nevertheless reuse them if we so wish although it might be confusing to do so. For clarity in this book we use lower case bold letters for the reserved identifiers and upper case letters for the others. This is purely a notational convenience; the language rules do not distinguish the two cases except when we consider the manipulation of characters themselves. Note also how the underscore character is used to break up long identifiers into meaningful parts. Finally observe that the name of the procedure, PRINT_ROOT, is repeated between the final end and the terminating semicolon. This is optional but is recommended in order to clarify the overall structure although is obvious in a small example such as this. Our program is still very simple; it might be more useful to enable it to cater for a whole series of numbers and print out each answer on a separate line. We could stop the program somewhat arbitrarily by giving it a value of zero. with SQRT, SIMPLE_IO; procedure PRINT_ROOT is use SIMPLE_IO; X: FLOAT; begin PUT("Roots of various numbers"); NEW_LINE(2); loop GET(X); exit when X = 0.0; PUT("Root of"); PUT(X); PUT("is); if X < 0.0 then PUT("not calculable"); else if; NEW_LINE; end loop; NEW_LINE; PUT("Program finished"); NEW_LINE; end PRINT_ROOTS; The output has been enhanced by the calls of further procedures NEW_LINE and PUT in the package SIMPLE_IO. A call of NEW_LINE will output the number of new lines specified by the parameter; the procedure NEW_LINE has been written in such a way that if no paramter is supplied then a default value of one is assumed. There are also calls of PUT with a string as argument. This is in fact a different procedure from the one which prints the number X. The compiler knows which is which because of the different types of parameters. Having more than one procedure with the same name is known as overloading. Note also the form of the string; this is a situation where the case of the letters does matter. Various new control structures are also introduced. The statements between loop and end loop are repeated until the condition X = 0.0 in the exit statement is found to be true; when this is so the loop is finished and we immediately carry on after end loop. We also check that X is not negative; if it is we output the message 'not calculable' rather than attempting to call SQRT. This is done by the if statement; if the condition between if and then is true, then the statements between then and else are executed, otherwise those between else and end if are executed. The general bracketing structure should be observed; loop is matched by end loop and if by end if. All the control structures of Ada have this closed form rather than the open form of Pascal which can lead to poorly structured and incorrect programs. We might well ask what would have happened if we had not tested for a negative value of X and consequently called SQRT with a negative argument. Assuming that SQRT has itself been written in an appropriate manner then it clearly cannot deliver a value to be used as the parameter of PUT. Instead an exception will be raised. The raising of an exception indicates that something unusual has happened and the normal sequence of execution is broken. In our case the exception might be NUMERIC_ERROR. If we did nothing to cope with this possibility then our program would be terminated and no doubt the Ada Programming Support Environment (APSE) will give us a rude message saying that our program has failed and why. We can, however, look out for an exception and take remedial action if it occurs. In fact we could replace our conditional statement by begin PUT(SQRT(X)); exception when NUMERIC_ERROR => PUT("not calculable"); end; We will now consider in outline the possible general form of the function SQRT and the package SIMPLE_IO that we have been using. The function SQRT will have a structure similar to that of our main program; the major difference will be the existence of parameters function SQRT(F:FLOAT) return FLOAT is R: FLOAT; begin --compute value of SQRT(F) in R return R; end SQRT; We see here the description of the formal parameters (in this case only one) and the type of the result. The details of the calculation are represented by the comment which starts with a double hyphen. The return statement is the means by which the result of the function is indicated. Note the distinction between a function which returns a result and is called as part of an expression and a procedure which does not have a result and is called as a single statement. The package SIMPLE_IO will be in two parts, the specification which describes its interface to the outside world and the body which contains the details of how it is implemented. If it just contained the procedures that we have used, its specification might be package SIMPLE_IO is procedure GET(F:out FLOAT); procedure PUT(F:in FLOAT); procedure PUT(S:in STRING); procedure NEW_LINE(N:in INTEGER:=1); end SIMPLE_IO; The parameter of GET is an out parameter because the effect of calling GET as in GET(X); is to transmit a value out from the procedure to the actual parameter X. The other parameters are all in parameters because the value goes in to the procedures. Only part of the procedures occurs in the package specification; this part is known as the procedure specificatin and just gives enough information to enable the procedures to be called. We see also the two overloaded specifications of PUT, one with a parameter of type FLOAT and the other with a parameter of type STRING. Finally, note how the default value of 1 for the parameter of NEW_LINE is indicated. The package body for SIMPLE_IO will contain the full procedure bodies plus any other supporting material needed for their implementation and which is naturally hidden from the outside user. In vague outline it might look like with INPUT_OUTPUT; package body SIMPLE_IO is ... procedure GET(F: out FLOAT) is ... begin ... end GET; -- other procedures similarly end SIMPLE_IO; The with clause shows that the implementation of the procedures in SIMPLE_IO uses the more general package INPUT_OUTPUT. It should also be noticed how the full body of GET repeats the procedure specification which was given in the corresponding package specification. (The procedure specification is the bit up to but not including is.) The example in this section has briefly revealed some of the overall structure and control statements of Ada. Details of data types will be discussed in due course. One purpose of this section has been to stress that the idea of packages is one of the most important concepts in Ada. A program should be conceived as a number of components which provide services to and receive services from each other. In the next few chapters we will of necessity be dealing with the small scale features of Ada but in doing so we should not lose sight of the overall structure which we will return to in Chapter 8. Perhaps this is an appropriate point to mention the special package STANDARD. This is a package which exists in every implementation and contains the declarations of all the predefined identifiers such as FLOAT and NUMERIC_ ERROR. We can assume access to STANDARD automatically and do not have to give its name in a with clause. It is discussed in detail in Appendix 2. Exercise 2.2 1 In practice it is likely that the function SQRT will not be in the library on its own but in a package along with other mathematical functions. Suppose this package has the identifier SIMPLE_MATHS and other functions LOG, EXP, SIN and COS. By analogy with the specification of SIMPLE_IO, write the specification of such a package. How would our PRINT_ROOTS need to be changed? 2.3 ERRORS An Ada program may be incorrect for various reasons. Three categories are recognized according to how they are detected. Some errors wil be detected by the compiler - these will include simple punctuation mistakes such as leaving out a semicolon or attempting to violate the type rules such as mixing up colours and fish. In these cases the program will not be executed. Other errors are detected when the program is executed. An attempt to find the square root of a negative number or divide by zero are examples of such errors. In these cases an exception is raised as we saw in the last section and we have an opportunity to recover from the situation. Finally there are certain situations where the program breaks the language rules but there is no simple way in which this violation can be detected. For example a program should not depend on the order in which parameters of a procedure call are evaluated. If it does then the behaviour may depend upon the implementation. Such a program is said to be erroneous. Care must be taken to avoid writing erroneous programs; in practice if we avoid clever tricks then all will be well. 2.4 INPUT-OUTPUT The Ada language is defined in such a way that all input and output is performed in terms of other language features. There are no special intrinsic features just for input and output. In fact input-output is just a service required by a program and so is provided by one or more Ada packages. This approach runs the attendant risk that different implementations will provide different packages and program portability will be compromised. In order to avoid this, the Language Reference Manual describes certain standard packages that can be expected to be available. Other, more elaborate, packages may be appropriate to special circumstances and the language does not prevent this. Indeed very simple packages such as our purely illustrative SIMPLE_IO may also be appropriate. Further consideration of input and output is deferred until Chapter 15 when we discuss interfaces between our program and the outside world in general. 2.5 TERMINOLOGY Every subject has its own terminology or jargon and Ada is no exception. (Indeed in Ada an exception is a kind of error as we have seen!) A glossary of terms will be found in Appendix 3. Terminology will generally be introduced as required but before starting off with the detailed description of Ada it is convenient to mention a few concepts which will occur from time to time. The term static refers to things that can be determined at compilation whereas dynamic refers to things determined during execution. Thus a static expression is one whose value can be determined by the compiler such as 2 + 3 and a static array is one whose bounds are known at compilation time. Sometimes it is necessary to make a parenthetic remark to the compiler where the remark is not a part of the program as such but more a useful hint. This can be done by means of a construction known as a pragma. As an example we can indicate that only parts of our program are to be listed by writing pragma LIST(ON); and pragma LIST(OFF); at appropriate places. Generally a pragma can appear anywhere that a declaration or statement can appear, but sometimes there may be restrictions on the position of a particular pragma.