You are on page 1of 23

COMPUTING SCIENCE 370 Programming Languages

Ada

Modularity and Abstraction Problem: The software crisis.


y

complex systems

large programs

exponential costs

Solution: Break large programs into small independent modules.


y

linear costs

Question: How is independent modularization achieved? Answer: Information hiding (Parnas)


y y

one module for each design decision a change of decision involves only one module

Data Abstraction y y

Users of an abstract data type are provided with procedures and functions to operate on the data type. The implementation is hidden and can be changed without the user's knowledge. E.g., String

Functional Abstraction y

Users of an algorithm are provided with a procedure, without implementation details. E.g., heapsort

Characteristics of Fourth-Generation Programming Languages y y y

modularity functional and data abstraction support for concurrent programming

Syntactic Structure

Ada's syntax, like that of most fourth-generation programming languages, is: 1. in the Algol/Pascal tradition E.g.,
2. 3. 4. 5. 6. procedure <name> (<formal parameters>) is <local declarations> begin <statements> end <name>;

but ';' is a statement terminator (as in C), not a separator (as in Pascal) identifiers have arbitrary length case is not significant (but the convention is to use lower case for keywords and mixed upper and lower case for everything else) 7. fully bracketed E.g.,
o o o 8. 9. 10. 11. 12. loop . . . end loop if . . . end if case . . . end case record . . . end record package <name> is . . . end <name> o elsif introduced to avoid problems of fully-bracketed, nested if statements (making if more like cond). o if <condition 1> then o <statement 1> o elsif <condition 2> then o <statement 2> o elsif <condition 3> then o <statement 3> o else o <statement 4> o end if;

Note that only one 'end if' is needed. Structural Organization


y

Imperative -- two kinds: 1. computational 2. control flow I/O is implemented using two suggested standard packages.

Declarations -- five kinds: 1. Objects: variables and constants 2. Types: enumerations and types constructed from predefined types 3. Subprograms: like Algol/Pascal, but allows use of built-in operators as names of subprograms on user-defined types (operator overloading)

4. Packages: modules used for data abstraction  contain types and subprograms  defined by two separate modules a. specification (public) -- declarations b. definition or body (private) -- implementation 5. Tasks: modules used for concurrency Separate compilation -- Ada supports true separate compilation. compilation unit A compilable block of code. independent compilation Program units may be compiled independently and then integrated using a linker to form a complete program. E.g., Fortran, C Compiler maintains a list of undeclared subroutines and variables and assumes that they are external references. o All external references are resolved by the linker at link time.
o

separate compilation Program units are compiled separately, but not independently, in that external references are checked and resolved during compilation. E.g., Ada, Modula-2
o

Incorporates strong type checking.

Data Structures
y y y y y

Four predefined types


Integer Boolean Character Float o Float corresponds to the machine's usual precision, but programmers are

encouraged to use constraints instead for more machine independence. range constraint a set of permissible values e.g., type Coordinate is range -100 .. 100; floating-point constraint specified precision e.g., type Coefficient is digits 10 range -1.0e10 .. 1.0e10; fixed-point constraint specified absolute error bound e.g., type Dollars is delta 0.01 range 0.00 .. 10_000_000.00;

Preservation of Information Principle -- The user should be able to specify things at an abstract level for the compiler to implement at the low level. Five constructors o enumeration o array o record o pointer (called an access) o subtype Name equivalence o Why name equivalence? 1. If the programmer restates a type definition with a different name, it is probably because the types are logically different. 2. The alternative -- structural equivalence -- is not well defined and is difficult to implement.
o

(See also the previous discussion of this topic and 4.3 Type Equivalence from Rationale for the Design of the Ada Programming Language.)
o o o o o o o o

A problem with name equivalence:


type Index is range 1 .. 100; AnInteger : Integer; AnIndex : Index; AnInteger := AnIndex; AnIndex := AnIndex + 1; -- illegal with name equivalence -- illegal assuming 1 is an Integer

Solution: A constraint is defined as producing a subtype. subtype A type which inherits all operations from its supertype, and is compatible with the supertype and all subtypes of that supertype. Declaration of a subtype may be explicit. E.g.,
subtype Index is Integer range 1 .. 100;

derived type A type which inherits all operations from its supertype, but is not compatible with the supertype. Declaration of a derived type must be explicit. E.g.,
type Percent is new Integer range 0 .. 100;

Explicit coercion between a derived type and its subtype is allowed. E.g.,

A_Percent := Percent( AnInteger ); y y y

Ada allows an identifier to belong to multiple enumerated types. E.g.,


type Primary is ( Red, Blue, Green ); type StopLight is ( Red, Amber, Green );

Overloaded identifiers are disambiguated by context.


y y y y y y y y

Index constraints allow general-purpose array procedures. E.g.,


type Vector is array ( Integer range < > ) of Book; Collection : Vector ( 1 .. 10000 ); Fiction : Vector ( 1 .. 20000 ); procedure Sort ( aVector : Vector ) is . . . o The compiler must pass the actual bounds as hidden parameters to the procedure. o Bounds are accessed as <name>'First and <name>'Last.

E.g.,
o o o o o o o o o o o o o o o o o subtype Index is Integer range aVector'First .. aVector'Last; Lower : Index; Upper : Index; Temp : Book; begin for Upper in A_Vector'Last .. A_Vector'First + 1 loop for Lower in A_Vector'First .. Upper - 1 loop if A_Vector( Lower ) > A_Vector( Lower + 1 ) then Temp := A_Vector( Lower ); A_Vector( Lower ) := A_Vector( Lower + 1 ); A_Vector( Lower + 1 ) := Temp; end if; end loop; end loop; end Sort;

Name Structures
Bindings

A name can be bound to:


y y y y

constant variable type procedure

y y y

function task package

Declarations y

y y y y

In general, a declaration can: o allocate memory o bind a name to a location o initialize memory In Ada, variable and constant declarations can do all three. E.g.,
Tax_Rate : Real := 1.0; PI : constant := 3.141592653589; Deduct_Rate : constant Real := Tax_Rate * 2.0; o Omitting the type in the declaration of PI makes it a "universal real" (max.

precision). o Initializations are performed during environment activation. o Constants cannot be changed after activation. Declarations can be broken into two parts: o Specification:
o o o Max_Size : constant Integer;

Definition:
Max_Size : constant Integer := 256;

This allows a constant to be provided without defining its value as part of the interface.
Problems with name structures in block-structured languages

(Wulf and Shaw, 1973) Side effects Changes in the non-local environment resulting from hidden access to (a) variable(s). E.g.,
function max ( x, y : integer ); begin count := count + 1; { side effect } if x > y then max := x else max := y end; y y count is declared in an outer block, and is being used to count the number of

times the function is called. If there is a bug involving count, it will be difficult to identify that it is modified in this function, especially if it is in a large program or if max is a predefined function in a library. E.g.

length := max( needed, requested );

This line modifies count without mentioning it. (See the Manifest Interface Principle.) Indiscriminate access The inability to block access to variables which should be private. E.g.,

y y y y

aStack must be declared in the same block as the procedures which use it (Push and Pop). To be visible to users, Push and Pop must be declared in a block containing all

uses of them. UserProc has indiscriminate access to aStack -- it may alter the stack without using Push or Pop. Implementation details are also visible, and use of the stack (other than by Push or Pop) may depend on those details, creating a maintenance problem.

Vulnerability The inability of a program segment to preserve access to a variable. E.g.,

y y

The nested block, illustrated by the code X := X + 1, has no access to the outer X. Vulnerability may accidentally occur in maintaining a large program by introducing a new variable.

No overlapping definitions There is no control over shared access to variables. E.g., given three modules P1, P2, and P3 such that
y y

P1 and P2 share DA P2 and P3 share DB

the language should provide a mechanism like

but in a block-structured language, access must be like

Attributes of an alternative language which does not possess these four problems: 1. No implicit inheritance of access to variables from enclosing blocks.  Prevents side effects, indiscriminate access, and vulnerability. 2. Access rights by mutual consent.  Prevents indiscriminate access and vulnerability, and supports overlapping definitions. 3. Decoupling of access rights to a structure and its parts. E.g., allow access to a stack through push and pop operations, but provide no access to 'top'.  Prevents indiscriminate access. 4. Different types of access modes. E.g. read-only vs. read/write.  Addresses side effects and vulnerability. 5. Decoupling of these orthogonal functions:  definition of a name  name access  allocation E.g., in Algol,
  

a name is defined by its appearance in a declaration; name access is defined as the current block and all inner blocks; allocation and deallocation are restricted to block entry and exit.

Algol decouples name access and definition from allocation with own variables.

In Pascal, decoupling is provided by use of pointers and dynamically allocated memory (new and dispose).

PARNAS'S INFORMATION HIDING PRINCIPLES The language should permit modules to be designed such that 1. the user has all the information needed to use the module correctly, and nothing more; 2. the implementor has all the information needed to implement the module correctly, and nothing more.

Packages

Ada uses packages to implement information hiding.


y y y y y y y y y y y y y y y y y y y

A package consists of two parts: a specification part and a definition part. E.g., a specification:
package Complex_Pack is type Complex is private; I : constant Complex;

-- deferred constant

function "+" ( z1, z2 : Complex ) return Complex; function Real_Part ( z : Complex ) return Complex; function "+" ( x1 : Real; z2 : Complex ) return Complex; . . . private type Complex is record Re, Im : Real := 0.0; end record; I : constant Complex := ( 0.0, 1.0 ); end Complex_Pack;

Features of the specification part: o Name access is by mutual consent -- everything declared in the public part of this package can be used by any other program which imports this package. o Programs and packages which import this package can be compiled even if the implementation part of this package doesn't exist yet. o The private part indicates to the compiler how much space to allocate for variables of the new data type in any routine which uses that type. E.g.,
o o o declare use Complex_Pack; z : Complex;

o o o

z1 : Complex := 1.5 + 2.5 * I; begin . . .

Compare this approach with that of Modula-2: opaque types occupy one word.
y y y y y y y y y y y y

The definition part of a package is separately compiled. E.g.,


package body Complex_Pack is function "+" ( z1, z2 : Complex ) return Complex is rp : constant Real := z1.re * z2.re - z1.im * z2.im; ip : constant Real := z1.re * z2.im + z1.im * z2.re; begin return ( rp, ip ); end; . . .

There is notation for importing only part of a package. Packages are used for three purposes: 1. Data abstraction -- data and associated procedures. 2. Procedure libraries -- no data structures. E.g., a set of procedures and functions for statistical analysis (mean, standard deviation, plot, etc.). 3. Shared data -- no procedures. E.g., a package to solve the overlapping definition problem by providing limited access to multiple shared data areas:
4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. package Communication is InPtr, OutPtr : Integer range 0..99 := 0; Buffer : array ( 0 .. 99 ) of Character := ( 0..99 =>'' ); end Communication; procedure Put is use Communication; begin . . . Buffer( InPtr ) := Next; InPtr := ( InPtr + 1 ) mod 100; . . . end Put; procedure Take is use Communication; begin . . . C := Buffer( OutPtr ); . . . end Take;

Internal vs. external representation o Complex_Pack is an example of an external representation of data abstraction: 1. A type is exported. 2. The user is responsible for declaring variables of the exported type.

An internal representation is an alternate, more powerful method for implementing ADT's. 1. Storage for the ADT is associated with the package instead of being allocated by the user. E.g.,
2. 3. 4. 5. 6. 7. 8. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 25. 26. 27. 28. 29. 30. 31. package Stack is procedure Push ( Item : in Integer ); procedure Pop ( Item : out Integer ); function Empty return Boolean; function Full return Boolean; Stack_Error : exception; end Stack; declare use Stack; First_Item : Integer; Second_Item : Integer; begin . . . Push( First_Item ); Pop( Second_Item ); . . . if Empty() then Push( Second_Item ); end if; . . . end; package body Stack is Stack : array ( 1..100 ) of Integer; Top : Integer range 0..100 := 0;

9. This package would be used by code of the form:

24. In an array implementation, the package body would be:

procedure Push ( Item : in Integer ) is begin . . . 32. However, this creates one instance of Stack, not a class of Stacks.

Generic Packages

To have several instances of the same ADT, we use a generic package. E.g.,
generic package Stack is . . . end Stack; y y y y

The body is the same as the previous example. Instances of a Stack are created by:
package A_Stack is new Stack; package B_Stack is new Stack;

Qualification is necessary to use Stack instances, because Ada cannot differentiate between A_Stack's operations and B_Stack's operations by context. E.g.,
A_Stack.Push( Item ); B_Stack.Pop( Item );

y y y y

Generic package instantiation is static (i.e., occurs at compile time), not dynamic like new(p) in Pascal or new Stack() in Java. Generic packages may be parameterized. E.g., to instantiate one stack with a maximum of 100 integers and another of up to 200 characters:
generic Length : Natural := 100; -- default value type Item is private; package Stack is . . . procedure Push ( An_Item : in Item ) is . . . o Item acts like a private type -- only assignment and equality comparison are

y y y y y y y

available (since they don't depend on size or structure). o Instantiation now uses the parameters Length and Item:
o o y y y y y y y y y package A_Char_Stack is new Stack( 200, Character ); package An_Int_Stack is new Stack( 100, Integer );

Implementation of a parameterized generic stack:


package body Stack is Stack : array ( 1..Length ) of Item; Top : Integer range 0..Length := 0; . . . end Stack;

These stacks can be accessed by qualification, e.g.,


An_Int_Stack.Push( An_Item ); A_Char_Stack.Pop( An_Item );

or by using Ada's context mechanism. e.g.,


declare use A_Char_Stack; use An_Int_Stack; An_Int_Item : Integer; A_Char_Item : Character; begin Push( An_Int_Item ); -- overloaded Push( A_Char_Item ); if A_Char_Stack.Full() then -- qualified . . . end;

o o

Push is overloaded, but Ada can tell from the context (by looking at the type of the parameter) which Push procedure is intended. Since there is no way to tell the difference between Full in the package A_Char_Stack and Full in An_Int_Stack, the use of this procedure must be

qualified. Comparison of Package and Procedure Instantiation Package Instantiation separate data areas common code area static
y

Procedure Instantiation separate data areas common code area dynamic

Code sharing by generic packages can be complex: 1. No parameters

2. Same elements, different lengths (i.e., number of elements)

Shared code accesses a Length indicator stored in each instance to do range checking at run-time. 3. Different element types of the same size, different lengths  Since only equality comparison and assignment are used, only the size of an element matters in the code. 4. Elements of different sizes


Code which depends on the element size (e.g., find the position of an element in an array) accesses the size indicator stored by the compiler in each instance.

Control Structures

Ada has seven classes of control structures, most of them fully bracketed: 1. 2. 3. 4. 5. 6. 7. Conditionals Iterators Case Subprograms
goto

Exception handling Concurrency

Conditionals if <boolean expr> then <statements> end if; if <boolean expr> then <statements> else <statements> end if; if <boolean expr> then <statements> elsif <boolean expr> then <statements> . . . else <statements> end if; Iterators y y y y y y y y y y y y y y y

infinite
loop <statements> end loop;

mid-decision E.g., exit when KEY is found or when the search list is exhausted:
Index := List'First; loop if Index > List'Last then exit; end if; if List ( Index ) = Key then exit; end if; Index := Index + 1; end loop;

An alternative, abbreviated notation is available, which has the added benefit of making the exit point more clearly visible:
Index := List'First; loop exit when Index > List'Last; exit when List ( Index ) = Key; Index := Index + 1; end loop; y y y y y y y y y y y y

indefinite iterator
Index := List'First; while Index <= List'Last; loop exit when List ( Index ) = Key; Index := Index + 1; end loop;

definite iterator
for Index in List'First .. List'Last loop exit when List ( Index ) = Key; end loop;

but location of KEY is not known outside the loop, as I is local to the loop.
Case case Value is when Value1 | Value2 => <statements> when Value3 => <statements> when Value4 | Value5 | Value6 => null; when others <statements> end case; Subprograms

Ada provides explicit support for three parameter modes:


in

read-only access
out

write-only access
in out

read and write access


y

This decouples the mode of a parameter from its implementation: o The programmer specifies how it is to be used (logic).

The compiler decides how it is to be implemented (performance). Copying Reference In Out In out

Compiler decides

Programmer decides Ada also supports position-independent parameters with default values. E.g.,
procedure Draw_Axes ( X_Origin, Y_Origin : Coordinate := 0; X_Scale, Y_Scale : Real := 1.0; X_Spacing, Y_Spacing : Natural := 1; X_Label, Y_Label : Boolean := True; X_Log, Y_Log : Boolean := False; Full_Grid : Boolean := False ); y

The call to this procedure may specify parameters by label, omitting some:

Draw_Axes ( 500, 500, Y_Scale => 0.5, Y_Log => True, X_Spacing => 10, Y_Spacing => 10 );

Position-independent parameters are an example of the Labelling Principle. Default parameters are an example of the Localized Cost Principle, because they allow users to ignore options (parameters) which they do not require. However, costs are associated with these features, as they are with all features:
y y

complexity feature interaction E.g., operator overloading + default parameters:


procedure P ( X : Integer; B : Boolean := False ); procedure P ( X : Integer; Y : Integer := 0 );

What is the meaning of P(3)? It could refer to either procedure, as the second parameter of each has a default value. o SOLUTION: A set of subprogram declarations is illegal if it introduces the potential for ambiguous calls.
o

E.g., overloaded enumerations + overloaded procedures:


type Primary is ( Red, Blue, Yellow ); type Stoplight is ( Red, Amber, Green ); procedure Switch ( Color : Primary; X, Y : Float ); procedure Switch ( Light : Stoplight; Y, X : Float ); o o o o Goto Red and Switch are both overloaded.

There are no default values. These procedure declarations are illegal, because they allow the following ambiguous call:
Switch ( Red, X => 0.0, Y => 0.0 );

EXERCISE: Was goto necessary, given that exit is provided?


Exception Handling

Exception handler A block of code to be executed when an exception is raised (signalled). E.g.,
procedure Producer ( . . . ); use Stack; begin . . . Push ( Data ); -- If an exception is raised here -- (by attempting to push to a full stack), -- execution jumps to *** . . . exception when Stack_Error => declare Scratch : Integer; begin -- *** exception handler for I in 1 .. 3 loop if not Empty () then Pop ( Scratch ); end if; end loop; Push ( Data ); end; end Producer;

Exceptions propagate from callee to caller until an exeption handler is found:


y

If an exception is raised in procedure A, then look for the corresponding handler in the procedure that called A (say, B).

y y

If no handler for the raised exception is given in B, then look in the caller of B, etc. If the main program is reached in this search down the dynamic chain and no exception handler is found there, terminate the program.

This means that exception handlers use dynamic scoping. Why dynamic scoping? Different callers may want to take different actions. In implementation, an exception is like a dynamically scoped, non-local goto:
y y

Execution of the subprogram in which the exception occurred and of the body of the calling subprogram is aborted. If the dynamic chain must be scanned to find the appropriate handler, the activation record of any subprogram or block that doesn't define a handler is deleted.

Thus, exceptions violate both the Structure Principle and the Regularity Principle.
Concurrency

A tasking facility allows a program to do more than one thing at a time. E.g., a word processor that allows the user to edit one file while printing another:
procedure Word_Processor is task Edit; end Edit; task body Edit is begin -- edit some file end Edit; task Print; end Print; task body Print is begin -- print some file end Print; begin -- initiate tasks and wait for their completion end Word_Processor;

In general, there are three common means of synchronizing communication between concurrent processes:
y y

semaphores monitors

rendezvous

Ada uses rendezvous:


y y y y y y

Task entry declarations are like mailboxes or message ports. E.g.,


task Retrieve; entry Seek ( K : Key ); . . . end Retrieve;

One task sends a message to a mailbox in the other task, using syntax that looks like a procedure call with parameters. E.g.
task body Summary is . . . Seek ( ID ); . . . end Summary;

y y y y y y y y y y y y y y

The receiving task accepts the message from the named mailbox, and processes it. E.g.,
task body Retrieve is . . . accept Seek ( K : Key ) do Save_Key := K; end Seek; . . . end Retrieve;

If one of the tasks is ready and the other is not, then the ready task must wait to rendezvous with the other task. However, once the rendezvous has occurred (i.e., the callee has accepted the parameters), both tasks continue executing concurrently (unlike a procedure call,

E.g., a system which retrieves records to produce a summary, such that records may be retrieved concurrently with the production of the summary:

procedure DB_System is task Summary;

end Summary; task body Summary is begin . . . Seek ( ID ); . . . Fetch ( New_Record ); . . . end Summary; task Retrieve; entry Seek ( K : Key ); entry Fetch ( out P : Packet ); end Retrieve; task body Retrieve is begin loop accept Seek ( K : Key ) do Save_Key := K; end Seek; -- seek packet record and put it in New_Packet . . . accept Fetch ( out P : Packet ) do P := New_Packet; end Fetch; end loop; end Retrieve; begin -- initiate tasks and wait for their completion end DB_System;

Note that the Fetch message is sent from the task Summary to the task Retrieve, even though the data transfer of the record retrieved from the database is from Retrieve to Summary, because the mode of the Fetch message parameter is out -- when it receives a Fetch message, Retrieve copies the record it retrieved from the database into the out-mode parameter provided by Summary. Comparison of Subprograms and Tasks Subprogram Parameters transmitted from caller to callee. Caller is suspended and callee is activated. When callee is deactivated, caller is same Caller continues executing concurrently with callee. Caller may not terminate until all local tasks Task

reactivated.

finish.

Ada provides a special synchronization control structure to allow a task to wait for any of several rendezvous. E.g.,
select accept Send ( D : Document ) do -- print the document . . . end Send; or accept Terminate do exit end Terminate; end select;

A deadlock can occur if one task is waiting for a rendezvous which never takes place. One way to avoid deadlocks is to use guarded entries so that a message is accepted only if a condition is satisfied:
select when Avail < Size accept Send ( D : in Document ) do -- add document to buffer Avail := Avail + 1; end Send; or when Avail > 0 accept Receive ( D : out Document ) do -- return a document from the buffer Avail := Avail - 1; end Receive; or accept Terminate do exit end Terminate; end select;

Copyright 2000, 2001 Jonathan Mohr


http://www.augustana.ab.ca/~mohrj/courses/common/csc370/lecture_notes/ada.html 2 August 2011

You might also like