Rationale for Ada 2012
3.5 Expression functions
The final new form to be discussed is the expression
function. As outlined in the Introduction, an expression function provides
the effect of a small function without the formality of introducing a
body. It is important to appreciate that strictly speaking an expression
function is basically another form of function and not another form of
expression. But it is convenient to discuss expression functions in this
chapter because like conditional expressions and quantified expressions
they arose for use with aspect clauses such as pre- and postconditions.
The syntax is
expression_function_declaration ::=
[overriding_indicator]
function_specification is
(expression)
[aspect_specification];
As an example we can
reconsider the type Point and the function
Is_At_Origin thus
package P is
type Point is tagged
record
X, Y: Float := 0.0;
end record;
function Is_At_Origin(P: Point) return Boolean is
(P.X = 0.0 and P.Y = 0.0)
with Inline;
...
end P;
The expression function Is_At_Origin
is a primitive operation of Point just as
if it were a normal function with a body. If a type My_Point
is derived from Point then Is_At_Origin
would be inherited or could be overridden with a normal function or another
expression function. Thus an expression function can be prefixed by an
overriding indicator as indicated by the syntax.
Expression functions can have an aspect clause and
since by their very nature they will be short, this will frequently be
with Inline as in this example.
The result of an expression
function is given by an expression in parentheses. The parentheses are
included to immediately distinguish the structure from a normal body
which could start with an arbitrary local declaration. The expression
can be any expression having the required type. It could for example
be a quantified expression as in the following
function Is_Zero(A: Atype) return Boolean is
(for all J in A'Range => A(J) = 0);
This is another example of a situation where the
quantified expression does not need to be enclosed in its own parentheses
because the context supplied by the expression function provides parentheses.
Expression functions
can be completions as well as standing alone and this introduces a number
of possibilities. Remember that many declarations require completing.
For example an incomplete type such as
type Cell; -- an incomplete type
is typically completed
by a full type declaration later on
type Cell is
record ... end record; -- its completion
Completion also applies
to subprograms. Typically the declaration (that is the specification
plus semicolon) of a subprogram appears in a package specification thus
package P is
function F(X: T); -- declaration
...
end P;
and then the body of
F which completes it appears in the body of
P thus
package body P is
function F(X: T) is -- completion
begin
...
end F;
...
end P;
A function body cannot
appear in a package specification. The only combinations are
function declaration F | function
body F
|
---|
in spec of P | in
body of P
|
in body of P | in
body of P
|
none | in body of P
|
Remember that mutual recursion may require that a
body be given later so it is possible for a distinct declaration of F
to appear in the body of P before the full
body of F. In addition to the above the function
body could be replaced by a stub and the proper body compiled separately
but that is another story.
The rules regarding
expression functions are rather different. An expression function can
be declared alone as in the example of Is_At_Origin
above; or it can be a completion of a function declaration and that completion
can be in either the package specification or body. A frequently useful
combination occurs with a private type where we need to make a function
visible so that it can be used in a precondition and the expression function
then occurs in the private part as a completion thus
package P is
type Point is tagged private;
function Is_At_Origin(P: Point) return Boolean
with Inline;
procedure Do_It(P: in Point; ... )
with Pre => not Is_At_Origin;
private
type Point is tagged
record
X, Y: Float := 0.0;
end record;
function Is_At_Origin(P: Point) return Boolean is
(P.X = 0.0 and P.Y = 0.0);
...
end P;
Note that we cannot give an aspect specification
on an expression function used as a completion so it is given on the
function specification; this makes it visible to the user. (This rule
applies to all completions such as subprogram bodies and is not special
to expression functions.)
An expression function
can also be used in a package body as an abbreviation for
function Is_At_Origin(P: Point) return Boolean is
begin
return P.X = 0.0 and P.Y = 0.0;
end Is_At_Origin;
The possible combinations
regarding a function in a package are
function declaration F | expression
function F
|
---|
in spec of P | in
spec or body of P
|
in body of P | in
body of P
|
none | in spec or body of P
|
We perhaps naturally
think of an expression function used as a completion to be in the private
part of a package. But we could declare a function in the visible part
of a package and then an expression function to complete it in the visible
part as well. This is illustrated by the following interesting example
of two mutually recursive functions.
package Hof is
function M(K: Natural) return Natural;
function F(K: Natural) return Natural;
function M(K: Natural) return Natural is
(if K = 0 then 0 else K – F(M(K–1)));
function F(K: Natural) return Natural is
(if K =0 then 1 else K – M(F(K–1)));
end Hof;
These are the Male and Female functions described
by Hofstadter
[14]. They are inextricably
intertwined and both are given with completions for symmetry.
Almost inevitably, at least one of the expression
functions in a mutually recursive pair will include an if expression
(or else else) otherwise the recursion will not stop.
Expression functions can also be declared in subprograms
and blocks (they are basic declarative items). Moreover, an expression
function that completes a function can also be declared in the subprogram
or block.
This is illustrated
by the following Gauss-Legendre algorithm which computes π to an
amazing accuracy determined by the value of the constant K.
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Long_Long_Float_Text_IO;
use Ada.Long_Long_Float_Text_IO;
with Ada.Numerics.Long_Long_Elementary_Functions;
use Ada.Numerics.Long_Long_Elementary_Functions;
procedure Compute_Pi is
function B(N: Natural) return Long_Long_Float;
function A(N: Natural) return Long_Long_Float is
(if N = 0 then 1.0 else (A(N–1)+B(N–1))/2.0);
function B(N: Natural) return Long_Long_Float is
(if N = 0 then Sqrt(0.5) else Sqrt(A(N–1)*B(N–1)));
function T(N: Natural) return Long_Long_Float is
(if N = 0 then 0.25 else
T(N–1)–2.0**(N–1)*(A(N–1)–A(N))**2);
K: constant := 5; -- for example
Pi: constant Long_Long_Float :=
((A(K) + B(K))**2 / (4.0*T(K));
begin
Put(Pi, Exp => 0);
New_Line;
end Compute_Pi;
With luck this will output 3.14159265358979324 (depending
on the accuracy of Long_Long_Float).
The functions A and B
give successive arithmetic and geometric means. They call each other
and so B is given as a function specification
which is then completed by the expression function.
I am grateful to Brad Moore and to Ed Schonberg for
these instructive examples.
The rules regarding
null procedures (introduced in Ada 2005 primarily for use with interfaces)
are modified in Ada 2012 to be uniform with those for expression functions
regarding completion. Thus
procedure Nothing(X: in T) is null;
can be used alone as
a declaration of a null operation for a type or as a shorthand for a
traditional null procedure thus possibly completing the declaration
procedure Nothing(X: in T);
Expression functions and null procedures do not count
as subprogram declarations and so cannot be declared at library level.
Nor can they be used as proper bodies to complete stubs. Library subprograms
are mainly intended for use as main subprograms and to use an expression
function in that way would be somewhat undignified!
Thus if we wanted to
declare a useful function to compute sin 2x from time to time,
we cannot write
with Ada.Numerics.Elementary_Functions;
use Ada.Numerics.Elementary_Functions;
function Sin2(X: Float) is
-- illegal
(2.0 * Sin(X) * Cos(X));
but either have to write it out the long way or wrap
the expression function in a package.
© 2011, 2012, 2013 John Barnes Informatics.
Sponsored in part by: