New aggregates with Ada 2022.

320 views
Skip to first unread message

Blady

unread,
Jun 19, 2022, 3:59:21 AM6/19/22
to
Hello,

Following the example section of RM Ada 2022 § 4.3.5 Container
Aggregate, I want to try map aggregates:

453. type Map_Type is private
454. with Aggregate => (Empty => Empty_Map,
455. Add_Named => Add_To_Map);
456.
457. procedure Add_To_Map (M : in out Map_Type; Key : in Integer;
Value : in String);
458.
459. Empty_Map : constant Map_Type;
... -- End of example code
482. private
...
488. type Map_Type is array (1..10) of String (1..10);
489.
490. procedure Add_To_Map (M : in out Map_Type; Key : in Integer;
Value : in String) is null;
491. Empty_Map : constant Map_Type := (1..10 => (1..10 => ' '));

GNAT 12 reports:

aarm_202x_ch04.adb:491:40: error: container aggregate must use [], not ()
aarm_202x_ch04.adb:491:42: error: choice must be static

The first error seems to say that even Map_Type full view is known in
the private part I can't use assignment with genuine array aggregates.
It is a legal feature or GNAT is puzzled?
Is there an other mean to assign array aggregates?

I can't really figure out the meaning of the second error.
Any idea?

Thanks Pascal.

Simon Wright

unread,
Jun 19, 2022, 10:15:51 AM6/19/22
to
Blady <p....@orange.fr> writes:

> aarm_202x_ch04.adb:491:40: error: container aggregate must use [], not ()

If you look at ARM202x 4.3.5, you'll see that *container* aggregates
must use []. I'm sure there wa a whole lot of argument about this in the
ARG!

Blady

unread,
Jun 20, 2022, 3:36:52 PM6/20/22
to
Yes I was aware of that but I wanted to give Empty_Map its real value of
the full type definition.

My understanding is:
you declare the aggregate aspect as:
type Map_Type is private
with Aggregate => (Empty => Empty_Map,
Add_Named => Add_To_Map);
thus:
MM : Map_Type;
...
MM := [] -- the compiler uses Empty_Map
MM := [1=>"toto", 4=>"titi"]; -- the compiler uses Add_To_Map

now if I declare:
Empty_Map : constant Map_Type := [];
then it could be an recursive infinite call, could be?

Note : in this latter case the compiler issues a ICE:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106031

Randy Brukardt

unread,
Jun 20, 2022, 5:47:37 PM6/20/22
to
"Blady" <p....@orange.fr> wrote in message
news:t8ml0l$1vo2$1...@gioia.aioe.org...
> Hello,
>
> Following the example section of RM Ada 2022 § 4.3.5 Container Aggregate,
> I want to try map aggregates:
>
> 453. type Map_Type is private
> 454. with Aggregate => (Empty => Empty_Map,
> 455. Add_Named => Add_To_Map);
> 456.
> 457. procedure Add_To_Map (M : in out Map_Type; Key : in Integer;
> Value : in String);
> 458.
> 459. Empty_Map : constant Map_Type;
> ... -- End of example code
> 482. private
> ...
> 488. type Map_Type is array (1..10) of String (1..10);

This type is illegal, by 4.3.5(10/5):

If the container type of an Aggregate aspect is a private type, the full
type of the container type shall not be an array type.

The reason for this is obvious in your question: it is ambiguous if an
aggregate is an array aggregate or a container aggregate wherever the full
type is visible, and that is not worth making work (any choice would be a
surprise in some contexts).

Apparently, GNAT failed to check for this error (probably because there
aren't ACATS tests yet for Ada 2022, so errors of omission are very hard to
find, not having a vetted set of tests).

Secondly, early post Ada 2022 AIs have removed the possibility of using a
constant for Empty_Map, since it does nto work with inheritance (and
container aggregates are supposed to work with inheritance). So while GNAT
may allow you to define Empty_Map this way now, it won't for very long. It
will need to be a function.

Randy.


Simon Wright

unread,
Jun 20, 2022, 6:01:47 PM6/20/22
to
Blady <p....@orange.fr> writes:

> Le 19/06/2022 à 16:15, Simon Wright a écrit :
>> Blady <p....@orange.fr> writes:
>>
>>> aarm_202x_ch04.adb:491:40: error: container aggregate must use [], not ()
>> If you look at ARM202x 4.3.5, you'll see that *container* aggregates
>> must use []. I'm sure there wa a whole lot of argument about this in the
>> ARG!
>
> Yes I was aware of that but I wanted to give Empty_Map its real value
> of the full type definition.
>
> My understanding is:
> you declare the aggregate aspect as:
> type Map_Type is private
> with Aggregate => (Empty => Empty_Map,
> Add_Named => Add_To_Map);
> thus:
> MM : Map_Type;
> ...
> MM := [] -- the compiler uses Empty_Map
> MM := [1=>"toto", 4=>"titi"]; -- the compiler uses Add_To_Map
>
> now if I declare:
> Empty_Map : constant Map_Type := [];
> then it could be an recursive infinite call, could be?

I think you'd need to supply the contents - Empty_Map here is presumably
the full declaration (it is in your PR). Fixing this, the compiler is
still confused (it thinks that 'others' in [others => [others => ' ']]
isn't static - also if replaced by '1 .. 10'!!!).

Jesper Quorning

unread,
Jun 20, 2022, 6:10:52 PM6/20/22
to
It look like the Aggregate aspect has some rough edges.
This code leads to internal compiler error with gnatmake 12.1.0:

package Container_Aggregates is
type Array_Type is
array (1 .. 10) of Integer
with Aggregate => (Empty => Empty_Array);

Empty_Array : constant Array_Type := [1..10 => 123];
end Container_Aggregates;

Dmitry A. Kazakov

unread,
Jun 20, 2022, 6:18:06 PM6/20/22
to
On 2022-06-20 23:47, Randy Brukardt wrote:

> The reason for this is obvious in your question: it is ambiguous if an
> aggregate is an array aggregate or a container aggregate wherever the full
> type is visible, and that is not worth making work (any choice would be a
> surprise in some contexts).

I don't agree that making the language regular does not worth work. The
choice is obviously inconsistent with handling both existing cases:

1. Built-in operations -> hiding:

type T is private;
function "+" (Left, Right : T) return T; -- Perfectly legal
private
type T is range 1..100;

2. Primitive operations -> overriding.

What's good for the goose is good for the gander? Nope. Here is a
totally new way of handling an operation!

--
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de

Jesper Quorning

unread,
Jun 20, 2022, 6:59:54 PM6/20/22
to
tirsdag den 21. juni 2022 kl. 00.10.52 UTC+2 skrev Jesper Quorning:
> This code leads to internal compiler error with gnatmake 12.1.0:

This is the bug report:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106037
The crash happens another place than the #106031 report by Blady.

I ran into a third ICE with erroneous memory access while playing around with an access type.


/Jesper

Randy Brukardt

unread,
Jun 21, 2022, 7:20:14 PM6/21/22
to
Thia ia also illegal: the aspect aggregate is defined "for a type other than
an array type", so the aspect should not even be recognized in this context.

Randy.

"Jesper Quorning" <jesper....@gmail.com> wrote in message
news:9e3aa56d-42b3-4552...@googlegroups.com...

Randy Brukardt

unread,
Jun 21, 2022, 7:28:21 PM6/21/22
to
"Dmitry A. Kazakov" <mai...@dmitry-kazakov.de> wrote in message
news:t8qrmo$p79$1...@gioia.aioe.org...
> On 2022-06-20 23:47, Randy Brukardt wrote:
>
>> The reason for this is obvious in your question: it is ambiguous if an
>> aggregate is an array aggregate or a container aggregate wherever the
>> full
>> type is visible, and that is not worth making work (any choice would be a
>> surprise in some contexts).
>
> I don't agree that making the language regular does not worth work.

There's always a way, but is it worth the implementation effort? And would
it be confusing to use?

> The choice is obviously inconsistent with handling both existing cases:
>
> 1. Built-in operations -> hiding:
>
> type T is private;
> function "+" (Left, Right : T) return T; -- Perfectly legal
> private
> type T is range 1..100;

This case is not worth the effort, IMHO. (Of course, it is in the language
now, so we're stuck with it.) If I was running the circus, private types
could only be completed with a record type. 99% of the time, that's what you
want. And the other 1% of the time, you will eventually end up changing it
into a record for one reason or the other. So why pollute the language rules
for something primarily useful in ACATS tests?

> 2. Primitive operations -> overriding.
>
> What's good for the goose is good for the gander? Nope. Here is a totally
> new way of handling an operation!

The problem here is illustrated by the OP, who seemed to expect to get the
container aggregate when the full view is visible. We looked at making the
container aggregate invisible and allowing the array aggregate in the full
view, but it would be something new (the contents of aggregates don't depend
on visibility in Ada 2012), and it seems useless (see my answer to [1]).

That is, the OPs construct is primarily useful in small examples; it's not a
real world thing you would want to do. (There ALWAYS is some other data that
you need to go along with an array: a length, a validity flag, etc.) So why
make implementers spend a lot of effort implementing it??

Randy.


Randy Brukardt

unread,
Jun 21, 2022, 7:39:47 PM6/21/22
to
"Dmitry A. Kazakov" <mai...@dmitry-kazakov.de> wrote in message
news:t8qrmo$p79$1...@gioia.aioe.org...
...
> 1. Built-in operations -> hiding:
>
> type T is private;
> function "+" (Left, Right : T) return T; -- Perfectly legal
> private
> type T is range 1..100;
>
> 2. Primitive operations -> overriding.

BTW, these two are really the same thing. In the first example, the "+" is
overriding the predefined operation.

But note an important difference here from the aggregate case: in no case is
an operation available for the private type that is *not* available for the
full view. Since the syntax and semantics of container aggregates and array
aggregate are subtly different (they are as close as we could make them, but
that is not that close), there definitely are things that would be only
possible when written for the private view. That would be new for Ada. So
while a definition could be made, it would be confusing in some cases. And
this case isn't useful enough to make that effort.

BTW2, there are many things in Ada that would be possible with effort but
are illegal for one reason or another. Part of the reason for doing that is
that it is always possible to allow such things in the future -- that would
be a compatible change. OTOH, defining them sloppily would leave us stuck
forever with an expensive and relatively useless feature. For instance, the
rather strict limitations on the use and contents of a declare expression
fall into that category. Allowing more would be possible, but it would have
far-reaching effects on compilers and on the language definition. Not worth
the cost at this point, perhaps that will change.

Randy.
.


Dmitry A. Kazakov

unread,
Jun 22, 2022, 4:26:40 AM6/22/22
to
On 2022-06-22 01:39, Randy Brukardt wrote:
> "Dmitry A. Kazakov" <mai...@dmitry-kazakov.de> wrote in message
> news:t8qrmo$p79$1...@gioia.aioe.org...
> ...
>> 1. Built-in operations -> hiding:
>>
>> type T is private;
>> function "+" (Left, Right : T) return T; -- Perfectly legal
>> private
>> type T is range 1..100;
>>
>> 2. Primitive operations -> overriding.
>
> BTW, these two are really the same thing. In the first example, the "+" is
> overriding the predefined operation.

Yes, they should be in a better world. A primitive operation is always
reachable. The case above works differently from:

type T is private;
overriding
function "+" (Left, Right : T) return T is abstract;

But this is a deeper problem of having such operations primitive.

> But note an important difference here from the aggregate case: in no case is
> an operation available for the private type that is *not* available for the
> full view. Since the syntax and semantics of container aggregates and array
> aggregate are subtly different (they are as close as we could make them, but
> that is not that close), there definitely are things that would be only
> possible when written for the private view. That would be new for Ada. So
> while a definition could be made, it would be confusing in some cases. And
> this case isn't useful enough to make that effort.

Much could be resolved by attributing proper types to all assumed or
real intermediate steps:

type T is private;
function "+" (Left, Right : T) return T;
private
type T_Parent is range 1..100;
type T is new T_Parent;

Dmitry A. Kazakov

unread,
Jun 22, 2022, 5:04:14 AM6/22/22
to
On 2022-06-22 01:28, Randy Brukardt wrote:

> This case is not worth the effort, IMHO. (Of course, it is in the language
> now, so we're stuck with it.) If I was running the circus, private types
> could only be completed with a record type.

Just like aggregates now, record types *must* have interface. Your
"circus" will inevitable face this same problem again - how to define
record members of a private type implemented by a built-in record type
in the full view? The language must universally handle all sorts of
interfaces.

> The problem here is illustrated by the OP, who seemed to expect to get the
> container aggregate when the full view is visible. We looked at making the
> container aggregate invisible and allowing the array aggregate in the full
> view, but it would be something new (the contents of aggregates don't depend
> on visibility in Ada 2012), and it seems useless (see my answer to [1]).

I would first answer basic questions, which interfaces have array vs
user-defined aggregate/index. How are they related to each other etc.

> That is, the OPs construct is primarily useful in small examples; it's not a
> real world thing you would want to do. (There ALWAYS is some other data that
> you need to go along with an array: a length, a validity flag, etc.) So why
> make implementers spend a lot of effort implementing it??

Well, I can give a useful example straight away. A string has two array
interfaces, the encoding and the character view. The former must be a
built-in array.

Randy Brukardt

unread,
Jun 22, 2022, 9:06:58 PM6/22/22
to
"Dmitry A. Kazakov" <mai...@dmitry-kazakov.de> wrote in message
news:t8ulu9$jca$1...@gioia.aioe.org...
> On 2022-06-22 01:28, Randy Brukardt wrote:
...
> Well, I can give a useful example straight away. A string has two array
> interfaces, the encoding and the character view. The former must be a
> built-in array.

The interface for a string should be quite different from that for an array,
they're not related at all. One might implement a string with a record type
that has an array component, but never as a bare array. (That leads to
nonsense, which Ada sadly has proven clearly.

Indeed, if you consider everything to have an interface, arrays themselves
cease to be a fundemental part of a language design and just become another
interface. (How something gets implemented should not be part of a language
design, so long as the design does not prevent an efficient implementation.)
I certainly would not treat them as special in any way, just a series of
function calls. (Possibly records could be treated that way as well,
although it is less clear that an efficient implementation is possible for
them.)

Randy.


Randy.


Randy Brukardt

unread,
Jun 22, 2022, 9:10:10 PM6/22/22
to
"Dmitry A. Kazakov" <mai...@dmitry-kazakov.de> wrote in message
news:t8ujnr$1hvf$1...@gioia.aioe.org...
Don't think this changes anything (at least not in Ada as it stands), since
that is essentially the meaning of "range 1 .. 100":

type Some_Int is range 1 .. 100;

means

type Some_Int is new <Some_Int_Chosen_by_the_Impl> range 1 .. 100;

Randy.


Dmitry A. Kazakov

unread,
Jun 23, 2022, 5:32:13 AM6/23/22
to
On 2022-06-23 03:06, Randy Brukardt wrote:
> "Dmitry A. Kazakov" <mai...@dmitry-kazakov.de> wrote in message
> news:t8ulu9$jca$1...@gioia.aioe.org...
>> On 2022-06-22 01:28, Randy Brukardt wrote:
> ...
>> Well, I can give a useful example straight away. A string has two array
>> interfaces, the encoding and the character view. The former must be a
>> built-in array.
>
> The interface for a string should be quite different from that for an array,
> they're not related at all. One might implement a string with a record type
> that has an array component, but never as a bare array. (That leads to
> nonsense, which Ada sadly has proven clearly.

String is an array of encoding units, historically and practically. Even
at the machine level string instructions deal with arrays.

A language unable to support such implementation does not look usable to me.

> Indeed, if you consider everything to have an interface, arrays themselves
> cease to be a fundemental part of a language design and just become another
> interface.

Everything has interface just because it is the way human brain works.

The language design question is what interfaces deserve to be formalized
and exposed in the language as entities the programmer can reference,
check against etc.

In Ada array interface is exposed in generics as a formal array, but not
in the type system proper.

In any case having an explicit array interface does not preclude
built-in arrays.

> (How something gets implemented should not be part of a language
> design, so long as the design does not prevent an efficient implementation.)
> I certainly would not treat them as special in any way, just a series of
> function calls. (Possibly records could be treated that way as well,
> although it is less clear that an efficient implementation is possible for
> them.)

Syntax sugar for subprogram calls is not enough because it does not
allow generic programming. One should be able to write a program that
deals with any instance of the interface. Like a generic body working
with any actual array or a class-wide body which unfortunately is
impossible to have for arrays presently.

Dmitry A. Kazakov

unread,
Jun 23, 2022, 5:32:46 AM6/23/22
to
On 2022-06-23 03:10, Randy Brukardt wrote:

> Don't think this changes anything (at least not in Ada as it stands), since
> that is essentially the meaning of "range 1 .. 100":
>
> type Some_Int is range 1 .. 100;
>
> means
>
> type Some_Int is new <Some_Int_Chosen_by_the_Impl> range 1 .. 100;

I mean that then we would have two distinct types to assign conflicting
operations to. So "+" each goes to a separate type.

G.B.

unread,
Jun 23, 2022, 6:53:15 AM6/23/22
to
On 23.06.22 11:32, Dmitry A. Kazakov wrote:

> In any case having an explicit array interface does not preclude built-in arrays.

Indeed, if there were no built-in arrays, how would one

- specify aspects like "contiguous memory",
- permit implementations to create efficient addressing
for arrays' storage units
- map to hardware?

Randy Brukardt

unread,
Jun 23, 2022, 9:21:14 PM6/23/22
to
"G.B." <bau...@notmyhomepage.invalid> wrote in message
news:t91gmo$rh1$1...@dont-email.me...
With aspects, of course, that are part of the "interface". Why do you think
that one needs a visible built-in array in order to support these (or any
other) operations?

Again, at the implementation level, surely there is some sort of built-in
array. But one does not need to expose that at the programmer level.
"Efficient addressing" is the compiler's job, it will do whatever makes
sense for the target (and those vary quite a bit).

For example, "contiguous" is an aspect that Ada is missing (but should
have). Implementations should have the freedom to pick any implementation in
the absence of such as aspect (which should be rarely needed, mainly for
interfacing with hardware and foreign software).

Mapping to hardware could be handled with a "fixed vector" container (one
that would be rarely used other than for interfacing). It doesn't require an
array, just something with the right semantics.

Randy.


Randy Brukardt

unread,
Jun 23, 2022, 9:24:36 PM6/23/22
to
"Dmitry A. Kazakov" <mai...@dmitry-kazakov.de> wrote in message
news:t91buq$10im$1...@gioia.aioe.org...
> On 2022-06-23 03:06, Randy Brukardt wrote:
...
>> (How something gets implemented should not be part of a language
>> design, so long as the design does not prevent an efficient
>> implementation.)
>> I certainly would not treat them as special in any way, just a series of
>> function calls. (Possibly records could be treated that way as well,
>> although it is less clear that an efficient implementation is possible
>> for
>> them.)
>
> Syntax sugar for subprogram calls is not enough because it does not allow
> generic programming. One should be able to write a program that deals with
> any instance of the interface. Like a generic body working with any actual
> array or a class-wide body which unfortunately is impossible to have for
> arrays presently.

You're thinking too small. Obviously, in a language without an syntactic
array construct, every data structure would be some sort of record. So
class-wide operations would be available for all of those -- and without all
of the complications of a separate formal array type. The idea is to have
one mechanism for pretty much everything, and let the compiler sort out the
results. Back when we created Janus/Ada, that wasn't really practical
because of memory and CPU speed constraints, but none of that holds true
anymore. Simplify the language, complicate the compiler!

Randy.


Dmitry A. Kazakov

unread,
Jun 24, 2022, 2:50:18 AM6/24/22
to
On 2022-06-24 03:24, Randy Brukardt wrote:
> "Dmitry A. Kazakov" <mai...@dmitry-kazakov.de> wrote in message
> news:t91buq$10im$1...@gioia.aioe.org...
>> On 2022-06-23 03:06, Randy Brukardt wrote:
> ...
>>> (How something gets implemented should not be part of a language
>>> design, so long as the design does not prevent an efficient
>>> implementation.)
>>> I certainly would not treat them as special in any way, just a series of
>>> function calls. (Possibly records could be treated that way as well,
>>> although it is less clear that an efficient implementation is possible
>>> for
>>> them.)
>>
>> Syntax sugar for subprogram calls is not enough because it does not allow
>> generic programming. One should be able to write a program that deals with
>> any instance of the interface. Like a generic body working with any actual
>> array or a class-wide body which unfortunately is impossible to have for
>> arrays presently.
>
> You're thinking too small. Obviously, in a language without an syntactic
> array construct, every data structure would be some sort of record.

They are fundamentally different. Record interface is static mapping:

identifier -> value

1D array interface is dynamic mapping:

ordered value -> value

It not only has run-time semantics of (indexing). It is also ordering of
the index which implies enumeration, ranges, slices.

> So
> class-wide operations would be available for all of those -- and without all
> of the complications of a separate formal array type. The idea is to have
> one mechanism for pretty much everything, and let the compiler sort out the
> results. Back when we created Janus/Ada, that wasn't really practical
> because of memory and CPU speed constraints, but none of that holds true
> anymore. Simplify the language, complicate the compiler!

I don't buy the idea of run-time penalty for having abstract data types
and I don't see why built-in arrays cannot coexist with user-defined
ones without turning the language into LISP. Furthermore, the age of
free CPU cycles came to an end. Soon we will have return back to sanity.

Randy Brukardt

unread,
Jun 24, 2022, 11:13:49 PM6/24/22
to
"Dmitry A. Kazakov" <mai...@dmitry-kazakov.de> wrote in message
news:t93mr6$pvm$1...@gioia.aioe.org...
Dymanic means a function. And there is no reason to treat a few functions as
special (again, at the user level).

Something purely-compile time (like the selection of record components)
clearly needs some special semantics. Not so clear with dynamic things.

>> So
>> class-wide operations would be available for all of those -- and without
>> all
>> of the complications of a separate formal array type. The idea is to have
>> one mechanism for pretty much everything, and let the compiler sort out
>> the
>> results. Back when we created Janus/Ada, that wasn't really practical
>> because of memory and CPU speed constraints, but none of that holds true
>> anymore. Simplify the language, complicate the compiler!
>
> I don't buy the idea of run-time penalty for having abstract data types
> and I don't see why built-in arrays cannot coexist with user-defined ones
> without turning the language into LISP.

They add a huge amount of complication for very little gain. One could
simplify them (getting rid of mistaken features like slices), but you still
would want a user-defined version. And once you have that, you no longer
need a special version - you just have some predefined versions (just like
Ada handles operators).

> Furthermore, the age of free CPU cycles came to an end. Soon we will have
> return back to sanity.

I don't think programming abstractly and translating that into good code
will ever be a bad idea. After all, that is the idea behind Ada. If you
truly want to worry about the cost of compilation, then you have program in
a very close to the metal language, even lower level than C. And current
machines are way harder to generate code for than the Z-80 that we started
out on (and even then, we generated pretty bad code with the very tiny
compiler).

I'd rather plan for a future where the compiler tool set does a lot of
correctness checking for one's programs; code generation will always be much
cheaper than that (especially if you do not need bleeding edge performance).
Simplify (and abstract) the language, complicate the compiler!

Randy.


Dmitry A. Kazakov

unread,
Jun 25, 2022, 4:50:59 AM6/25/22
to
On 2022-06-25 05:13, Randy Brukardt wrote:
> "Dmitry A. Kazakov" <mai...@dmitry-kazakov.de> wrote in message
> news:t93mr6$pvm$1...@gioia.aioe.org...
>> On 2022-06-24 03:24, Randy Brukardt wrote:

>>> You're thinking too small. Obviously, in a language without an syntactic
>>> array construct, every data structure would be some sort of record.
>>
>> They are fundamentally different. Record interface is static mapping:
>>
>> identifier -> value
>>
>> 1D array interface is dynamic mapping:
>>
>> ordered value -> value
>>
>> It not only has run-time semantics of (indexing). It is also ordering of
>> the index which implies enumeration, ranges, slices.
>
> Dymanic means a function. And there is no reason to treat a few functions as
> special (again, at the user level).

No. Identifier is not a value, it is a static name. This allows us to
have any statically chosen type on the right side of the mapping.

With a dynamic mapping we must have the same type for all outcomes, e.g.
of the array element.

>> I don't buy the idea of run-time penalty for having abstract data types
>> and I don't see why built-in arrays cannot coexist with user-defined ones
>> without turning the language into LISP.
>
> They add a huge amount of complication for very little gain.

We have different priorities here. I see arrays with slices as one of
the most important language features. Especially considering formal
verification and validation.

>> Furthermore, the age of free CPU cycles came to an end. Soon we will have
>> return back to sanity.
>
> I don't think programming abstractly and translating that into good code
> will ever be a bad idea. After all, that is the idea behind Ada. If you
> truly want to worry about the cost of compilation, then you have program in
> a very close to the metal language, even lower level than C. And current
> machines are way harder to generate code for than the Z-80 that we started
> out on (and even then, we generated pretty bad code with the very tiny
> compiler).

No, I worry about cost of execution. You want to simplify the compiler
at the expense of the program complexity and efficiency of its code.

> I'd rather plan for a future where the compiler tool set does a lot of
> correctness checking for one's programs;

Yes and correctness checking requires proper and very refined
abstractions you are ready to throw away. Here is a contradiction. In a
language like Forth there is basically nothing to check.

Randy Brukardt

unread,
Jun 27, 2022, 5:37:13 PM6/27/22
to
"Dmitry A. Kazakov" <mai...@dmitry-kazakov.de> wrote in message
news:t96i9f$1mc1$1...@gioia.aioe.org...
> On 2022-06-25 05:13, Randy Brukardt wrote:
...
>> I don't think programming abstractly and translating that into good code
>> will ever be a bad idea. After all, that is the idea behind Ada. If you
>> truly want to worry about the cost of compilation, then you have program
>> in
>> a very close to the metal language, even lower level than C. And current
>> machines are way harder to generate code for than the Z-80 that we
>> started
>> out on (and even then, we generated pretty bad code with the very tiny
>> compiler).
>
> No, I worry about cost of execution. You want to simplify the compiler at
> the expense of the program complexity and efficiency of its code.

I think we strongly agree here. I want to simplify the front-end to
strengthen abstractions, and so that the effort can be put where it belongs,
on static analysis/optimization and on code generation. These things are
mostly fancy pattern matching, and the more of it that you can do, the
better. Time spent implementing features that don't increase the expressive
power (or worse, compilicate code generation in all cases) is
counter-productive.

>> I'd rather plan for a future where the compiler tool set does a lot of
>> correctness checking for one's programs;
>
> Yes and correctness checking requires proper and very refined abstractions
> you are ready to throw away. Here is a contradiction. In a language like
> Forth there is basically nothing to check.

Slices are not an abstraction. They are a way to describe a particular kind
of processor operation in a vaguely abstract way. But they don't extend to
user-defined structures or even multi-dimensional maps (arrays if you
prefer) in any practical way. The distributed overhead that they cause is
immense (for instance, you can't have a discontigious array represesentation
with slices, unless you are willing to pay a substantial cost for *every*
array parameter). They're the anti-abstraction feature.

Randy.


Niklas Holsti

unread,
Jun 28, 2022, 1:36:25 AM6/28/22
to
On 2022-06-28 0:37, Randy Brukardt wrote:
>
> Slices are not an abstraction. They are a way to describe a particular kind
> of processor operation in a vaguely abstract way.


The abstraction is the concept of a one-dimensional array (a vector or a
sequence). Slicing such an object -- taking a subsequence -- seems a
very natural operation to me, even on the abstraction level.


> The distributed overhead that they [slices] cause is immense (for
> instance, you can't have a discontigious array represesentation with
> slices,


Why would you want to have a non-contiguous representation for
one-dimensional arrays? Perhaps to make "unbounded" (extensible) arrays?

Dmitry A. Kazakov

unread,
Jun 28, 2022, 3:52:20 AM6/28/22
to
On 2022-06-27 23:37, Randy Brukardt wrote:

> The distributed overhead that they cause is
> immense (for instance, you can't have a discontigious array represesentation
> with slices, unless you are willing to pay a substantial cost for *every*
> array parameter). They're the anti-abstraction feature.

There is nothing wrong with having non-contiguous slices of
non-contiguous arrays! Contiguity of index does not automatically imply
contiguity of element allocation, unless specifically required by the
[sub]type constraint.

1D array abstraction is a mapping index -> element where

1. Index has an order. Index has operations 'Succ and 'Pred;

2. The mapping is convex. If there are elements for two indices, then
there are elements for all indices between them.

Contiguity is a representation constraint.

One should be able to have an array equivalent of unbounded string with
unbounded slices both allocated non-contiguously. The slices you could
shrink or expand:

Text (45..80) := ""; -- Cut a piece off

At the same time one should have a contiguous subtype of the same
unbounded string for interfacing purposes, dealt by copy-out copy-in.

Randy Brukardt

unread,
Jun 29, 2022, 12:01:44 AM6/29/22
to
"Niklas Holsti" <niklas...@tidorum.invalid> wrote in message
news:jhviam...@mid.individual.net...
> On 2022-06-28 0:37, Randy Brukardt wrote:

>> Slices are not an abstraction. They are a way to describe a particular
>> kind
>> of processor operation in a vaguely abstract way.
>
>The abstraction is the concept of a one-dimensional array (a vector or a
>sequence).

But there is (or shouldn't be) anything special about a one-dimensional
array (presuming you intend to allow arrays with more dimensions). And the
"abstraction" you talk about is selecting a bunch of barely related elements
from a multi-dimensional array.

...
> Why would you want to have a non-contiguous representation for
> one-dimensional arrays? Perhaps to make "unbounded" (extensible) arrays?

Exactly. If you don't know how large something will grow to, it's better to
allocate it in pieces than to copy it each time it grows.

Randy.


Randy Brukardt

unread,
Jun 29, 2022, 12:07:09 AM6/29/22
to
What you say here is precisely how they should work. But you would make the
cost of supporting that necessary on every array object, because one could
not know when an array is passed as a parameter what sort of representation
it has. So one has to use dispatching helper operations to implement all
assignments. And that's way more expensive than traditional array
implementation.

If you didn't have slices that could be passed as array parameters, then
that problem does not exist. And I don't think slices are sufficiently
worthwhile to force expensive, unoptimizable code.

Randy.

"Dmitry A. Kazakov" <mai...@dmitry-kazakov.de> wrote in message
news:t9ebvc$1amt$1...@gioia.aioe.org...

Dmitry A. Kazakov

unread,
Jun 29, 2022, 3:24:41 AM6/29/22
to
On 2022-06-29 06:07, Randy Brukardt wrote:
> What you say here is precisely how they should work. But you would make the
> cost of supporting that necessary on every array object, because one could
> not know when an array is passed as a parameter what sort of representation
> it has. So one has to use dispatching helper operations to implement all
> assignments. And that's way more expensive than traditional array
> implementation.

It is already there because Ada 83 arrays have definite and indefinite
representations. Adding a user-defined representation on top is nothing.

> If you didn't have slices that could be passed as array parameters, then
> that problem does not exist. And I don't think slices are sufficiently
> worthwhile to force expensive, unoptimizable code.

You still must be able to pass a definite array for indefinite argument
and conversely. Slices change nothing.

The language must expose subtype conversions per adding removing
constraints to the user. This is what definite/indefinite and slicing
does. But also, it is what the type tag could be for class-wide arrays
or scalar types. I am talking about Integer'Class. And it is what
measurement units constraints are.

There should be a universal mechanism of dealing with type constraints
unifying discriminants/tags/bounds.

Jeffrey R.Carter

unread,
Jun 29, 2022, 4:30:23 AM6/29/22
to
On 2022-06-29 06:01, Randy Brukardt wrote:
>
> But there is (or shouldn't be) anything special about a one-dimensional
> array (presuming you intend to allow arrays with more dimensions). And the
> "abstraction" you talk about is selecting a bunch of barely related elements
> from a multi-dimensional array.

Arrays are usually used to implement map, (mathematical) matrices and vectors,
or sequences. Each usage tends to have unique features:

* Maps are usually constrained. It does not make sense to concatenate, sort,
slice, or slide a map. The abstraction of a map includes non-discrete key
subtypes, so arrays used as maps are a special case.

* Matrices have component types that behave like numbers. The mathematical
definition of matrices includes integer indices with a lower bound of 1. Vectors
are usually considered to be matrices of one column ("column vector") which can
be transposed to obtain matrices of one row ("row vector").

* Sequences are usually unconstrained. Typical discussion of sequences outside
of programming use integer values to indicate positions, using terms such as the
first thing in a sequence, the second thing, ..., so indices should be of an
integer type with a lower bound of 1. It sometimes makes sense to concatenate,
sort, slice, or slide sequences.

A language that provided direct support for these abstractions should not need
to provide arrays.

--
Jeff Carter
"[T]he language [Ada] incorporates many excellent structural
features which have proved their value in many precursor
languages ..."
C. A. R. Hoare
180

Dmitry A. Kazakov

unread,
Jun 29, 2022, 5:04:16 AM6/29/22
to
On 2022-06-29 10:30, Jeffrey R.Carter wrote:

> * Maps are usually constrained. It does not make sense to concatenate,
> sort, slice, or slide a map. The abstraction of a map includes
> non-discrete key subtypes, so arrays used as maps are a special case.

There is an important distinction regarding the keys. Some maps assume
keys ordered or have a distance measure, so that the map would have
operations like next-to-this or closest-neighbor-of. Another type of
maps is when keys are totally unordered, e.g. relational tables (key =
tuple).

> A language that provided direct support for these abstractions should
> not need to provide arrays.

Which is of course impossible considering the variety of maps (e.g.
graph is a map etc) and all problem-space specific.

Array as a building block is the best way to go, IMO.

Niklas Holsti

unread,
Jun 29, 2022, 7:06:34 AM6/29/22
to
On 2022-06-29 11:30, Jeffrey R.Carter wrote:
> On 2022-06-29 06:01, Randy Brukardt wrote:
>>
>> But there is (or shouldn't be) anything special about a
>> one-dimensional array (presuming you intend to allow arrays with
>> more dimensions). And the "abstraction" you talk about is selecting
>> a bunch of barely related elements from a multi-dimensional array.
>
> Arrays are usually used to implement map, (mathematical) matrices and
> vectors, or sequences. Each usage tends to have unique features:
>
> * Maps are usually constrained. It does not make sense to concatenate,
> sort, slice, or slide a map.

In mathematics, maps (functions) are often sliced, in other words
restricted to a subset of their full domain. They are also often
concatenated, in the sense of combining functions defined on separate
domain sets into a combined function defined on the union of those
separate domain sets. Those operations would be useful in programs too.

The essential aspect of maps and map operations is that there is no
"sliding" that changes the relationship of domain values (keys) with
range values.

That said, it very often makes sense to provide sorted access to the
elements of a map, sorted by some criterion, while maintaining the
relationship of keys and their mapped values. That might be seen as
sorting the map into a sequence (as described below).


> * Matrices have component types that behave like numbers. The
> mathematical definition of matrices includes integer indices with a
> lower bound of 1. Vectors are usually considered to be matrices of one
> column ("column vector") which can be transposed to obtain matrices of
> one row ("row vector").


Computations that really use matrices (as opposed to multi-dimensional
maps or plain arrays) very often make use of slices in the form of
selected rows, columns, diagonals, or contiguous submatrices, with
sliding of indices.


> * Sequences are usually unconstrained. Typical discussion of sequences
> outside of programming use integer values to indicate positions, using
> terms such as the first thing in a sequence, the second thing, ..., so
> indices should be of an integer type with a lower bound of 1. It
> sometimes makes sense to concatenate, sort, slice, or slide sequences.


Yes. But IME it is almost never useful /not/ to slide the indices.


> A language that provided direct support for these abstractions should
> not need to provide arrays.


Except if someone invents new abstractions and needs a "raw memory" type
to implement them.

I like that Ada is a multi-level language, with both high-level services
(eg. containers) and low-level services (arrays). As long as Ada uses
arrays to represent sequences, such as character strings, array slices
are quite nice to have, at least as values. I have not often needed to
use a slice as a variable (eg. as the target of an assignment, or as an
"out" parameter).

Jeffrey R.Carter

unread,
Jun 29, 2022, 8:53:36 AM6/29/22
to
On 2022-06-29 13:06, Niklas Holsti wrote:
> On 2022-06-29 11:30, Jeffrey R.Carter wrote:
>>
>> * Maps are usually constrained. It does not make sense to concatenate, sort,
>> slice, or slide a map.
>
> In mathematics, maps (functions) are often sliced, in other words restricted to
> a subset of their full domain. They are also often concatenated, in the sense of
> combining functions defined on separate domain sets into a combined function
> defined on the union of those separate domain sets. Those operations would be
> useful in programs too.
>
> The essential aspect of maps and map operations is that there is no "sliding"
> that changes the relationship of domain values (keys) with range values.
>
> That said, it very often makes sense to provide sorted access to the elements of
> a map, sorted by some criterion, while maintaining the relationship of keys and
> their mapped values. That might be seen as sorting the map into a sequence (as
> described below).

Perhaps I could have been clearer. I mean that it doesn't make sense to
concatenate, sort, slice, or slide an array used as a map. For example, if we
represent a map Character => Natural as

type Char_Count_Map is array (Character) of Natural;
Map : Char_Count_Map;

and want a map with the domain restricted to the ASCII letters, both capital and
small,

Map ('A' .. 'Z') & Map ('a' ..'z')

doesn't do what we want.

You are talking about the abstraction of a map in general, and the operations
you describe are different from the array operations I was talking about.

Randy Brukardt

unread,
Jun 30, 2022, 1:00:38 AM6/30/22
to
"Dmitry A. Kazakov" <mai...@dmitry-kazakov.de> wrote in message
news:t9gunm$97a$1...@gioia.aioe.org...
> On 2022-06-29 06:07, Randy Brukardt wrote:
>> What you say here is precisely how they should work. But you would make
>> the
>> cost of supporting that necessary on every array object, because one
>> could
>> not know when an array is passed as a parameter what sort of
>> representation
>> it has. So one has to use dispatching helper operations to implement all
>> assignments. And that's way more expensive than traditional array
>> implementation.
>
> It is already there because Ada 83 arrays have definite and indefinite
> representations. Adding a user-defined representation on top is nothing.

Actually, it changes everything. Array descriptors are easy, indeed, for
many years Janus/Ada represented all arrays the same (an indefinite
representation with a small descriptor containing a pointer at a data area).
Changing the representation of the underlying data is where the cost is.

Randy.


Randy Brukardt

unread,
Jun 30, 2022, 1:03:13 AM6/30/22