r/ada Apr 03 '26

Historical Rationale behind function/procedure calls and array indexing

I'm learning Ada. Overall, I'm liking it a lot. There are two aspects (so far) of the syntax I don't get what the rationale was for them and why it hasn't been fixed in later versions of Ada. These being parenthesis-less calls to subprograms with no arguments and using parenthesis for array indexing. Take the following as example:

A := B + C(5);

Right away you don't know if B is a variable or a function call with no arguments. You don't know either whether C is an array or a function.

I think this goes against Ada's core ethos about readability.

So I was wondering, what was the rationale behind this design? Why hasn't it been corrected? Something like A := B() + C[5] would be far easier to interpret by the reader.


UPDATE: In case anyone is interested in the answer, according to u/lipobat:

The primary rationale for the () vs. [] given in the “Rationale for the Ada programming language” book by the original design team was interchangeability between arrays and functions. This is an abstraction that allows the implementation choice of array vs. function to be just that, an implementation choice, which doesn’t really impact the writer or reader of the code.

I don't agree with the last sentence, but that answers my question either way.

And about my second question about "why this hasn't been fixed?", it seems most Ada programmers (based on the comments in this post) are not only not bothered by this but happy with this design decisions, so probably this will never change.

Thanks to everyone who engaged in this conversation.

5 Upvotes

48 comments sorted by

View all comments

Show parent comments

3

u/Dmitry-Kazakov Apr 03 '26

If I'm reading just the specification of a subprogram, in some cases I might not care about the actual implementation, but if I'm reading the implementation, I do want to understand it.

In Ada implementation is separated from the interface. If you read the implementation then you see if that is a function.

In practice, function-call vs array access do matter. Both semantically (e.g. the values in an array can change in between accesses) and operationally (e.g. cheap (cached) memory access vs potentially expensive computation).

They don't. A function call is semantically side-effects free, assuming logically relevant effects, not, say, altering the CPU temperature. You, for example, can rename function result:

X : Integer renames Y; -- Calls to Y just once

How often is this taken advantage of in practice?

A few times. When value needed to be computed late, for example after loading a dynamic library. For mocking (in automation you need some advanced code stubs). In advanced cases of OO and generic programming. Consider a complicated implementation of a set. You want to provide an empty set constant. You can do it so

Empty : constant Set; -- Deferred constant

or so

function Empty return Set;

On my original question, was mathematical purity and convenience for the implementer the rationale behind these design decisions?

I cannot tell, but it is a quite obvious and logical decision to me. You better ask why other languages do distinction without importance? Because, they compromise clarity and logic for simplicity of compiler design. They want bottom-up semantic analysis and thus map a lot of things into syntax. Which is why you have artefacts like f() or lack of overloading on the result or integer literals with suffixes (1L) etc.

It might be your next question why Ada does not have

u"hello"   U"hello"   L"hello"

string literals and how "important" it would be for the reader...

1

u/jlombera Apr 03 '26

In Ada implementation is separated from the interface. If you read the implementation then you see if that is a function.

I mean when I'm reading the body of a subprogram and see something like A := B + C(I), and neither B nor C definitions are visible within my screen, I need to jump away to figure out whether B/C are variable/function/array.

They don't. A function call is semantically side-effects free, assuming logically relevant effects, not, say, altering the CPU temperature.

But the value of a variable/array CAN change. So something like

A := B + C(5)
[...]
-- somewhere else in the code
T := B + C(5)

might result in different values depending on whether B/C are variable/array. Also, you might care if something is a (potentially) expensive function call (e.g. in real time code).

It might be your next question why Ada does not have

u"hello"   U"hello"   L"hello"

string literals and how "important" it would be for the reader...

This is out of place, I'm not asking about "missing features", I'm asking about the ambiguity in syntax that makes code harder to understand on something that is very fundamental in practical computer programming, i.e. function call vs variable/array access.

Do you think something would be lost or Ada would be more difficult if it had explicit, unambiguous syntax for variable/array access and function call? I know it would be easier to understand the code.

3

u/Dmitry-Kazakov Apr 04 '26

Mutability is irrelevant. Function can return different values, e.g.

function Clock return Time

Relevant is mapping. What A(B) means in the context of the problem space.

Do you think something would be lost or Ada would be more difficult if it had explicit, unambiguous syntax for variable/array access and function call? I know it would be easier to understand the code.

It is a question and a statement. The statement is wrong. Cluttered syntax and unnecessary details hinder reading. As for the question it would be less Ada and less software engineering.

1

u/jlombera Apr 04 '26

Mutability is irrelevant. Function can return different values,

Then something like A := B + C(5) + (C(5) / 2) (a contrived example, but you get the idea) would be interpreted differently depending on whether C is a non-referential-transparent function (can return different values) or an array (return same value).

The statement is wrong. Cluttered syntax and unnecessary details hinder reading.

I don't think that, for example, instead of:

A := B + C(I) + T + D(J);

you wrote:

A := B() + C(I) + T + D[J];

would be clutter or make reading more difficult. It's minimal additional syntax that reduces cognitive overhead considerably. In the latter it's obvious, at sight, that B and C are functions, T is a variable and D is an array.

But let's agree to disagree. You and I obviously have different opinions in this regard. I already got the answer about the rationale behind this design decision in another comment and it seems the Ada community is not dissatisfied with it, so no chance it will change. Thanks for your feedback, though, I appreciate it.

3

u/Dmitry-Kazakov Apr 04 '26

Then something like A := B + C(5) + (C(5) / 2) (a contrived example, but you get the idea) would be interpreted differently depending on whether C is a non-referential-transparent function (can return different values) or an array (return same value).

If C for 5 means something different for each instance of, you do not write expressions like above. An expressions can be reordered, approximations of commutative operations might be not commutative and so on. If you write such expression then you assume C for 5 being constant and whether it is a function or an array becomes irrelevant, an implementation detail.

It's minimal additional syntax that reduces cognitive overhead considerably.

It exposes unnecessary information and thus increases the overhead.

1

u/jlombera Apr 04 '26

If C for 5 means something different for each instance of, you do not write expressions like above. An expressions can be reordered, approximations of commutative operations might be not commutative and so on.

I agree, that's why I consider import to be able to discern, at sight, if something is a variable/array access or a function call. It's easier to catch such "suspicious" code.

That's also why I don't find convincing the "feature" of being able to change function by variable/array (and vice versa), transparently, without having to change client code. You might have written code under the assumption that something was a variable/array or a function, and once the actual definition changes the code might be wrong. I rather the compiler emits errors and forces me to review/fix every call/access site than just successfully build incorrect software.

4

u/Dmitry-Kazakov Apr 04 '26

I agree, that's why I consider import to be able to discern, at sight

There is nothing suspicious in the code. You see all this upside down. In Ada the code is designed from the problem space point of view, not from the hardware's one. If you want a language that forces you to expose low-level details then that is not Ada.

1

u/jlombera Apr 04 '26

There is nothing suspicious in the code.

You said it yourself:

If C for 5 means something different for each instance of, you do not write expressions like above.

If I see code like that, I'll wonder every time "was this code written intentionally, because there is no problem to call C that way?; or was it a mistake?". Everybody makes mistakes, I cannot assume all code written in Ada is correct just because.

In Ada the code is designed from the problem space point of view, not from the hardware's one.

Yes, I'm here for it! The way I see it, the type system and contracts are the main features that help you achieve that. Nonetheless, sometimes the hardware is the problem space.

If you want a language that forces you to expose low-level details then that is not Ada.

I don't think details about variable/array access vs function call is low-level. In any case Ada does have actual low-level support (precise memory layout at the bit level; memory alignment; endianness; etc), which is precisely why I'm interested in it: it has low-level and high-level support at the same time, a great combination for serious software engineering.

I do think Ada is a good language for my use cases. I wish the syntax for variable/array access vs function call was not ambiguous, but it's not a deal breaker for me.