Consequences of Ada Design Goals
January 4, 2024
by Randy Brukardt
We often are asked why Ada does not have some gadget or other,
and the usual reason is that it doesn't meet the design goals. I wrote the following article
after one such conversation with the intent of explaining of how the design goals have been
applied. I was hoping to make something like this part of the
ARG design goals document,
but it got pushback about not being "neutral" enough and the thread ran out of steam. I
rediscovered it last night in my archives of unfinished projects, and since it is relevant to
another comp.lang.ada discussion today, I'm posting it here as personal opionion.
Often, people propose features to make Ada code easier to write. However,
"ease-of-writing" (often mischaracterized as "ease-of-use") has never been an
Ada Design Goal (see the
ARG design goals document).
Readability is a design goal, as is simplifying maintenance, always keeping in mind
that programmers are human (and thus are limited in how much information they can
process and remember, unlike a computer).
Meeting these goals often does enhance ease of writing. For instance, the Ada
features of user-defined literals, target name symbols, and user-defined
indexing all were added to enhance readability by avoiding duplicative text that
provides little information. All of these features also make Ada code easier to
write by reducing the amount of text that needs to be written, but that was not
the reason that they were added to Ada.
On the other hand, Ada does not have the wide variety of operators found in
some other popular languages. This is intentional: humans are bad at remembering
what little-used constructs do; maintenance and readability are enhanced by
avoiding them. Consider that when a human reading code encounters an unfamiliar
operator, they have to go look up what it does. Needing to do so breaks their
train of thought and thus slows down their understanding what the code actually does.
Case-in point: the original Ada 2012 proposal was for an "implies" Boolean
operator to use in preconditions and other assertions. But the ARG quickly found
out that there were many people (including ARG members) who did not intuitively
know exactly what function the implies operator implemented. Such an operator
would impede understanding of code using it for those people. It was suggested
that an if expression would have much the same function (along with others, as it
is more general), and every Ada programmer would understand what
if x then y else z does. So the if expression
was adopted for Ada 2012.
Moreover, to simplify human understanding, the same model was used for case
expressions, declare expressions, and quantified expressions. We could have used
dedicated syntax and semantics for these constructs, but rather opted for syntax
and semantics that are similar to the statements that every Ada programmer
already understands.
Similarly, Ada has resisted adding features that would terminate the current
iteration of a loop without exiting the loop (the "continue" statement), or
to do something after the last iteration of a loop (again, only if the loop is
not exited explicitly). Both of these come up occasionally in programming, but
were judged not be to common enough to justify a special feature. (Note that
both can easily be written using a goto, the first by jumping to the
end loop and the second by jumping past the "normal termination" code
for an exit.) A rarely used feature could stop a reader cold, requiring them to
look up what the feature does before continuing reading. And the goto alternative
is neither a lot longer nor harder to understand.
On the other hand, the target name symbol (@) was added to Ada after much
discussion, as when used carefully, it can greatly enhance the readability and
correctness of Ada code. Consider a line from my web log file analyzer:
Quarter_Item_Data (Data.By_Month (Year, Quarter_Num (Month)))
(Quarter_Item (Month)).Hit_Count :=
Quarter_Item_Data (Data.By_Month (Year, Quarter_Num (Month)))
(Quarter_Item (Month)).Hit_Count + Amount;
This is adding Amount to the Hit_Count component, but you have to careful
read both sides of the assignment statement to know that for sure. Contrast the
above to the following:
Quarter_Item_Data (Data.By_Month (Year, Quarter_Num (Month)))
(Quarter_Item (Month)).Hit_Count := @ + Amount;
Here it is immediately obvious what the operation is. Moreover, if the target
symbol is regularly used, then it is obvious that the following is doing something
different as opposed to being a mistake:
Quarter_Item_Data (Data.By_Month (Year, Quarter_Num (Month)))
(Quarter_Item (Month)).Hit_Count :=
Quarter_Item_Data (Data.By_Month (Year, Quarter_Num (Month)))
(Quarter_Item (Month)).View_Count + Amount;
Using the target name symbol also prevents errors by avoiding writing the
right-hand side subtly different by mistake.
Examples like these serve to show that while ease-of-writing (often
mischaracterized as "ease-of-use") is not a goal of Ada, many proposed features
do in fact make Ada easier to write. The ARG is willing to add new features to
Ada when they meet the design goals of Ada. But features that are judged to
not enhance readability tend to remain omitted from Ada.
The ARG has remained committed to the original Ada design goals as they
have made Ada what it is: a language that is usable, understandable, and
maintainable. Too much deviation from those goals risk losing what makes Ada
what it is.
|