Features to note:
The high ratio of notation and type juggling to stuff actually happening is quite characteristic. It reflects two things:
The other extreme is Scheme, in which there are no declarations, no user-defined types (I think), and almost no compile time errors. It's faster to write, but run-time errors are much harder to catch and to deal with, for obvious reasons.
case N is when Val => Statement; when Val | Val | Val => Statement; when others => Statement end case;
type ItemCat is (Book, Article, MS); type BibRecord(Category: ItemCat) record Author, Title: string; case Category is when Book => year: Integer, publisher: String; when Article => year, vol, pStart, pEnd: Integer; journal: String; end case; end record;Can only accesss B.pStart if B.Category = Article.
As of Ada-95, it is possible to create pointers to a stack object, but the lifetime of the object must be greater than the lifetime of the pointer. Usually a static check; sometimes a dynamic check.
Lots of other pointer mechanisms and notations.
Garbage collection is optional -- up to implementation. User deallocation is supported.
procedure P is I : integer; procedure Q is I : integer; ... P.I ... end Q end P;
Package specifications can be top-level or imbedded in the declaration of another package or a subprogram.
Types can be:
Import package with "use packageName"
function ... begin body of function exception when ExceptionName => Action ExceptionName => Action ... others => Action endUser-defined exceptions are raised using "raise ExceptionName".
A standard library provides functionalities for passing more information from the raiser to the catcher.
-- Define type pRealFloat as a pointer to a function from Float to Float type pRealFun is access function(X: Float) return Float; function integrate(F: pRealFun; L,U: Float, N: Integer) return Float is begin ... Sum := Sum + F(X)*Delta; -- Call of function referenced by F; ... end; function square(X: Float) return Float is begin return X*X end; ... integrate(square'Access, -- creates pointer to function square 0.0, 10.0 100) ...
Generic type and function
generic type Item is private; type ItemArray is array(Integer range < >) of Item; with function f(X: Item) return Item; procedure ArrayApplier (A,B: in out ItemArray) is begin for I in A'Range loop B[I] := f(A[I]); end loop end function square(X: Float) return Float is begin return X*X end; type FArray is array(Integer range < >) of Float; function ApplySquare is new ArrayApplier(Float,FArray,square);Works essentially like a structured macro: with each instance of the generic that is created, the compiler essentially copies the source code and compiles it into a separate function. (Most generics work this way; the major exception is Java.)
Tasks can be individual or instances of a task type.
Individual task:
task Name; task body Name is begin ... endIndividual tasks begin when program execution begins.
Task types:
type task TaskName; -- Declaration task body Name is begin ... end -- Body type TArray is array(< >) of TaskName; type PTask is access TaskName; procedure foo is N1, N2 : TaskName; TA : TArray(1..10); PT : PTask; begin -- N1, N2, TA start running on entering begin. PT = new Ptask; -- PT starts running now ... end
From the point of view of A: When A reaches an accept entry point, it halts until some caller C calls that entry point. The in parameters are received; the body of the accept is executed; the out parameters are returned to C; and then A continues executing.
From the point of view of C: When C calls an entry point of task A, it posts the in parameters, and then goes to sleep. When A is ready to accept the call, the run-time system delivers the parameters, and A executes its body, and returns the out parameters. Then C wakes up and continues execution.
Calling syntax in C: A.E(actual parameters);
Accepting syntax in A:
Entry points are declared at the start of A.
accept E(formal parameters) do ... body of E ... end E.Note that C has to know about A but A does not have to know about C (like a procedure call.)