Rationale for Ada 2012

John Barnes
Contents   Index   References   Search   Previous   Next 

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 ::=
   function_specification is
As an example we can reconsider the type Point and the function Is_At_Origin thus
package P is 
   type Point is tagged
         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
   end F;
end P;
A function body cannot appear in a package specification. The only combinations are
function declaration Ffunction body F
in spec of Pin body of P
in body of Pin body of P
nonein 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;
   type Point is tagged
         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
   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 Fexpression function F
in spec of Pin spec or body of P
in body of Pin body of P
nonein 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
   K: constant := 5;    -- for example
   Pi: constant Long_Long_Float :=
         ((A(K) + B(K))**2 / (4.0*T(K));
   Put(Pi, Exp => 0);
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.

Contents   Index   References   Search   Previous   Next 
© 2011, 2012, 2013 John Barnes Informatics.
Sponsored in part by:
The Ada Resource Association:


and   Ada-Europe: