Rationale for Ada 2012
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
else
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
begin
Z := P; A := B
end
else
begin
Z := Q; A := C
end
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 ;
begin
Z := P; A := B
end;
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;
fi;
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);
else
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 = False | C1
= True
|
C2 = False | R = True | R
= False
|
C2 = True | R = True | R
= 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.
© 2011, 2012, 2013 John Barnes Informatics.
Sponsored in part by: