Version 1.1 of ai05s/ai05-0147-1.txt
!standard 4.4 (07) 09-03-13 AI05-0147-1/01
!standard 4.5.7 (0)
!standard 4.7(2)
!standard 4.7(3)
!standard 4.7(5)
!standard 4.9(12)
!standard 4.9(33)
!class amendment 09-03-13
!status work item 09-03-13
!status received 09-03-13
!priority Medium
!difficulty Medium
!subject Conditional expressions
!summary
Conditional expressions are added to Ada.
!problem
It is not unusual that an expression value needs to be calculated in a way
that avoids an exception or other boundary. For instance, consider
declaring a range based on a parameter:
procedure P (N : in Natural) is
subtype Calculation_Range is Natural range 0 ..
(10_000 / N) + 1;
begin
This obviously doesn't work if N is 0. One might want to use the maximum
value in this case, but such an expression is hard to write in Ada.
One common workaround for the lack of conditional expressions in Ada is
to multiply by Boolean'Pos:
procedure P (N : in Natural) is
subtype Calculation_Range is Natural range 0 ..
(Boolean'Pos(N=0) * 10_000 + Boolean'Pos(N/=0)*(10_000 / N)) + 1;
begin
But this doesn't work because the entire expression will be evaluated even when
N is 0, including the divide-by-zero.
Similar problems occur avoiding overflow in the expressions of the bounds of a
range.
A conditional expression would make writing this expression easy:
subtype Calculation_Range is Natural range 0 ..
(if N=0 then 10_000 else 10_000 / N)) + 1;
The pending addition of preconditions to Ada will greatly increase this
need. It is not at all unusual to have the value of one parameter
depend on another. It is possible to write a Boolean expression like
Precondition => (not Param_1 >= 0) or Param_2 /= ""
but this sacrifices a lot of readability, when what is actually meant is
Precondition => (if Param_1 >= 0 then Param_2 /= "" else True)
for both of these reasons, we are proposing to add conditional expressions
to Ada.
!proposal
(See wording.)
!wording
In 4.4(7), add:
primary ::=
numeric_literal | null | string_literal | aggregate
| name | allocator | (expression) | conditional_expression
Add a new clause:
4.5.7 Conditional expressions
Syntax
conditional_expression ::= (if condition then *dependent*_expression
{elsif condition then *dependent*_expression}
[else *dependent*_expression])
Name Resolution Rules
The expected type for a conditional_expression shall be a single
type. The expected type for each dependent_expression of a
conditional_expression shall be the type of the conditional_expression.
Redundant[The expected type for each condition is expected to be of any
boolean type (see 5.3).]
[Editor's Note: "condition" is defined in 5.3. Should it (both syntax and
semantics - 2 lines) be moved here to avoid the forward reference??]
Legality Rules
If there is no "else" dependent_expression, the type of conditional_expression
shall be of a boolean type.
Dynamic Semantics
For the execution of a conditional expression, the condition specified after
if, and any conditions specified after elsif, are evaluated in succession
(treating a final else as elsif True then), until one evaluates to True or
all conditions are evaluated and yield False. If a condition evaluates to
True, the associated dependent_expression is evaluated and its value
is the value of the expression. Otherwise, the value of the expression
is True.
[Editor's note: This is nearly a copy of 5.3(5). I left the clunkyness
intact. Note that the last otherwise can be true only for a boolean
conditional expression, as an "else" is required in all other cases.]
In 4.7(2), add conditional_expression:
qualified_expression ::=
subtype_mark'(expression) | subtype_mark'aggregate |
subtype_mark'conditional_expression
In 4.7(3), modify the start of the paragraph:
The operand (the expression{,}[ or] aggregate{, or conditional_expression}) shall...
In 4.7(5), modify the note:
When a given context does not uniquely identify an expected type, a
qualified_expression can be used to do so. In particular, if an overloaded name{,}[ or]
aggregate{, or conditional_expression} is passed to an overloaded subprogram, it might
be necessary to qualify the operand to resolve its type.
Add after 4.9(12):
* A conditional_expression all of whose conditions and dependent_expressions are
static expressions;
Split 4.9(33), replacing it by the following:
A static expression is evaluated at compile time except when:
* it is part of the right operand of a static short-circuit control form whose value
is determined by its left operand;
* it is the dependent_expression of some part of a conditional_expression,
and one or more conditions of preceeding parts of the conditional_expression
has the value True.
[Editor's note: This wording requires all conditions of a conditional_expression
to be evaluated at compile-time (including ones after the first true one); we could
try to include the order of evaluation but that would require a much messier definition,
because we would have to talk about the actual order of evaluation. Boolean expressions
are fairly unlikely to overflow anyway.]
The compile-time evaluation of a static expression is performed exactly, without
performing Overflow_Checks. For a static expression that is evaluated:
!discussion
The syntax of Ada requires that we require some sort of surrounding syntax to make
a conditional expression different than a if statement. Without that requirement,
we would have code like:
if if Func1 then Func2 then Proc1; end if;
Even if the compiler can figure it out (which is not clear to the author), it would be
dicey for a human, and small errors such as leaving out a semicolon could change the
meaning of a statement drastically.
The obvious choice is to surround a conditional expression with parenthesis. This is
already a common Ada design choice, and will be familar to Ada programmers. Other choices,
such as using square brackets (currently unused in Ada), or using syntax other than
"if", would be less familar and would cause programmers a confusion as to which syntax
to use in a given location.
Thus, we design conditional expressions work like and look like aggregates. This the
reason that they are defined to be a primary rather than some other sort of expression.
The fact that the parenthesis are required makes this preferable (it would be confusing
otherwise).
One could imagine rules allowing the parenthesis being omitted in contexts where they
are not needed, such as a pragma argument. However, this would simply add conceptual
overhead - it important to note that this was not done for aggregates except in the
case of qualified expressions. We follow the same strategy here.
Following the model of aggregates also simplifies the resolution of conditional
expressions. This avoids nasty cases of resolution, in which a compiler
might be able to figure out a unique meaning but a human could not. For instance,
given the declarations:
procedure Q (A, B : Integer);
procedure Q (A, B : float);
function F return Integer;
function F return Boolean;
function G (N : Natural) return Integer;
function G (N : Natural) return Float;
Q (if X > 3 then F else G(1)), (if X > 12 then G(2) else G(3)));
If we used full resolution, this would resolve to Integer because there is no F
for Float. With the unlimited number of terms available in a conditional expression,
one can construct ever more complex examples.
We allow the "else" branch to be omitted for boolean-valued conditional
expressions. This eases the use of conditional expressions in preconditions
and postconditions, as it provides a very readable form of the "implies"
relationship of Boolean algebra. That is,
A implies B
could be written as
(if A then B)
In this case, the "else" branch is more noise than information.
!examples
Another usage is fixing plurals in output. Much Ada code doesn't even try to
get this right because it requires duplicating the entire call.
For instance, we have to write:
if Num_Errors = 1 then
Put_Line ("Compilation of " & Source_Name & "completed with" &
Error_Count'Image(Num_Errors) & "error detected.");
else
Put_Line ("Compilation of " & Source_Name & "completed with" &
Error_Count'Image(Num_Errors) & "errors detected.");
end if;
The duplication of course brings the hazard of not making the same changes
to each message during maintenance.
Using a conditional expression, we can eliminate this duplication:
Put_Line ("Compilation of " & Source_Name & "completed with" &
Error_Count'Image(Num_Errors) &
(if Num_Errors = 1 then "error" else "errors")
& " detected.");
!ACATS test
ACATS B and C tests are needed.
!appendix
From: Tucker Taft
Sent: Sunday, February 22, 2009 4:51 AM
[Find the rest of this message in AI05-0145-1.]
Somewhat independent suggestion:
Add "X implies Y" as a new short-circuit operation meaning
"not X or else Y".
By making it a short-circuit operation, we avoid the
burden of worrying about user-defined "implies" operators
(which then might no longer mean "not X or Y"),
boolean array "implies" operators, etc., and the compiler
can trivially transform them to something it already
knows how to compile.
I suspect "implies" will be used almost exclusively in Assert
and pre/post conditions.
****************************************************************
From: Bob Duff
Sent: Tuesday, February 24, 2009 10:25 AM
It rubs me the wrong way to have short-circuit and non-short-circuit versions
of and[then] and or[else], but not for implies. How about "implies" and
"implies then"? Seems more uniform.
I don't see why anybody would "worry" about user-defined "implies", any more
than they would worry about user-defined "and".
"Implies" on arrays is not terribly useful, but the implementation is trivial,
so I'd go for uniformity.
Note that "<=" on Booleans means "implies" (not short circuit, of course).
> I suspect "implies" will be used almost exclusively in Assert
> and pre/post conditions.
...which makes any efficiency argument in favor of short-circuits not so important.
By the way, coding convention at AdaCore is to always use "and then" and "or else",
and never "and" or "or". (Well, at least for Booleans -- I suppose we're allowed to
"and" arrays.) I don't agree with this policy -- I think the short-circuit forms
should be used when the meaningfulness of the right-hand side depends on the value
of the left-hand side, and perhaps for efficiency in some cases.
If I were writing an assertion using "implies" (in my own code, not subject to AdaCore
rules!), I would normally want non-short-circuit, so that if the right-hand side is
buggy, it will get evaluated and cause an exception.
I'd reserve short-circuits for things like "Assert(X /= null implies then X.all > 0);".
Eiffel has "and then", "or else", and "implies", which are short-circuit (called
"semi-strict"). It also has "and" and "or", which _might_ be short-circuit, depending
on the whim of the compiler writer -- yuck.
****************************************************************
From: Robert Dewar
Sent: Thursday, February 26, 2009 3:28 AM
> It rubs me the wrong way to have short-circuit and non-short-circuit
> versions of and[then] and or[else], but not for implies. How about
> "implies" and "implies then"? Seems more uniform.
This is uniformity run amok. The non-short circuited versions of AND and OR
are dubious in any case, and "implies then" is plain horrible, if you must
have two different operators, use a horrible name for the non-sc form of
implies :-) In fact you already have the horrible name <=, those who insist
on the non-short-circuited form can use that name.
> I don't see why anybody would "worry" about user-defined "implies",
> any more than they would worry about user-defined "and".
> "Implies" on arrays is not terribly useful, but the implementation is
> trivial, so I'd go for uniformity.
Again, this is uniformity run amok to me, implies on arrays is like NOT on
non-binary modular types, an unhelpful result of orthogonality likely to
correspond to a coding error.
> By the way, coding convention at AdaCore is to always use "and then"
> and "or else", and never "and" or "or". (Well, at least for Booleans
> -- I suppose we're allowed to "and" arrays.) I don't agree with this
> policy -- I think the short-circuit forms should be used when the
> meaningfulness of the right-hand side depends on the value of the
> left-hand side, and perhaps for efficiency in some cases.
Actually the other case where AND/OR are allowed is for plain boolean
variables. where indeed the operations are more like boolean arithmetic.
The reason I think that Bob's preference is plain horrible is that it is
VERY rare but not impossible to have a situation where you rely on the
non-short-circuited semantics, and in such a case you use AND/OR relying on
this. If you use AND/OR routinely, then such rare cases are seriously buried.
In English AND/OR short circuit, if you hear an announcement at an airport
that says "All passengers who are reserved on China Airlines flight 127 and
who have not checked in at the checkin counter should now proceed directly
to the gate".
you stop listening at 127. You do not carefully evaluate the first part to
False, then the second part to true (because you have checked in for your
flight), and then calculate that (False AND True) Is False so you can ignore
the announcement after all.
> If I were writing an assertion using "implies" (in my own code, not
> subject to AdaCore rules!), I would normally want non-short-circuit,
> so that if the right-hand side is buggy, it will get evaluated and cause an
> exception. I'd reserve short-circuits for things like "Assert(X /= null
> implies then X.all > 0);".
I definitely disagree strongly with this, and I really can't believe anyone
would coutenance the syntax in this implies then" example, it's nothing like
english usage, whereas "AND THEN" and "OR ELSE" are reasonably in the domain
of normal english.
> Eiffel has "and then", "or else", and "implies", which are
> short-circuit (called "semi-strict"). It also has "and" and "or",
> which _might_ be short-circuit, depending on the whim of the compiler writer -- yuck.
Sounds the right choice to me, Eiffel has it right, and Ada has it wrong, and
Ada will be even wronger if it has "implies then". The nice thing about the
Eiffel choice is that it clearly indicates that AND/OR are to be used when
you don't care about short circuiting, but they don't imply the inefficiency
of forced evaluation, or worse still cover up a case where the full evaluation
is required for correctness.
In Eiffel, I would go for Bob's view of using AND/OR routinely and reserving
AND THEN/OR ELSE for the cases where left to right short circuiting is required
for correctness.
I did not know this feature in Eiffel, definitely the right choice I think.
****************************************************************
From: Tucker Taft
Sent: Thursday, February 26, 2009 9:13 AM
>> I suspect "implies" will be used almost exclusively in Assert
>> and pre/post conditions.
>
> ...which makes any efficiency argument in favor of short-circuits not
> so important.
I don't think it is an efficiency argument. To me, it seems quite clear
in "A implies B" that if A is False, then you have no reason to evaluate B,
and my brain would intuitively take advantage of that. E.g.
X /= null implies X.Kind = Abc
There seems no particular advantage to having a non-short-circuit version,
and if you need short-circuiting, as in the above case, you can't get it.
> By the way, coding convention at AdaCore is to always use "and then"
> and "or else", and never "and" or "or". (Well, at least for Booleans
> -- I suppose we're allowed to "and" arrays.) I don't agree with this
> policy -- I think the short-circuit forms should be used when the
> meaningfulness of the right-hand side depends on the value of the
> left-hand side, and perhaps for efficiency in some cases.
I do agree with this convention, but I realize you don't.
I think in part it depends on what is your first language.
You were a heavy Pascal user for a while, I believe, and in Pascal, all you
have are "and" and "or."
To me it is always safer, and frequently more efficient, to use "A and then B."
Also, in the case when the compiler could convert "A and B" into "A and then B"
for efficiency, it could go the other way as well. Hence if I always write
"A and then B," the compiler will use short-circuiting by default, but if it
is smart and B is sufficiently simple as to clearly have no side-effects, and
for some reason "A and B" is more efficient (e.g. it eliminates a branch),
then the compiler can choose it. On the other hand, if I use "A and B" always,
and "B" is at all expensive to compute, then the compiler has to be pretty highly
optimizing to determine that it can do the short-circuiting, in exactly the
cases where it is most important to do so.
> If I were writing an assertion using "implies" (in my own code, not
> subject to AdaCore rules!), I would normally want non-short-circuit,
> so that if the right-hand side is buggy, it will get evaluated and cause an
> exception. I'd reserve short-circuits for things like "Assert(X /= null implies
> then X.all > 0);".
I'm not convinced. To me "implies" suggests short circuiting quite strongly,
since if the antecedent is False, the whole thing is uninteresting. It is an infix
form for "if A then B" in my mind.
> Eiffel has "and then", "or else", and "implies", which are
> short-circuit (called "semi-strict"). It also has "and" and "or",
> which _might_ be short-circuit, depending on the whim of the compiler writer -- yuck.
I don't like the last part, but I agree with the first part.
I think Ada's rules are right, but I choose to think of them as the compiler
can short-circuit "and"/"or" only if the second operand has no side-effects,
which is the usual "as if" rules.
****************************************************************
From: Bob Duff
Sent: Thursday, February 26, 2009 7:27 AM
> > It rubs me the wrong way to have short-circuit and non-short-circuit
> > versions of and[then] and or[else], but not for implies. How about
> > "implies" and "implies then"? Seems more uniform.
>
> This is uniformity run amok.
Of course -- it's no surprise that someone who never uses "and" would see
no use in a non-short-circuit version of "implies".
>... The non-short circuited versions of AND and OR are dubious in any
>case, and "implies then" is plain horrible, if you must have two
>different operators, use a horrible name for the non-sc form of
>implies :-) In fact you already have the horrible name <=, those who
>insist on the non-short-circuited form can use that name.
I sometimes write:
pragma Assert (Is_Good (X) <= -- implies
Is_Gnarly (Y));
The comment is necessary. ;-)
I can live with that, but I'd prefer to say "implies" instead of
"<= -- implies".
> > I don't see why anybody would "worry" about user-defined "implies",
> > any more than they would worry about user-defined "and".
> > "Implies" on arrays is not terribly useful, but the implementation
> > is trivial, so I'd go for uniformity.
>
> Again, this is uniformity run amok to me, implies on arrays is like
> NOT on non-binary modular types, an unhelpful result of orthogonality
> likely to correspond to a coding error.
I doubt it would cause coding errors. I think "implies" on arrays, like "not"
on non-binary modular types [*] would just be a feature nobody would use.
[*] I don't think anybody should use non-binary modular types, with or
without "not". ;-)
> > By the way, coding convention at AdaCore is to always use "and then"
> > and "or else", and never "and" or "or". (Well, at least for
> > Booleans -- I suppose we're allowed to "and" arrays.) I don't agree
> > with this policy -- I think the short-circuit forms should be used
> > when the meaningfulness of the right-hand side depends on the value
> > of the left-hand side, and perhaps for efficiency in some cases.
>
> Actually the other case where AND/OR are allowed is for plain boolean
> variables. where indeed the operations are more like boolean arithmetic.
>
> The reason I think that Bob's preference is plain horrible is that it
> is VERY rare but not impossible to have a situation where you rely on
> the
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> non-short-circuited semantics, and in such a case you use AND/OR
> relying on this. If you use AND/OR routinely, then such rare cases are
> seriously buried.
Probably the root of our disagreement is that I disagree with your "not
impossible" above -- one should NEVER, EVER write code that relies on
non-short-circuited semantics. Therefore, I only want to distinguish two
cases: (1) I rely on short-circuit semantics ("and then"), (2) I do not
rely on it ("and").
> In English AND/OR short circuit, if you hear an announcement at an
> airport that says "All passengers who are reserved on China Airlines
> flight 127 and who have not checked in at the checkin counter should
> now proceed directly to the gate".
>
> you stop listening at 127.
You had better not -- what if the next word is "or". ;-)
Your analogy proves that you spend too much time traveling. ;-)
>... You do not carefully evaluate the first part to False, then the
>second part to true (because you have checked in for your flight), and
>then calculate that (False AND True) Is False so you can ignore the
>announcement after all.
Shrug. If you say "multiply the number of unicorns in the world by the
cost of feeding a unicorn", I can stop listening at "by".
It doesn't mean that 0 * F(X) should fail to evaluate F(X) in Ada.
...
> > Eiffel has "and then", "or else", and "implies", which are
> > short-circuit (called "semi-strict"). It also has "and" and "or",
> > which _might_ be short-circuit, depending on the whim of the compiler writer -- yuck.
>
> Sounds the right choice to me, Eiffel has it right, and Ada has it
> wrong, and Ada will be even wronger if it has "implies then". The nice
> thing about the Eiffel choice is that it clearly indicates that AND/OR
> are to be used when you don't care about short circuiting, but they
> don't imply the inefficiency of forced evaluation, or worse still
> cover up a case where the full evaluation is required for correctness.
The problem with the Eiffel rule is that if you _accidentally_ care, you've
got a latent bug that will rear its ugly head when you turn on the optimizer,
or switch compilers, or make a seemingly-unrelated code change. We both agree
that you _shouldn't_ care, but bugs do happen, and bugs discovered "later" are
very costly.
> In Eiffel, I would go for Bob's view of using AND/OR routinely and
> reserving AND THEN/OR ELSE for the cases where left to right short
> circuiting is required for correctness.
>
> I did not know this feature in Eiffel, definitely the right choice I
> think.
I guess you and I will never agree on this issue.
I can live with that. And I'll continue to put in the noise word "then" after
every "and" in AdaCore code. ;-)
****************************************************************
From: Tucker Taft
Sent: Thursday, February 26, 2009 4:51 AM
****************************************************************
From: Tucker Taft
Sent: Thursday, February 26, 2009 4:51 AM
****************************************************************
From: Tucker Taft
Sent: Thursday, February 26, 2009 4:51 AM
****************************************************************
From: Tucker Taft
Sent: Thursday, February 26, 2009 4:51 AM
****************************************************************
From: Tucker Taft
Sent: Thursday, February 26, 2009 4:51 AM
****************************************************************
From: Tucker Taft
Sent: Thursday, February 26, 2009 4:51 AM
****************************************************************
From: Tucker Taft
Sent: Thursday, February 26, 2009 4:51 AM
****************************************************************
From: Tucker Taft
Sent: Thursday, February 26, 2009 4:51 AM
****************************************************************
From: Tucker Taft
Sent: Thursday, February 26, 2009 4:51 AM
****************************************************************
From: Tucker Taft
Sent: Thursday, February 26, 2009 4:51 AM
****************************************************************
From: Tucker Taft
Sent: Thursday, February 26, 2009 4:51 AM
****************************************************************
From: Tucker Taft
Sent: Thursday, February 26, 2009 4:51 AM
****************************************************************
From: Tucker Taft
Sent: Thursday, February 26, 2009 4:51 AM
****************************************************************
Questions? Ask the ACAA Technical Agent