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
end
User-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.)