Rationale for Ada 2012

John Barnes
Contents   Index   References   Search   Previous   Next 

3.2 If expressions

It is perhaps very surprising that Ada does not have if expressions as well as if statements. In order to provide some background context we briefly look at two historic languages that are perhaps the main precursors to modern languages; these are Algol 60 [8] and CPL [9].
Algol 60 had conditional expressions of the form
Z := if X = Y then P else Q
which can be contrasted with the conditional statement
if X = Y then
   Z := P
   Z := Q
Conditional statements in Algol 60 allowed only a single statement in each branch, so if several were required then they had to be grouped into a compound statement thus
if X = Y then
      Z := P;  A := B
      Z := Q;  A := C
It may be recalled that statements were not terminated by semicolons in Algol 60 but separated by them. However, a null statement was simply nothing so the effect of adding an extra semicolon in some cases was harmless. However, accidentally writing
if X = Y then ;
      Z := P;  A := B
results in a disaster because the test then just covers a null statement and the assignments to Z and A always take place. The complexity of compound statements did not arise with conditional expressions.
The designers of Algol 68 [10] sensibly recognized the problem and introduced closing brackets thus
if X = Y then
   Z := P; A := B;
where fi matches the if. Conditional expressions in Algol 68 were similar
Z := if X = Y then P else Q fi;
An alternative shorthand notation was
Z := (X = Y | P | Q);
which was perhaps a bit too short.
The next major language in this series was Pascal [11]. The designers of Pascal rejected everything that had been learnt from Algol 68 and foolishly continued the Algol 60 style for compound statements and also dropped conditional expressions. Only with Modula did they realise the need for bracketing rather than compounding.
The other foundation language was CPL [9]. Conditional statements in CPL took the following form
if X = Y then do Z := P
if X = Y then do § Z := P; A := B §|
where compound statements were delimited by section symbols (note that the closing symbol has a vertical line through it).
From CPL came BCPL, B and C. Along the way, the expressive := for assignment got lost in favour of = which then meant that = had to be replaced by == for equality. And the section brackets became { and } so in C the above conditional statements become
if (X == Y) Z = P;
if (X == Y) {Z = P;  A = B;}
This suffers from the same stray semicolon problem mentioned above with reference to Algol 60.
Steelman [12] did not require Ada to have conditional expressions and since they were not required they were not provided (the requirements were treated with considerable reverence). A further influence might have been that the new language had to be based on one of Pascal, Algol 68 and PL/I and Ada is based on Pascal which did not have conditional expressions as mentioned above.
Moreover, the Ada designers felt that the Algol 68 style with reversed keywords such as fi (or worse esac) for conditional statements would not be acceptable to the USDoD or the public at large and so we have end if as the closing bracket thus
if X = Y then
   Z := P;
   A := B;
end if;
Remember that semicolons terminate statements in Ada and so those above are all required. Moreover, since null statements in Ada have to be given explicitly, placing a stray semicolon after then gives a compiler error.
The absence of conditional expressions is a pain. It leads to unnecessary duplication such as having to write
if X > 0 then
   P(A, B, D, E);
   P(A, C, D, E);
end if;
where all parameters but one are the same. This can even lead to disgusting coding using the fact that Boolean'Pos(True) is 1 whereas Boolean'Pos(False) is 0. Thus (assuming that B and C are of type Integer) the above could be written as a single procedure call thus
P(A, Boolean'Pos(X>0)*B+Boolean'Pos(X<=0)*C, D, E);
So it is a great relief in Ada 2012 to be able to write
P(A, (if X>0 then B else C), D, E);
A worse problem was when a static expression was required such as the initial value for a named number as in the following gruesome code
Febdays: constant := Boolean'Pos(Leap)*29 + Boolean'Pos(not Leap)*28;
which we can now thankfully write as
Febdays: constant := (if Leap then 29 else 28);
Note carefully that there is no end if. One reason is simply that it is logically unnecessary since there can be only a single expression after else and also end if would have been obtrusively heavy (compared say with fi of Algol 68). However, it was felt that some demarcation was required to aid clarity and so a conditional expression is always enclosed in parentheses. If the context already has parentheses then additional ones are not required. Thus in the case of a positional call with a single parameter we just write
P(if X > 0 then B else C);
but if we use named notation then extra parentheses are required
P(Para => (if X > 0 then B else C));
Note carefully that the term conditional expression in Ada 2012 embraces both if expressions and case expressions (which are discussed in the next section).
As expected, a series of tests can be done using elsif thus
P(if X > 0 then B elsif X < 0 then C else D);
and expressions can be nested
P(if X > 0 then (if Y > 0 then B else C) else D);
ithout the rule requiring enclosing parentheses this could be written as
P(if X > 0 then if Y > 0 then B else C else D);  -- illegal
which seems more than a little confusing.
There is a special rule if the type of the expression is Boolean (that is of the predefined type Boolean or derived from it). In that case a final else part can be omitted and is taken to be true by default. Thus the following are equivalent
P(if C1 then C2 else True);
P(if C1 then C2);
Such abbreviations appear frequently in preconditions as was illustrated in the Introduction where we had
Pre => (if P1 > 0 then P2 > 0);
which has the obvious meaning that the precondition requires that if P1 is positive then P2 must be positive as well but if P1 is not positive then all is well and we don't care about P2.
This abbreviated form has the same effect as an implies operation.
R := C1 implies C2;       -- not Ada!
with the following truth table
C1 = FalseC1 = True
C2 = FalseR = TrueR = False
C2 = TrueR = TrueR = True
Some consideration was given to including such an operation in Ada 2012 (it existed in Algol 60). However, this is exactly the same as
R := not C1 or C2;
and so somewhat unnecessary. Moreover, although implies might appeal to some programmers it could lead to maintenance problems since it might be considered incomprehensible by many others.
There are important rules regarding the types of the various dependent expressions in the branches of an if expression. Basically they have to all be of the same type or convertible to the same expected type. But there are some interesting situations.
If the conditional expression is the argument of a type conversion then effectively the conversion is considered pushed down to the dependent expressions. Thus
X := Float(if P then A else B);
is equivalent to
X := (if P then Float(A) else Float(B));
As a consequence we can write
X := Float(if P then 27 else 0.3);
and it doesn't matter that 27 and 0.3 are not of the same type.
If the expected type is class wide, perhaps giving the initial value for a class wide variable V, then the individual dependent expressions have that same expected class wide type but they need not all be of the same specific type within the class. Thus we might write
V: Object'Class := (if B then A_Circle else A_Triangle);
where A_Circle and A_Triangle are objects of specific types Circle and Triangle which are themselves descended from the type Object.
If the expected type is a specific tagged type then various situations can arise regarding the various branches which are similar to the rules for calling a subprogram with several controlling operands. Either they all have to be dynamically tagged (that is class wide) or all have to be statically tagged. They might all be tag indeterminate in which case the conditional expression as a whole is also tag indeterminate.
Some obscure curiosities arise. Remember that the controlling condition for an if statement can be any Boolean type. Consider
type My_Boolean is new Boolean;
My_Cond: My_Boolean := ... ;
if (if K > 10 then X = Y else My_Cond) then
   -- illegal
end if;
The problem here is that X = Y is of type Boolean but My_Cond is of type My_Boolean. Moreover, the expected type for the condition in the if statement is any Boolean type so it cannot make up its mind. We could overcome this foolishness by putting a type conversion around the if expression.
There are also rules regarding staticness. If all branches are static then a conditional expression as a whole is static as in the example of Febdays above. Thus the definition of a static expression has been extended to permit the inclusion of static conditional expressions.
The avid reader of the Reference Manual will find that the term statically unevaluated has been introduced. This relates to situations where expressions are not evaluated because a prior expression is static. Consider
X := (if B then P else Q);
If B, P and Q are all static then the conditional expression as a whole is static. If B is true then the answer is P and there is not any need to even look at Q. We say that Q is statically unevaluated and indeed it does not matter that if Q had been evaluated it would have raised an exception. Thus we might write
Average := (if Count = 0 then 0.0 else Total/Count);
and there is no risk of dividing by zero.
Similar rules regarding being statically unevaluated apply to short circuit conditions, case expressions, and membership tests.
As might be expected there are rules regarding access types and accessibility. The accessibility level of a conditional expression is simply that of the chosen dependent expression and thus (generally) determined dynamically.
Readers might feel that Ada has embarked on a slippery slope by introducing more flexibility thereby possibly damaging Ada's reputation for reliability. Certainly a number of additional rules have been required but from the users' point of view these are almost intuitive. It should be remembered that the difficulties in C stem from a combination of things
that assignment is permitted as an expression,
that integer values are used as Booleans,
that null statements are invisible. 
None of these applies to Ada so all is well.

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


and   Ada-Europe: