Version 1.4 of ai12s/ai12-0257-1.txt

Unformatted version of ai12s/ai12-0257-1.txt version 1.4
Other versions for file ai12s/ai12-0257-1.txt

!standard 4.1.3(9.1/2)          20-01-30 AI12-0257-1/02
!standard 4.1.3(9.2/2)
!class Amendment 18-02-22
!status work item 20-01-30
!status Hold by Letter Ballot (8-0-3) - 18-05-07
!status work item 18-02-22
!status received 18-03-20
!priority Very_Low
!difficulty Medium
!subject Generalize prefix views
!summary
Prefixed views are allowed on all (?) types.
!problem
Ada users often are surprised that prefixed views cannot be used for abstractions that aren't tagged. That can lead to abstractions being declared tagged for no reason other than wanting prefixed notation (argubly, this was the case with the containers).
!proposal
There are four possible extensions to prefixed views:
(1) Allow them on all types without restrictions.
(2) Allow them only on record types (tagged and untagged). (Presumably with a way to declare that a private type will be completed with a record.)
(3) Allow them on tagged types and untagged types with partial views.
(4) Allow them on record types and untagged types with partial views.
The pros and cons of these proposals are explored in the !discussion.
!wording
** TBD.
!discussion
Supporting prefixed views on just untagged private types (as well as the current tagged types) initially seems appealing (as it side-steps the access problem).
It would be unusual if the prefixed view wasn't allowed at all on the full type of an untagged private type (unless the full type was tagged or access-to-tagged). That would mean that moving a subprogram previously defined in client code into the package of an abstraction could fail to compile if it used any prefixed views. (This could happen if the package was changed to be a child of the package that defines the type, for one example.)
Therefore, we do not consider untagged private types alone as a viable option.
----
Unfortunately allowing prefixed views on any access types (all but option (2)) would be incompatible.
Ptr.Id
could mean Id(Ptr) or Id(Ptr.all). Currently, Id(Ptr) is not considered, so allowing prefixed views on all types could make some currently legal calls ambiguous. To work around such an ambiguity would require falling back to "regular" call notation, meaning that a "use" clause or lengthy selected notation would be required. But the entire reason for using prefixed notation is to avoid those things, so this is a nasty incompatibility.
A second incompatibility is possible for any potential extension of prefixed notation. If the prefix is overloaded with a mix of tagged and untagged types, a prefixed call could become ambiguous. In this case, the problem can be worked around by qualifying the prefix, so the fix requires much less change.
----
Supporting prefixed views on all types is a precursor to obfuscated Ada contests, catching up with the C-family languages. For instance, as brought up at an ARG meeting:
if A."-"."/="(3)."not" then ...
This means:
if not (-A /= 3) then...
The value of prefix notation for scalar types (in particular) is unclear. OTOH, having a cleaner set of rules is important.
----
The current wording of 4.1.3 does not require subprograms to be primitive in order to use prefixed notation, rather only that they be declared in the same declarative region as the type. This is important for types that are declared in subprograms and blocks (for which no non-inherited subprograms are primitive). This seems to be an important property to preserve, especially as it is much more likely for untagged types to be declared outside of packages.
----
Regardless of what option is selected, care is needed that prefix calls and normal calls don't end up with different rules. In particular, the "implicit conversions" (they're not called that in Ada, they're just different types that can resolve) defined in 8.6 are not invoked by the 4.1.3 rules. That means that some calls that are legal by "normal" notation might not be legal for prefix notation.
For instance, if we just struck "tagged" from 4.1.3(9.1-9.2/3), then consider:
package P1 is type T1 is ... procedure Process (P : access T1); type AT1 is access T1'Class; procedure Process2 (P : AT1); end P1;
with P1; package P2 is type T2 is new P1.T1 with ...; procedure Process (P : access T2); type AT2 is access T2'Class; end P2;
with P1; package P3 is type T3 is new P1.T1 with .. procedure Process (P : access T3); type GAT3 is access all T3'Class; procedure Process3 (P : GAT3); end P3;
declare Obj1 : P1.At1 := ...; Obj2 : P2.At2 := ...; Obj3 : access P3.T3 := ...; begin Obj1.Process2; -- OK if rules changed, not allowed in Ada 2012. Obj2.Process; -- OK. (Actually Obj2.all'Access.Process). Obj2.Process2; -- Nope. (Different access types.) -- Note: 'Access only tried for anonymous access params. P1.Process (Obj1); -- OK. P2.Process (Obj2); -- OK. P1.Process2 (Obj2); -- Nope. Obj3.Process; -- OK. (Actually Obj3.all'Access.Process). Obj3.Process3; -- Nope. P3.Process (Obj3); -- OK. P3.Process3 (Obj3); -- OK by 8.6(26.1/3) end;
In order to avoid these sorts of problems, the wording of 4.1.3(9.2/3) would have to allow the various implicit conversions. Note that the oddity here is not changed by the rule changes; it already resolves differently in the two cases. There are many additional such cases that can happen if access types are allowed.
If we make any change to the wording, we should definitely try to reduce these cases.
----
Option (2) requires some way to specify on a private type that the completion will be a record type so that prefix notation can be supported on the private type. This is necessary to avoid breaking privacy in the worst way. Note that "tagged" has this role in the current rules. (Without the notation, prefixed views would not be allowed on private types.)
Two options have been suggested:
An aspect:
type Priv is private with Prefix_Notation;
[As always, this is shorthand for "Prefix_Notation => True".]
Or a keyword in place of "tagged":
type Priv is record private;
It's a little weird to affect resolution with an aspect (it usually only affects Legality Rules), but it would not be too far outside of the model. Using "record" also seems a bit weird.
!ASIS
[Not sure. Most likely, the existing prefix notation support would be sufficient - Editor.]
!ACATS test
ACATS B- and C-Tests are needed to check that the new capabilities are supported.
!appendix

From: Tucker Taft
Sent: Saturday, January 20, 2018  10:32 AM

> 0) Should Big_Integer and (especially) Big_Rational be visibly tagged?
>
> If so, then we can use prefix notation on functions like Numerator and
> Denominator. We could also consider deriving both versions (usual and
> bounded) from an abstract ancestor.
> ...

Somewhat off topic, but should we consider generalizing prefix notation to all
types, or at least all private types?  I can't remember any reason why prefix
notation really needs tagged types to make sense.  Now that we have "use all
type" clauses, we already know how to make all the relevant (primitive)
operations directly visible.  Why not allow prefix notation for all such
operations?

****************************************************************

From: Gary Dismukes
Sent: Saturday, January 20, 2018  7:40 PM

It seems reasonable to me to consider such an extension.

****************************************************************

From: Randy Brukardt
Sent: Saturday, January 20, 2018  7:37 PM

My recollection was that access types caused problems, because of the implicit
indirection. In particular, does

    Ptr.Id

mean Id(Ptr) or Id(Ptr.all)? By making the rules tagged only, we side-stepped
that.

I'd have to look up the minutes/notes on the prefixed notation to see the exact
cases that caused trouble. (The implicit 'Access might also cause issues.) I
know the issues were bothersome enough to restrict them to tagged types as a
last resort (your original proposal allowed them on all types).

Blame Baird, as usual. ;-)

Note that allowing all types now would in fact be incompatible, if Ptr and
Ptr.all both had an Id primitive operation. Currently, the Ptr operation isn't
considered, so changing the rules could make some legal calls ambigious.

Allowing private only was considered, but it would be weird if clients could
use prefixed notation but the implementation of some type could not.

****************************************************************

From: Randy Brukardt
Sent: Saturday, January 20, 2018  8:17 PM

> It seems reasonable to me to consider such an extension.

You are willing to introduce an incompatibility and/or visibility hole for
that??

****************************************************************

From: Gary Dismukes
Sent: Sunday, January 21, 2018  1:45 AM

Probably not, it was a quick reply to Tucker's message just indicating that it
might be worth considering his suggestion, without thinking more deeply about
it at the time (whereas you did).  It did occur to me that there might be
incompatibilities.  In any case, I hadn't yet seen your message at the time I
posted that.

****************************************************************

From: Tucker Taft
Sent: Sunday, January 21, 2018  2:11 PM

...
> Note that allowing all types now would in fact be incompatible, if Ptr
> and Ptr.all both had an Id primitive operation. Currently, the Ptr
> operation isn't considered, so changing the rules could make some calls
> ambigious.

Good point.  So access types should probably be left out of the picture,
except in service to other types via access parameters.

> Allowing private only was considered, but it would be weird if clients
> could use prefixed notation but the implementation of some type could not.

This doesn't seem so odd to me now, in part because several of the features
I have been working on recently will probably have similar rules, where you
can use a notation when you only see the partial view, but when you see the
full view the notation is unavailable.  That doesn't seem so bad.  When you
are defining an abstraction, you often don't want the abstraction being used
"inside" because it might lead to infinite recursion, or just some confusion.
On the other hand, when looking at it from the outside, you aren't distracted
by the details of the implementation, and can focus purely on the abstract
behavior, where the "special notation" whatever it is makes sense.  Literals
and container aggregates, in particular, will probably be unavailable when you
can see the full type if the full type happens to be something that has its
own literals and/or aggregates.

The term "partial view" might better be something like "abstraction view" or
"external view" to emphasize how different it might be from the full/internal
view.  It is not just a subset of the full view, but rather a potentially
quite different view.

****************************************************************

From: Randy Brukardt
Sent: Monday, January 22, 2018  3:49 PM

This still seems weird to me.

The literal/aggregate case is one of hiding: if the full type has literals or
aggregates, then the ones of the private view are hidden. But if the full type
doesn't have those things, the literals or aggregates can be used on the full
type as well. That would make perfect sense, and it wouldn't be the case that
the operation "disappeared" so much as being "replaced". (That happens in
other ways as well, so it seems normal.)

But in this case, you'd have an capability that would unconditionally
disappear on the full type. There is nothing like that in Ada currently. And
we don't do anything currently to prevent infinite recursion in package bodies
(spoken as someone who has a lot of experience with implementing "+", "=", and
other overridden operations in terms of themselves); it's strange to suddenly
care about that 40 years too late.

> The term "partial view" might better be something like "abstraction
> view" or "external view" to emphasize how different it might be from
> the full/internal view.  It is not just a subset of the full view, but
> rather a potentially quite different view.

I suppose that could work, but it would take a lot of getting used to, as it
certainly is not the current state of affairs.

****************************************************************

From: Tucker Taft
Sent: Wednesday, October 23, 2019  9:05 AM

!topic Generalizing object.operation(...) notation
!reference Ada 202x 2012 RM4.1.3(9-9.2/3), 6.3.1(20-21/4)
!from Tucker Taft 2019-10-23
!keywords prefix notation, object.operation notation
!discussion

This has come up several times, but it seems worth re-considering allowing the
use of "object.Operation(...)" notation more generally.  We have taken some
steps to unify tagged and untagged record types (e.g. as far as whether equality
"reemerges" in generics and composite equality).  It would be nice to take
another step to reduce the "specialness" of tagged types.

As an indicator of the problem, there are situations some of us have seen where
a type was made visibly tagged merely to get prefix notation.  This is a sign
that the prefix notation has significant value, and probably shouldn't be tied
to a type being visibly tagged.

The simplest solution would be to allow prefix notation on any type when
invoking one of its primitive operations.  Class-wide operations are not
meaningful for untagged types, so that simplifies the problem somewhat.

One challenge is access types, where allowing prefix notation would introduce
potential ambiguity due to implicit dereference.  We could disallow access
types, but then that would mean that a private type completed with an access
type could not use prefix notation in any expression subject to full conformance
checks, such as the default expression of some subprogram declared in the
visible part of the package.  This could be solved by allowing conformance
between a call using a prefixed-view and a regular call, though that would add
complexity to the full conformance rules for expressions (6.3.1(20-21/4)).

However, the ambiguity introduced by allowing access types to use prefixed
notation does not seem excessive, and overload resolution already has a similar
situation where a private tagged type happens to have a record component with
the same name as a visible primitive operation (see 4.1.3(9.2/3)), and prefix
notation that is acceptable in the visible part might be ambiguous or have a
different interpretation (do we have a Beaujolais effect here??) in the private
part.

Slightly less radical would be to allow prefix notation on private types and
record types.  Allowing it on private types introduces a similar problem in that
the full type would need to be a record type for a default expression to use
prefix notation, unless we relax the conformance rules.

****************************************************************

From: Arnaud Charlet
Sent: Sunday, October 27, 2019  4:55 PM

> One challenge is access types, where allowing prefix notation would
> introduce potential ambiguity due to implicit dereference.  We could
> disallow access types, but then that would mean that a private type
> completed with an access type could not use prefix notation in any
> expression subject to full conformance checks, such as the default
> expression of some subprogram declared in the visible part of the package.
> This could be solved by allowing conformance between a call using a
> prefixed-view and a regular call, though that would add complexity to
> the full conformance rules for expressions (6.3.1(20-21/4)).

I would not bother on access types for non tagged types. This isn't where people
have the need for the dotted notation outside of tagged types in my experience,
so I'd be in favor of a simple model here.

****************************************************************

From: Luke A. Guest
Sent: Sunday, October 27, 2019  4:58 PM

> This has come up several times, but it seems worth re-considering allowing
> the use of "object.Operation(...)" notation more generally.  We have taken
> some steps to unify tagged and untagged record types (e.g. as far as whether
> equality "reemerges" in generics and composite equality).  It would be nice
> to take another step to reduce the "specialness" of tagged types.

Yes, I would love to see Ada have something like D's UFCS
(https://tour.dlang.org/tour/en/gems/uniform-function-call-syntax-ufcs).
e.g.

procedure Test_1 (Name : in Positive);

Something : Positive := ...;

Something.Test_1;

But would this be allowed?

procedure Test_2 (Name : in String);

"Hello".Test_2;

> One challenge is access types, where allowing prefix notation would
> introduce potential ambiguity due to implicit dereference.  We could
> disallow access types, but then that would mean that a private type
> completed with an

I don't think this is a good idea really as Ada already has in some places
implicit dereferences, i.e. pointers to sub-programs.

****************************************************************

From: Alejandro R. Mosteo
Sent: Wednesday, October 23, 2019  10:13 AM

A couple of comments purely as a user:

>As an indicator of the problem, there are situations some of us have seen
>where a type was made visibly tagged merely to get prefix notation.  This
>is a sign that the prefix notation has significant value, and probably
>shouldn't be tied to a type being visibly tagged.

I usually do this. The main reasons I do it are: 1) It allows function
chaining [1]. 2) It eliminates the need to either "use" or prefix with
package name.

> The simplest solution would be to allow prefix notation on any type when
> invoking one of its primitive operations.  Class-wide operations are not
> meaningful for untagged types, so that simplifies the problem somewhat.

I'm developing a couple of generic-intensive libraries [2] where non-primitive
functions would greatly benefit from the aforementioned function chaining. I
understand the big deterrent here is that a new "with" could generate
ambiguity whereas now this can't happen?

Just bringing up the point; I think there's a broader issue here that touches
on implicit generics too.

>One challenge is access types, where allowing prefix notation would introduce
>potential ambiguity due to implicit dereference.  (...)

All my experiences using reference types with implicit dereference involve
sooner than later situations where I have to use the .Element.all to
disambiguate or have the pointee type recognized. I'd say that cat is out of
the bag.

[1] https://en.wikipedia.org/wiki/Uniform_Function_Call_Syntax
[2] https://github.com/mosteo/rxada

****************************************************************

From: Yannick Moy
Sent: Monday, October 28, 2019  5:46 AM

>The simplest solution would be to allow prefix notation on any type when
>invoking one of its primitive operations.  Class-wide operations are not
>meaningful for untagged types, so that simplifies the problem somewhat.

I confirm this would be a very nice enhancement to Ada, that many users would
welcome.

>>One challenge is access types, where allowing prefix notation would
>>introduce potential ambiguity due to implicit dereference.  We could
>>disallow access types, but then that would mean that a private type
>>completed with an access type could not use prefix notation in any
>>expression subject to full conformance checks, such as the default
>>expression of some subprogram declared in the visible part of the package.
>>This could be solved by allowing conformance between a call using a
>>prefixed-view and a regular call, though that would add complexity to the
>>full conformance rules for expressions (6.3.1(20-21/4)).

>I would not bother on access types for non tagged types. This isn't where
>people have the need for the dotted notation outside of tagged types
>in my experience, so I'd be in favor of a simple model here.

I agree with Arno, a simpler model is enough and probably better for that
feature.

****************************************************************

From: Tucker Taft
Sent: Monday, October 28, 2019  8:08 AM

Unfortunately, there is no completely simple solution.  If we disallow using
object.op() for access types but allow it for private types, we need to deal
with private types that are implemented using an access type.  It is always
awkward to allow something on a private type but not on its full view.  In any
case, given the apparent support for this idea, the ARG can work to come up
with a set of rules that are good on the usability side, even if there are
some complexities behind the scene.

One approach might be to allow object.op() only on access types that have a
partial view (i.e. that implement a private type).  That way the full type
allows the same capabilities as the partial view.

****************************************************************

From: Bob Duff
Sent: Monday, October 28, 2019  12:17 PM

> Unfortunately, there is no completely simple solution.

I can think of one.  ;-)

It's not sufficiently broken, so don't fix it.

****************************************************************

From: Egil Harald Hoevik
Sent: Monday, October 28, 2019  9:05 AM

> The simplest solution would be to allow prefix notation on any type
> when invoking one of its primitive operations.  Class-wide operations
> are not meaningful for untagged types, so that simplifies the problem
> somewhat.

I can imagine some confusion with functions returning the type,  enumarators
especially, ie:

type Colour is (Red, Green, Blue);
Tint : Colour := Red.Green.Blue;

or

return True.False;

****************************************************************

From: Egil Harald Hoevik
Sent: Monday, October 28, 2019  9:49 AM

> But would this be allowed?
>
> procedure Test_2 (Name : in String);
>
> "Hello".Test_2;
>

Or this?

Put_Line( "Hello"."&"( "World" ) );

****************************************************************

From: Randy Brukardt
Sent: Monday, October 28, 2019  10:13 PM

...
>Unfortunately, there is no completely simple solution.  If we disallow
>using object.op() for access types but allow it for private types, we need
>to deal with private types that are implemented using an access type.  It is
>always awkward to allow something on a private type but not on its full view.
>In any case, given the apparent support for this idea, the ARG can work to
>come up with a set of rules that are good on the usability side, even if
>there are some complexities behind the scene.

I don't think that a version of rules that allow private types to *lose*
capabilities when the full view is seen is going to fly. And the access type
problems were pretty severe as I recall, allowing them ever is likely to be
trouble.

I rather liked your idea of only allowing Object.Op on (all) record types.
This would be similar to how we handled composition of equality, and that was
for similar reasons -- doing it generally lead to oddities (see the disgusting
examples with enumeration literals that some recently sent). It's always
possible to wrap something in a record if that is needed to get Object.Op, and
my experience is that seems to happen a lot anyway.

As far as private types go, I think it has to be syntactically part of the
private type. That's currently the case with "tagged". We could add something
else where "tagged" is (say "record"), or we could define an aspect for the
purpose. Usability would suggest the less the better, so a keyword seems
preferable.

It would be nice for usability to allow private types to allow Object.Op by
default, but that then requires the completion to be a record type. That's
likely to be the case in about 95% of examples already (it's *hard* to create
viable examples where a private type is not completed with a record
-- I've needed to do that for various ACATS tests and it tends to not work
well, especially access types), but forcing people to change 5% of their
existing code isn't likely to go over well.

So I'd suggest something like:
     type Priv is record private;
which of course requires the completion to be a record type (untagged or
tagged), and then Object.Op can be allowed on it and all record types without
much complication.

This seems like the simplest solution (short of the one Bob suggested :-).
It doesn't allow *every* possibility, but the vast majority of the practical
ones.

****************************************************************

From: Yannick Moy
Sent: Tuesday, October 29, 2019  3:48 AM

I agree with Randy on his proposed solution:

> So I'd suggest something like:
>     type Priv is record private;
> which of course requires the completion to be a record type (untagged
> or tagged), and then Object.Op can be allowed on it and all record
> types without much complication.
>
> This seems like the simplest solution (short of the one Bob suggested :-).
> It doesn't allow *every* possibility, but the vast majority of the
> practical ones.

and that dot-notation would only be allowed for primitive operations of the
type, not any subprogram, to avoid the problem with with-clauses introducing
ambiguities.

****************************************************************

From: Tucker Taft
Sent: Tuesday, October 29, 2019  8:01 AM

I am not a real fan of adding syntax for this ("record private").  What about
the suggestion of allowing it on a primitive function of any type that has a
partial view?

The enumeration literal example is a red herring -- this would only work on
primitive functions that have at least one parameter, which is of the type
for which it is a primitive.

****************************************************************

From: Tucker Taft
Sent: Tuesday, October 29, 2019  8:03 AM

> I can imagine some confusion with functions returning the type,  enumarators
> especially, ie:
>
> type Colour is (Red, Green, Blue);
> Tint : Colour := Red.Green.Blue;

This would not be legal, because the parameter profile of an enumeration
literal, such as "Green", is:

   function Green return Colour;

while to use "object.op()" notation, the operation has to have at least one
parameter, and the first parameter has to be of the type for which it is
primitive.

> or
>
> return True.False;

Again, not legal since False does not have any parameters.

****************************************************************

From: Egil Harald Hoevik
Sent: Tuesday, October 29, 2019  8:18 AM

...
> while to use "object.op()" notation, the operation has to have at
> least one parameter, and the first parameter has to be of the type for which
> it is primitive.

So then it's a more complex solution than just "when invoking one of its
primitive operations".

****************************************************************

From: Yannick Moy
Sent: Tuesday, October 29, 2019  8:17 AM

> I am not a real fan of adding syntax for this ("record private").  What
> about the suggestion of allowing it on a primitive function of any type
> that has a partial view?

Then how do you allow dot-notation on a private type that is not derived
from another type?

****************************************************************

From: Tucker Taft
Sent: Tuesday, October 29, 2019  8:27 AM

Not sure I understand the question.  The suggestion was that object.op() is
available on private types, as well as on the full view of the type.  By
saying a type "has a partial view" all that means is that it implements a
private type.  And every private type is implemented by some "full" type.

****************************************************************

From: Yannick Moy
Sent: Tuesday, October 29, 2019  8:38 AM

> The suggestion was that object.op() is available on private types, as well
> as on the full view of the type.  By saying a type "has a partial view" all
> that means is that it implements a private type.  And every private type is
>  implemented by some "full" type.

Indeed that clarifies your point, thanks.
How does that solve the issue that you initially raised about implicit
dereference? My understanding was that Randy's proposal precisely avoided any
further complications here.

****************************************************************

From: Tucker Taft
Sent: Tuesday, October 29, 2019  8:32 AM

> So then it's a more complex solution than just "when invoking one of its
> primitive operations".

The current object.op() feature (see RM 4.1.3(9.1-9.2)) is only defined on
primitive subprograms (and "class wide" subprograms) of tagged types, and the
object that comes out front is used as the first parameter.  The proposal was
to generalize that to the primitive subprograms of other types (I wrote
"primitive function" in an earlier e-mail, but I really meant "primitive
subprogram").  There was no intent to generalize it to primitives with no
parameters, or with the "controlling" parameter being something other than the
first parameter.  Sorry if I implied otherwise.

****************************************************************

From: Tucker Taft
Sent: Tuesday, October 29, 2019  8:34 AM

I should have said "primitive subprogram" rather than "primitive function" in
the following...
...
> I am not a real fan of adding syntax for this ("record private").  What
> about the suggestion of allowing it on a primitive function of any type
> that has a partial view?
>
> The enumeration literal example is a red herring -- this would only work
> on primitive functions that have at least one parameter, which is of the
> type for which it is a primitive.

****************************************************************

From: Tucker Taft
Sent: Tuesday, October 29, 2019  9:16 AM

...
> How does that solve the issue that you initially raised about implicit
> dereference?

The problem if we allow object.op() on all access types, is that you end up
having lots of possibilities for ambiguities.  By limiting it to access types
that "complete" a private type, you limit it to operations that were defined
as part of defining a private type.   If you were to allow them on any named
access type, you would have lots of operations that were "primitive" on the
type by chance because they happen to have the named access type as their
first parameter, but without any real intent to being "primitive" in the
logical sense.  So it is a question of degree.  You will still have the
possibility for ambiguity, but should be dramatically decreased, and should
not involve the same level of potential surprise for the user.

> My understanding was that Randy's proposal precisely avoided any further
> complications here.

Perhaps, but it introduces a very nasty need to go back and add "record" to
hundreds of existing private types on the off chance someone will want to use
object.op() notation, and perhaps introduce record wrappers, which would then
imply changing lots of package-body code that will now need an extra level of
selection.  This is exactly the kind of change that people hate having to do,
when all they want is to be able to use "object.op()" notation with their
private type.

****************************************************************

From: Bob Duff
Sent: Tuesday, October 29, 2019  9:32 AM

> ...and perhaps introduce record wrappers,

Wrapping things in records can be pretty painful, readability wise.
Whenever you want to do anything, you have to put Rec.Blah, and then you have
to put (Blah => ...) when you're done to wrap it back up.

****************************************************************

From: Jeffrey R. Carter
Sent: Tuesday, October 29, 2019  11:43 AM

> So I'd suggest something like:
>      type Priv is record private;

type Priv is private record;

reads better.

****************************************************************

From: Randy Brukardt
Sent: Tuesday, October 29, 2019  6:52 PM

> > ...and perhaps introduce record wrappers,
>
> Wrapping things in records can be pretty painful, readability wise.
> Whenever you want to do anything, you have to put Rec.Blah, and then
> you have to put (Blah => ...) when you're done to wrap it back up.

Agreed, but we are talking about things that almost always end up as records
anyway (that is, ADTs). Object.Op implies a form of ADT, and certainly should
not be used on non-ADTs (like any visible elementary type). It probably ought
not be supported by every ADT, either, even if completed by a record.

Pretty much every time that I started out making a private type something
other than a record, I had to ultimately change it to a record. Not making it
a record in the first place is simply a self-inflicted pain. So there should
be very little need to add wrapping after the fact.

It's not worth the complication to figure out how to deal with the unusual
case where a private type is completed by some elementary type.

****************************************************************

From: Randy Brukardt
Sent: Tuesday, October 29, 2019  7:10 PM

> >> The suggestion was that object.op() is available on
> >> private types, as well as on the full view of the type.  By saying a
> >> type "has a partial view" all that means is that it implements a
> >> private type.  And every private type is implemented by some "full"
> >> type.
> >
> > Indeed that clarifies your point, thanks.
> > How does that solve the issue that you initially raised
> > about implicit dereference?
>
> The problem if we allow object.op() on all access types, is that you
> end up having lots of possibilities for ambiguities.
>  By limiting it to access types that "complete" a private type, you
> limit it to operations that were defined as part of
> defining a private type.   If you were to allow them on any
> named access type, you would have lots of operations that were
> "primitive" on the type by chance because they happen to have the
> named access type as their first parameter, but without any real
> intent to being "primitive" in the logical sense.  So it is a question
> of degree.  You will still have the possibility for ambiguity, but
> should be dramatically decreased, and should not involve the same
> level of potential surprise for the user.

I don't buy this for a moment. I find this a bad idea, simply because it has all
of the complications (from a language design perspective) of unlimited
Object.Op(), but you are only allowed to use it in a rare case that almost never
works in practice. If we're going to go through all of the horrible issues from
a language-design perspective, then we might as well allow it everywhere.

> > My understanding was that Randy's proposal precisely
> > avoided any further complications here.
>
> Perhaps, but it introduces a very nasty need to go back and add
> "record" to hundreds of existing private types on the off chance
> someone will want to use object.op() notation, and perhaps introduce
> record wrappers, which would then imply changing lots of package-body
> code that will now need an extra level of selection.  This is exactly
> the kind of change that people hate having to do, when all they want
> is to be able to use "object.op()" notation with their private type.

I totally agree, but I disagree with the idea that every package needs to have
this done.

Object.Op() is a privilege, not a right, and it should be allowed only for ADTs
that were designed to be readable when it is used. It most certainly should not
be allowed for every type, since if you use it at all, you need to use it pretty
much exclusively (both to avoid confusion when reading, and also to gain the
benefit of not needing a "use" clause).

For instance, Text_IO is a package that should not allow Object.Op(). Many of
the most commonly used operations can never be used in this form (the parameters
aren't right), and if you have to have a "use" clause anyway, then use the
traditional form of call everywhere to clarify what you are doing.

I'd never want to see something like:

     use Ada.Text_IO;
     Some_File : File_Type;
     ...
 begin
     ...
     Some_File.Get_Line (Buffer, Len);
     if Input_Trace then
         Put_Line ("Line " & Line_Counter'Image & " has length " & Len'Image);
     end if;

because now you have all of the readability problems of a use clause AND all of
the readability problems of Object.Op() munged together. And I don't think
anyone is going to write:

     Some_File : Ada.Text_IO.File_Type;
     ...
 begin
     ...
     Some_File.Get_Line (Buffer, Len);
     if Input_Trace then
         Ada.Text_IO.Current_Output.Put_Line (
             "Line " & Line_Counter'Image & " has length " & Len'Image);
     end if;

to avoid the confusion. :-)

Ergo, whether Object.Op() is even a good idea is a package-by-package decision.
No one should be adding that willy-nilly to every package. It only makes sense
for OOP-like ADTs. And I find it unlikely that any package for which it is a
good idea would need any wrapping -- I don't believe I've completed a private
type with a non-record type outside of ACATS tests ever (ultimately -- I've
tried a number of times, but that just ended up making me work when I had to
make it into a record).

****************************************************************

From: Emmanuel Briot
Sent: Tuesday, October 29, 2019  3:14 AM

I agree with Randy that this feature only needs to apply to record types. There
is in practice no cost in performance when wrapping a type inside a record. It
is more work (and perhaps not obvious to beginners) how to do that with
indefinite type (unbounded arrays or records with discriminants), but it can be
done.

Compared to the current state of things (where one would add "tagged"), it
doesn't add a whole lot, except:
   1 - avoids the extra memory needed to store the tag in each instance
   2 - doesn't let users extend the record type

Extra memory usage
================

As for requiring an additional keyword to make that possible, as in:

    type A is record private;

that reminded of C++, where classes do not necessarily get a virtual table
pointer. If the compiler can determine that table will not be needed, it doesn’t
have to reserve space for it in class instances. I believe one of the cases
where this optimisation is possible is:   no virtual method, no inherited
virtual method.

It perhaps could be done similarly in Ada. The user would write what we already
do:

   type A is tagged private;

At that point, the current version of the standard already allows dot notation
for subprogram calls, so there’s nothing to change.

Upon seeing the full view of the object, the compiler could then decide whether
or not it needs to add the tag. I am far from being a language lawyer, but can
we imagine something like:

    - no primtiive operations (only class-wide operations)
      This is a bit restrictive possibly, in particular for functions returning
      the type since they would now return a class-wide and thus use secondary
      stack maybe

    - no stream operations

    - no Ada.Tags operations on the tag (external_tag,…) Those might be hard to
      check, since they might occur in other packages.

Because of the third point above, we would likely need the type to have a pragma
similar to GNAT's

     pragma No_Tagged_Streams (Type);

and perhaps

    pragma Discard_Names;

Basically I am proposing to change this new feature request into an optimization
problem.

No type extension
==============

I believe there are already proposals for a “final” keyword or some such for Ada
202x, so that making “tagged” publicly visible doesn’t necessarily mean users
can extend the type.

This is an independent feature which seems to have its own merits in terms of
describing an API

****************************************************************

From: Randy Brukardt
Sent: Tuesday, October 29, 2019  11:46 AM

...
> At that point, the current version of the standard already allows dot
> notation for subprogram calls, so there's nothing to change.
>
> Upon seeing the full view of the object, the compiler could then
> decide whether or not it needs to add the tag. I am far from being a
> language lawyer, but can we imagine something like:
>
>     - no primtiive operations (only class-wide operations)
>       This is a bit restrictive possibly, in particular for functions
>       returning the type since
>       they would now return a class-wide and thus use secondary stack maybe

This would seem to defeat the purpose of allowing Object.Op() notation. (The
class-wide thing is rather a hack, IMHO, even if it was originally my idea. :-)
And I don't see why any such restriction would be needed (presuming some sort of
"final", it can't work otherwise as dispatching is always possible in that case
in any existing class-wide operations - that might not even exist yet).

>     - no stream operations
>
>     - no Ada.Tags operations on the tag (external_tag,.) Those might
> be hard to check,
>       since they might occur in other packages.
>
> Because of the third point above, we would likely need the type to
> have a pragma similar to GNAT's
>
>      pragma No_Tagged_Streams (Type);
>
> and perhaps
>
>     pragma Discard_Names;

Wouldn't it simply make more sense to just give an aspect No_Tag on the type and
then enforce some restrictions on the use? Or, perhaps just make it a different
keyword, like say "record"?? ;-)

Isn't the GNAT pragma a global thing? It doesn't seem to make sense otherwise
(the overhead is in Ada.Tags for the lookup table, not the type, other than the
type name which is covered by Discard_Names). One would want to declare that one
is not going to use Ada.Tags.Internal_Name and friends at all, 'cause that's
what's expensive, not so much having a single type using it.

> Basically I am proposing to change this new feature request into an
> optimization problem.

I don't think it works without throwing out the baby with the bathwater. And one
needs "final" (see below).

> No type extension
> ==============
>
> I believe there are already proposals for a "final" keyword or some
> such for Ada 202x, so that making "tagged" publicly visible doesn't
> necessarily mean users can extend the type.
>
> This is an independent feature which seems to have its own merits in
> terms of describing an API

I can't find any such proposal that was ever formally presented to the ARG. I
think it has come up occassionally in conversation, but most such conversation
has considered it as having minimal value.

Certainly, we'd need a full proposal that explained the "merits in terms of
describing an API", since it seems to be the height of hubris to assume that you
(an API designer) knows how clients are going to use your libraries. Ideally, an
API would be designed to give the client maximum flexibility (which means
allowing extension, avoiding visible access types, allowing clients to compose
or allocate or use in a container as needs, etc.).

The primary use seems to be to prevent users from extending API
designs/implementations that don't work properly with extensions -- but that
seems to be a problem with the API (and in some cases, the Ada language) more
than any sort of good design.

Anyway, there is no "final" proposal on the table, nor has there ever been one
presented this cycle so far as I can tell, so that would have to be defined in
order to make your idea even remotely viable. But that would be pretty similar
to the proposal to use a new keyword (since you wouldn't get Object.Op() on an
existing library with an untagged private type without modifying the spec to use
"tagged final" or whatever syntax is decided -- it would be essentially the same
as "record private", just with more complications). Not sure there is much gain
for Object.Op() specifically (ignoring the merits or lack thereof for "final").

****************************************************************

From: Jean-Pierre Rosen
Sent: Wednesday, October 30, 2019  12:55 AM

> Pretty much every time that I started out making a private type
> something other than a record, I had to ultimately change it to a record.

I almost agree. "Almost", since the full type is sometimes an array of (hidden
in private part) records

****************************************************************

From: Jean-Pierre Rosen
Sent: Wednesday, October 30, 2019  4:14 AM

> Perhaps, but it introduces a very nasty need to go back and add
> "record" to hundreds of existing private types on the off chance
> someone will want to use object.op() notation, and perhaps introduce
> record wrappers, which would then imply changing lots of package-body
> code that will now need an extra level of selection.  This is exactly
> the kind of change that people hate having to do, when all they want
> is to be able to use "object.op()" notation with their private type.

Hmmm... Everytime you write a reusable component, you have to make assumptions
about how it will be used. For example, the choice of your identifiers depends
on whether you assume users will have a use clause or not.

Which makes me think of another possibility: allow prefix notation for
(primitive) operations of any type within the scope of a use type clause (for
the type). So people would allow it selectively if they want to. And those
people are likely to already have the use type clause.

****************************************************************

From: Christoph Grein
Sent: Wednesday, October 30, 2019  6:42 AM

> Which makes me think of another possibility: allow prefix notation for
> (primitive) operations of any type within the scope of a use type
> clause (for the type). So people would allow it selectively if they
> want to. And those people are likely to already have the use type clause.

I := I."+"(2);

or even

I:=@."+"(2);

Isn't this of utmost beauty? ;-)

****************************************************************

From: Tucker Taft
Sent: Wednesday, October 30, 2019  9:18 AM

> Which makes me think of another possibility: allow prefix notation for
> (primitive) operations of any type within the scope of a use type
> clause (for the type). So people would allow it selectively if they want to.
> And those people are likely to already have the use type clause.

A "use type" is not currently required to use object.op notation for tagged
types.  The main point of this proposal, at least in my mind, is that something
that is allowed on a tagged private type should also be allowed on an untagged
private type.  That is, you shouldn't have to add "tagged" just to get to use a
feature that really has little to do with being tagged.

As far as other comments, I don't understand the comment that some private types
should allow this and others shouldn't.  Almost by definition, a private type is
an abstract data type, and hence it would seem that forcing users to distinguish
between private types that allow object.op() and those that don't is an odd
distinction to make.

As far as what proportion of private types are completed with what sort of full
types, here are two separate examples:

ParaSail:
31 private types (ignoring 14 formal private types)
  full types:
12 untagged record types
12 access types
4 derived types (typically derived from a private type exported by a
                 generic instance)
2 protected types
1 tagged record type

CodePeer/Utils:
51 private types (ignoring 123 formal private types)
  full types:
23 derived types (often from a type exported by a generic instance)
16 untagged record types
7 access types
5 tagged record types

---

I would add to the original goal that the source code for the package defining
the private type should *not* have to be changed to allow object.op notation,
and that removing or adding "tagged" to a private type should have no effect on
the clients w.r.t. this object.op feature (presuming this addition or removal of
"tagged" is otherwise legal).

One further way to reduce the issue with ambiguity w.r.t. private types
completed with access types is to limit this to primitive operations declared in
the package spec -- disallowing use with so-called "late" primitives declared in
the package body.

****************************************************************

From: Randy Brukardt
Sent: Wednesday, October 30, 2019  2:22 PM

...
> As far as what proportion of private types are completed with what
> sort of full types, here are two separate examples:
>
> ParaSail:
> 31 private types (ignoring 14 formal private types)
>   full types:
> 12 untagged record types
> 12 access types
> 4 derived types (typically derived from a private type exported by a
> generic instance)
> 2 protected types
> 1 tagged record type
>
> CodePeer/Utils:
> 51 private types (ignoring 123 formal private types)
>   full types:
> 23 derived types (often from a type exported by a generic instance)
> 16 untagged record types
> 7 access types
> 5 tagged record types

Interesting. How do you deal with the need to free the memory associated with
private access types when the objects are destroyed? That almost always forces
me to use a controlled record type with a private type, even if the primary data
is an aceess value.

****************************************************************

From: Randy Brukardt
Sent: Wednesday, October 30, 2019  2:41 PM

...
> A "use type" is not currently required to use object.op notation for
> tagged types.

Agreed, and requiring it would defeat the primary purpose of allowing this in
the first place, eliminating the need for use clauses in many cases.

...
> The main point of this proposal,
> at least in my mind, is that something that is allowed on a tagged
> private type should also be allowed on an untagged private type.  That
> is, you shouldn't have to add "tagged"
> just to get to use a feature that really has little to do with being
> tagged.

Agreed, but you should need to add *something*. (And it's probably a mistake for
this to be allowed for all tagged types, but too late to fix.)

> As far as other comments, I don't understand the comment that some
> private types should allow this and others shouldn't.
> Almost by definition, a private type is an abstract data type, and
> hence it would seem that forcing users to distinguish between private
> types that allow object.op() and those that don't is an odd
> distinction to make.

Definitely not true. A private type is just private; it might not be an ADT
itself but rather a helper for some other ADT. A container cursor is a private
type, but no one should think of it as an ADT (and I don't think it makes sense
to use Object.Op() notation with it for that reason). Certainly, in any case
where a package has multiple private types, only one of them can be the ADT
(they all can't be the first parameter of subprograms!).

I wonder if most of the private types completed with access types in your list
are a form of reference (like a cursor) rather than an ADT, because memory
management of ADTs made out of bare cursors is impossible. (See my other query.)
In such a case, most subprograms using them won't be usable with Object.Op()
notation anyway [wrong parameter position], a use clause would be inevitable
(unless of course the *real** ADT allows Object.Op()).


Aside: Ada does not have good support for the case where an ADT has various
ancillary private types, as such types need to be derived/extended as a group.
I've figured out a workable solution for that (after years of trying), but the
ARG decided not to consider the probem further this go-round. Too bad, but
understandable.

If you care, the solution involves treating a pair (or more) of tagged types as
a single entity for the purposes of dispatching and derivation. If anyone is
interested, I can explain further.

****************************************************************

From: Bob Duff
Sent: Wednesday, October 30, 2019  3:06 PM

> Interesting. How do you deal with the need to free the memory
> associated with private access types when the objects are destroyed?

I don't know about ParaSail, but CodePeer allocates most heap memory in
"subpools".  These are not the subpools in Ada -- Ada hijacked the name to mean
something different. The CodePeer subpools have the property that all memory is
freed when the subpool is finalized.  That means that individual objects in
subpools need not be freed.

(Later):

> If you care, the solution involves treating a pair (or more) of tagged
> types as a single entity for the purposes of dispatching and
> derivation. If anyone is interested, I can explain further.

I had a similar idea some years ago.  I've never worked out the details.  Didn't
you write up an AI on that?  A reference to the AI would be good, or if that
doesn't exist, I'd be interested in you explaining further.

****************************************************************

From: Tucker Taft
Sent: Wednesday, October 30, 2019  3:24 PM

Here are the details on some of the access types used in the ParaSail front end:

   type Reclamation_Info_Ptr is access all Reclamation_Info_Record;
      --  this points to a table of pointers that is within a larger heap object
      --  that is recovered "en masse" on leaving the scope.

   type Stg_Rgn_Manager_Ptr is access Stg_Rgn_Manager_Type;
      --  These are not reclaimed, but rather there is a free list and they
      --  get reused that way

   type Hash_Table is access Hash_Table_Rec;
      --  There are explicit operations:
         generic
            with procedure Action (Key : Key_Type; Elem : Element_Type);
         procedure Iterate_And_Remove (Table : in out Hash_Table);
         --  Iterate over the table and remove each entry.
         --  Reclaim the storage occupied by the table.

         procedure Reclaim (Table : in out Hash_Table);
         --  Reclaim the storage occupied by the table.
         --  Leave the table empty.

   type Pair_Ref is access all Hash_Table_Entry;
        --  This is a short-lived type returned by a lookup routine, which in turn provides access
        --  to two other short-lived refs:
         function Key (Pair : Pair_Ref) return Key_Ref;
         --  Return R/O reference to key of pair

         function Element (Pair : Pair_Ref) return Element_Ref;
         --  Return R/W reference to element of pair

   type Read_Write_Mapping is access Read_Write_Mapping_Rec;
       --  These are explicitly managed

   type U_String is access U_String_Rec;
       --  These are used to point into a string table that is used by the lexer
       --  and grows as needed when new strings are seen, but never shrinks.

So bottom line is that there are many different strategies used.  Clearly
different situations call for different approaches.  I would claim that most if
not all of the above are reasonable, and I would imagine that the universe of
Ada programmers would have many many more situations where they have chosen, and
will continue to choose, to implement private types as access types.  I really
don't think we should be in the business of trying to force this to change.

This seems completely orthogonal with allowing the use of object.op().  Having
to change the package that defines the private type to allow the object.op()
notation seems inappropriate.  We certainly aren't forcing anyone to use it, so
presumably programmers will use it where it is appropriate, where their project
encourages it, or on every third Tuesday, should they so choose.  I have heard
many folks requesting that we don't tie this ability to a type being tagged, and
it seems just as bad to tie it to how a private type is implemented.

I would be interested in seeing cases where there is problematic ambiguity
introduced by simply allowing this notation on all private types, and on their
"full" types, when calling the primitive operations of the type that are defined
in the package spec.  This seems to be the simplest rule, and from what I can
tell, the chance of new ambiguity is very small.

Certainly for those those who choose to never use access types to implement
their private types, they won't have to worry about the problem!  And for those
that do, based on the anecdotal bit of research I have done, they won't run into
the problem at all.  The only place they could run into the problem are places
where they have visibility on the full type, and if that becomes a big burden,
well they can introduce a wrapper without altering the view seen by clients.
But *requiring* them to introduce such a wrapper when there is no problem to
begin with, seems diabolical... ;-)

****************************************************************

From: Randy Brukardt
Sent: Wednesday, October 30, 2019  4:19 PM

[List of uses.]

I'd argue that most of those aren't ADTs in the conventional sense and probably shouldn't be [and most likely can't be] using Object.Op() notation.
Allowing it for use in a few cases seems bad for readability of Ada code.

But I don't think there is much point in further discussion on this particular
point, as it's unlikely that we'll agree.

...
> I would be interested in seeing cases where there is problematic
> ambiguity introduced by simply allowing this notation on all private
> types, and on their "full" types, when calling the primitive
> operations of the type that are defined in the package spec.  This
> seems to be the simplest rule, and from what I can tell, the chance of
> new ambiguity is very small.
>
> Certainly for those those who choose to never use access types to
> implement their private types, they won't have to worry about the
> problem!  And for those that do, based on the anecdotal bit of
> research I have done, they won't run into the problem at all.  The
> only place they could run into the problem are places where they have
> visibility on the full type, and if that becomes a big burden, well
> they can introduce a wrapper without altering the view seen by
> clients.  But *requiring* them to introduce such a wrapper when there
> is no problem to begin with, seems diabolical... ;-)

This problem never was a usage problem (so far as I know), but rather a language
definition and implementation problem. The issue is that there is an infinite
number of possible implicit dereferences/'Access insertions if one allows access
types, and that isn't practically implementable. There has to be some
restriction on it to make it implementable.

I don't see that tying this to private types solves anything for the language
definition and implementation. After all, you can add a partial view to any type
by introducing a nested package, so every possibility exists (perhaps only in
ACATS tests, but the language definition and implementations both have to cover
all of the possibilities). You have all of the costs (essentially infinite for
some compiler designs; a compiler would have to introduce an artificial limit on
nesting) and you would be preventing people from using the notation in the most
likely cases. There doesn't seem to be any difference from introducing partial
views to the mix; you would get the same semantics from simply saying that it
only works on primitive operations (for any type) -- which I think has been the
only proposal from the beginning of time.

So I think you either have to allow it everywhere, or only allow it for types
that are specifically marked to allow it (tagged serves this purpose today).

I think there are many arguments against allowing it generally: it harms
readability (especially when mixed with other notation) - see Christophe's mail
for a few examples, the implementation and language definitional complexity is
quite high, and it seems abusive of the original Simula idea.

To fix the language definitional issues, you have to reign in the implicit
dereference and implicit 'Access cases (best to eliminate 'Access altogether and
only allow a single dereference), but that would be a wart and difference
compared to the tagged case. I suppose you could combine that with the partial
view rule for non-record types, but now you have a three sets of rules (tagged
types, untagged record types, untagged private type s) that are all subtly
different.

****************************************************************

From: Bob Duff
Sent: Wednesday, October 30, 2019  4:26 PM

> ...  Having to change the package that defines the private type to
> allow the object.op() notation seems inappropriate.

I'm with Tucker on that one.  Prefix notation is not some dangerous thing that
needs syntax along the lines of "Warning, Will Robinson!  I'm putting on my
HAZMAT suit so I can use prefix notation."  ;-)  Just use it wherever you see
fit.

As I said, I'd prefer not to fix the problem under discussion at all, but if
we're going to fix it, I agree with Tucker's approach.

****************************************************************

From: Tucker Taft
Sent: Wednesday, October 30, 2019  4:28 PM

> This problem never was a usage problem (so far as I know), but rather a
> language definition and implementation problem. The issue is that there is
> an infinite number of possible implicit dereferences/'Access insertions if
> one allows access types, and that isn't practically implementable. There has
> to be some restriction on it to make it implementable. ...

I'd like to see some concrete examples.  I really don't understand the problem
you are describing.

****************************************************************

From: Randy Brukardt
Sent: Wednesday, October 30, 2019  4:46 PM

It's in the minutes and/or e-mail discussion (I think it was covered at a
meeting, so it should be in the minutes) of the original Object.Op() proposal.
The AIs in question are AI95-0252-1 and AI95-0407-1. You can use the fancy
"voting" index to find the discussions.

I note from reading 407 itself that Y.P when Y is an access value and P has an
access parameter is actually interpreted as "Y.all'Access". Some Tucker Taft guy
made that statement in e-mail. I suspect that is the crux of the problem.

If you're still confused next week, I can do the research then, but I don't have
time now and I expect to be gone the rest of the week.

****************************************************************

From: Randy Brukardt
Sent: Wednesday, October 30, 2019  4:50 PM

> I'm with Tucker on that one.  Prefix notation is not some dangerous
> thing that needs syntax along the lines of "Warning, Will Robinson!
> I'm putting on my HAZMAT suit so I can use prefix notation."  ;-)
> Just use it wherever you see fit.

That's nasty, because it leads to all kinds of nonsense. But if we are going to
take that approach, then there is no reason to restrict it to private types (why
should private types be special???).

> As I said, I'd prefer not to fix the problem under discussion at all,
> but if we're going to fix it, I agree with Tucker's approach.

I don't, because Tucker's approach fixes none of the real problems we had with
access Object.Op, and simply requires people to put a private type on anything
they want to use with Object.Op(). If we are going to do it at all, it either
has to be restricted to record types (always) or allowed generally. I don't
think private types per-se have anything to do with it.

I'm rapidly moving to your primary position of doing nothing, because the
alternative is too much.

****************************************************************

From: John Barnes
Sent: Wednesday, October 30, 2019  5:17 PM

Having followed this discussion, I would do nothing.  I fear having to explain
curious stuff when I update my book.

****************************************************************

From: Jeffery R. Carter
Sent: Wednesday, October 30, 2019  3:49 PM

> ...
>> As far as what proportion of private types are completed with what
>> sort of full types, here are two separate examples:
>>
>> ParaSail:
>> 31 private types (ignoring 14 formal private types)
>>   full types:
>> 12 untagged record types
>> 12 access types
>> 4 derived types (typically derived from a private type exported by a
>> generic instance)
>> 2 protected types
>> 1 tagged record type

13 out of 31 (41.9%) have full types that are record types.

>> CodePeer/Utils:
>> 51 private types (ignoring 123 formal private types)
>>   full types:
>> 23 derived types (often from a type exported by a generic instance)
>> 16 untagged record types
>> 7 access types
>> 5 tagged record types

21 out of 51 (41.2%)

PragmAda Reusable Components:
28 non-formal private types of which 13 are visibly tagged (only to allow
  Object.Operation notation) Of the remaining 15:
1 modular type
3 extend Ada.Finalization.[Limited_]Controlled
9 untagged record types
2 derived from an untagged private type that is one of the 9 above

So 25 of 28 (89.3%)

Not surprising since many of the components are ADTs.

****************************************************************

From: Tucker Taft
Sent: Wednesday, October 30, 2019  8:37 PM

I looked at the AIs and the minutes, and didn't find anything more concrete that
there were complexities associated with supporting this on untagged types, and
we felt that tagged types were the most important, so we focused on those.

I believe time has shown that programmers like object.op() notation enough that
they will make a type tagged merely to allow for this notation.  I think even in
the ARG we have periodically said that we want a private type to be tagged not
so it can support extension, but rather so that it will support object.op()
notation.  We have had at least one suggestion to make Unbounded_Strings tagged
for this same reason.

To me the primary justification for this change comes from private types where
there really is no reason to make them tagged except to provide object.op()
notation.

In any case, I'll create some concrete examples to try to suss out what might be
the implementation or usability issues.  Without such examples I am struggling
to see the problem.

****************************************************************

From: Egil Harald Hoevik
Sent: Thursday, October 31, 2019  4:42 AM

First of all, add me to the "do nothing"-camp.

However, if this is going to be "fixed", why does it have to be a property of
the type? Can it be part of the visibility instead? We're already used to
controlling visibility based on what we need from a type with a use_type_clause,
like the following

-- make primitive operators visible
declare
   use type Ada.Strings.Unbounded.Unbounded_String;
   Foo, Foobar, Foobaz : Ada.Strings.Unbounded.Unbounded_String;
begin
   Foo := Ada.Strings.Unbounded.To_Unbounded_String("foo");
   Foobar := Foo & "bar"; -- primitive operator visible
   Foobaz := Ada.Strings.Unbounded.Append(Foo, "baz"); end;

-- make all primitive operations visible in addition to operators declare
   use all type Ada.Strings.Unbounded.Unbounded_String;
   Foo, Foobar, Foobaz : Ada.Strings.Unbounded.Unbounded_String;
begin
   Foo := To_Unbounded_String("foo"); -- primitive operations visible
   Foobar := Foo & "bar";
   Foobaz := Append(Foo, "baz"); -- primitive operations visible
end;


As far as I can tell (and I believe RM/AIs state as much), the above "use all
type" already makes everything visible that's needed  for prefix notation, so
why not just piggy-back on that feature? Something along these lines:

-- allow prefix notation
declare
   use all type Ada.Strings.Unbounded.Unbounded_String with Prefix_Notation;
   Foo, Foobar, Foobaz : Ada.Strings.Unbounded.Unbounded_String;
begin
   Foo := To_Unbounded_String("foo");
   Foobar := Foo & "bar";
   Foobaz := Foo.Append("baz"); -- prefix notation
end;


Now, I am not a compiler writer, so I probably don't see all the problems
associated with this solution, but I thought I'd throw it out there...

In any case, I'm in the "do nothing"-camp, and I believe the co-derivation
problem would be a far more usable thing to spend time on, especially when there
already seems to exist an actual implementable solution

****************************************************************

From: Jean-Pierre Rosen
Sent: Thursday, October 31, 2019  4:57 AM

> As far as I can tell (and I believe RM/AIs state as much), the above
> "use all type" already makes everything visible that's needed  for
> prefix notation, so why not just piggy-back on  that feature?

<tongue_in_cheek>
Because the proponents of this proposal want to have the benefits of a use
clause without having to write a use clause, 'cause use clauses are evil
(only if explicitely written).
</tongue_in_cheek>

****************************************************************

From: Richard Wai
Sent: Thursday, October 31, 2019  12:46 PM

Would we not perhapse just be able to modify 4.1.3 like this:

9.1/2: " view of a subprogram whose first formal parameter is of [a tagged]
{any} type or is an access parameter whose designated type is tagged:"

9.2/3: "The prefix [(after any implicit dereference)] shall resolve to denote an
object or value of {any type} [specific tagged type] T] or class-wide tagged
type T'Class. {If prefix denotes a view of an access type, the view shall be an
access to a tagged type or class-wide tagged type}. The selector_name shall
resolve to denote a view of a subprogram declared immediately within the
declarative region in which an ancestor of the type T is declared. The first
formal parameter of the subprogram shall be of type T, or a class-wide type that
covers T, or an access parameter designating one of these types. The designator
of the subprogram shall not be the same as that of a component of the tagged
type visible at the point of the selected_component. The subprogram shall not be
an implicitly declared primitive operation of type T that overrides an inherited
subprogram implemented by an entry or protected subprogram visible at the point
of the selected_component. The selected_component denotes a view of this
subprogram that omits the first formal parameter. This view is called a prefixed
view of the subprogram, and the prefix of the selected_component (after any
implicit dereference) is called the prefix of the prefixed view."

This avoids issues with implicit dereferencing, while also allowing for private
types completed as access types. This would probably require prefixed calls in
private part/body of the implementing package to use "Object.all.Operation", but
I think this would be rare enough to not really be much of a burden.

This approach would also bring with it the bennefits of not always having to use
"use". I think readability is not much harmed by this since A) this already
happens with tagged types, and B) the behaviour is simple and obvious, so one
knows where to look to find these operations since the type itself either
directly specifies the package it is defined in, or that is visable via "use" or
a subtype declaration.

I'd imagine that implementation would not be terribly hard, either.

I generally agree this would be well-received, and is probably worth pursuing,
however if it involves new syntax or reserved words, I think it would be far too
disruptive to be worth it.

One other much simpler idea I had was to possibly intruduce an aspect for tagged
types, such as perhapse "Static" which, if true, disallows extension or
dispatching calls. It would be for those who want the prefixed notation of
tagged types, without any of the run-time implications or OOP features.

****************************************************************

From: Tucker Taft
Sent: Thursday, October 31, 2019  2:12 PM

I'll have to look at your wording in more detail to understand the implications.

I don't see that the notion of a "static" tagged type addresses the same issue,
since there is a lot of existing code where the notation would be useful.  And
in what sense does the phrase "static tagged" suggest "object.op()" notation?

If we wanted to be excruciatingly explicit we could have "with
Allow_Prefixed_Calls => True", but since you don't need to do that for tagged
private, it seems odd to require it for untagged private.

In any case, I'll look at your wording suggestion.  I would rather have an
"English" non-lawyerly explanation of the rule before we get too deep into the
RM wording (and we were going to move back to the ARG mailing list for detailed
wording debates... ;-).

****************************************************************

From: Richard Wai
Sent: Thursday, October 31, 2019  2:24 PM

> I'll have to look at your wording in more detail to understand the
> implications.
>
> I don't see that the notion of a "static" tagged type addresses the
> same issue, since there is a lot of existing code where the notation
> would be useful.  And in what sense does the phrase "static tagged"
> suggest "object.op()" notation?

The idea here was more about avoiding the implications of using tagged types
just to get the object.op functionality - i.e. being able to do this is
situations where dynamic dispatching would be disallowed or unwanted.

> If we wanted to be excruciatingly explicit we could have "with
> Allow_Prefixed_Calls => True", but since you don't need to do that for
> tagged private, it seems odd to require it for untagged private.
>
> In any case, I'll look at your wording suggestion.  I would rather
> have an "English" non-lawyerly explanation of the rule before we get
> too deep into the RM wording (and we were going to move back to the
> ARG mailing list for detailed wording debates... ;-).

Fair enough! The basic idea is that selected components can resolve to a view of
a subprogram for any type, not just tagged types, except for access types, which
can only resolve to a view of a subprogram if they denote any tagged type.

****************************************************************

From: Tucker Taft
Sent: Thursday, October 31, 2019  2:48 PM

A goal is to avoid having to switch notation when you move a piece of code from
a place where it can only see the private type, to a place where it can see the
full type.  This comes up in things like default expressions of subprograms,
since they generally appear both in the visible part of a package as well as at
the point of the body.  Alternatively, you might have a piece of code that was
in an external package, and you decide to make the package into a child package,
and suddenly its body might now have visibility on the full definition of some
private type defined in its parent package.  This is why one of our "design
principles" is that anything you can do on the partial view of a type you should
be able to do also on the full view.

I have yet to find a serious problem with access types that are used to
implement a private type.  I hope to provide some examples shortly to help
understand the issue better...

****************************************************************

From: Tucker Taft
Sent: Thursday, October 31, 2019  3:28 PM

So here is an example, produced by simplifying a Vector abstraction in the
ParaSail front end:

generic
   type Element_Type is private;
package PSC.Vectors is

   --  Extensible Vector of elements

   Max_Elem_Index : constant := 1_000_000;
   --  Max number of elements in a vector

   type Elem_Index is range 0 .. Max_Elem_Index;

   No_Elem_Index : constant Elem_Index := 0;

   type Vector is private;
   --  Vector of elements

   function Num_Elements (Vec : Vector) return Elem_Index;
   --  Return number of Elements in Vec

   procedure Add_Element
     (Vec : in out Vector;
      Elem : Element_Type;
      Index : out Elem_Index);
   --  Add Element to Vector and return its Index

   function Nth_Element
     (Vec : Vector;
      Index : Elem_Index)
      return Element_Type;
   --  Retrieve Element at given index.

   procedure Set_Nth_Element
     (Vec : in out Vector;
      Index : Elem_Index;
      Elem : Element_Type);
   --  Overwrite nth element.

private

   type Vector_Rec;  --  defined below
   type Vector is access Vector_Rec;

   type Element_Array is array (Positive range <>) of Element_Type;

   type Vector_Array is array (Positive range <>) of Vector;
   --  For large Vectors, we break them up into sub-Vecs

   subtype Level_Range is Natural range 0 .. 3;
   --  Level indicates number of number of levels of indirection.

   type Vector_Rec (Level : Level_Range; Len : Natural) is
   --
   --  The single discriminant "Len" is being used
   --  in two different ways:
   --   If Level = 0, as the capacity of the Vec.
   --   If Level > 0, as the number of Sub_Vecs.
   record
      Count : Natural := 0;
      case Level is
         when 0 =>
            Elements : Element_Array (1 .. Len);
         when others =>
            Total_Capacity : Natural := 0;
            Sub_Vecs : Vector_Array (1 .. Len);
      end case;
   end record;

end PSC.Vectors;

-----

So, the question is where could problems arise, if we allow object.op() notation
on the private type Vector, including in places where we can see that Vector is
actually "access Vector_Rec"?

So given "V : Vector" is there danger in allowing "object.op()" references such
as "V.Num_Elements" or "V.Nth_Element(3)" in places where "ptr.component"
references such as "V.Level" and "V.Elements(4)" are also legal, thanks to
implicit dereference?  Or let us suppose that Vector_Rec is itself defined by
deriving from another private type, meaning it might have a set of operations
defined on that private type, in addition to potentially still having a visible
discriminant such as Level.  Does this impose problems?

Clearly there is overload resolution to be done.  We already have situations in
Ada 2012 where the prefix of a prefixed view can be overloaded, such as when we
have two overloaded functions that return different tagged types each with their
own set of operations and components.  We can also have overloaded functions
where one returns a tagged type T1 and one returns an access type designating a
tagged type T2, and we have to resolve calls on operations using prefixed
notation. That is, we might have F(...).Op1 where one F(...) might return T1 and
another might return access T2, and we need to resolve the meaning(s) of Op1 in
the two contexts.  There is already a "preference" for components over prefixed
views in 4.1.3(9.2), and this could generalize directly to giving a preference
to components of the designated type of an access type.

The above seems to indicate to me that allowing object.op() notation on access
types that implement a private type does not introduce any particularly new
overloading situations, and the implementor of the private type can deal
internally with any problems that they encounter in their own internal code that
bumps into unresolvable ambiguities.  It is never a problem for code that only
sees the private type, which is what the clients of the abstraction care about.

If there really is an implementation problem, it would be helpful to try to
express it based on the above example, or by showing another example that
illustrates the problem.  I am having trouble finding one.

****************************************************************

From: Jeffrey R. Carter
Sent: Friday, November 1, 2019  6:33 AM

> First of all, add me to the "do nothing"-camp.

The ARG has a history of rejecting proposals for which there is a workaround,
even when, unlike this proposal, things are very straightforward. As there is
a workaround for this (just make it visibly tagged), I expect the ARG to
reject this proposal as well.

****************************************************************

From: Tucker Taft
Sent: Monday, November 4, 2019  1:31 PM

>> First of all, add me to the "do nothing"-camp.
>
> The ARG has a history of rejecting proposals for which there is a
> workaround, even when, unlike this proposal, things are very
> straightforward. As there is a workaround for this (just make it
> visibly tagged), I expect the ARG to reject this proposal as well.

There are always workarounds, given that Ada 83 was pretty much Turing
complete.

The question is often more of balancing ease of use in a particular situation
vs overall language complexity.  In a case like this, we are effectively
simplifying the language, by removing a somewhat arbitrary restriction that
Object.Op() only works with tagged types.

****************************************************************

From: Arnaud Charlet
Sent: Tuesday, November 5, 2019  7:10 AM

I like Randy's suggestion to a KISS approach where only record types get the
object.operation (and private types marked record, which looks like a reasonable
extension of the current tagged private).

I disagree with Tuck on the requirement that this feature should require no code
change: this isn't what we see in practice from  GNAT users: people who use the
object.method notation are definitely willing to modify their code to do so,
proof is that these people today are precisely modifying their code to add
artificial 'tagged' to their record.

I don't think we should worry about access types at all, yes you can come up
with some real examples where having support for object.method on private access
types would be useful, that doesn't mean we should complexify the language to
allow it. After all object.method is a (very nice) convenience, and as I said
above, people are definitely willing to modify their code to take advantage of
this convenience, and in particular in the example you sent, wrapping the
private access type into a record would work just fine.

The Ada language is already complex enough, so let's be careful about
introducing extra complexity only when it is necessary.

****************************************************************

From: Tucker Taft
Sent: Tuesday, November 5, 2019  12:34 PM

> I like Randy's suggestion to a KISS approach where only record types
> get the object.operation (and private types marked record, which looks
> like a reasonable extension of the current tagged private).

Adding a new form of syntax to reveal that the full type is a record type is not
keeping it simple in my mind, particularly if the only reason to add "record" to
the private type declaration is so you can use object.op() notation.  The
original proposal required no syntax changes, and simply modified the wording of
4.1.3(9.2/3) to remove some restrictions. ...

****************************************************************

From: Arnaud Charlet
Sent: Tuesday, November 5, 2019  1:30 PM

Well, so far I have understood Randy's proposal at first sight while I still do
not understand your "original proposal", hence the fact that it's much more
complex to grasp for users (and for implementers? Can't tell until I understand
what you are proposing).

****************************************************************

From: Randy Brukardt
Sent: Tuesday, November 5, 2019  7:06 PM

> Adding a new form of syntax to reveal that the full type is a record
> type is not keeping it simple in my mind, particularly if the only
> reason to add "record" to the private type declaration is so you can
> use object.op() notation.  The original proposal required no syntax
> changes, and simply modified the wording of 4.1.3(9.2/3) to remove
> some restrictions.
> ...

I don't think that works, at least not the way you have described it. The
wording in question is:

The prefix (after any implicit dereference) shall resolve to denote an object or
value of a specific tagged type T or class-wide type T'Class. The selector_name
shall resolve to denote a view of a subprogram declared immediately within the
declarative region in which an ancestor of the type T is declared. The first
formal parameter of the subprogram shall be of type T, or a class-wide type that
covers T, or an access parameter designating one of these types. The designator
of the subprogram shall not be the same as that of a component of the tagged
type visible at the point of the selected_component. The subprogram shall not be
an implicitly declared primitive operation of type T that overrides an inherited
subprogram implemented by an entry or protected subprogram visible at the point
of the selected_component. The selected_component denotes a view of this
subprogram that omits the first formal parameter. This view is called a prefixed
view of the subprogram, and the prefix of he selected_component (after any
implicit dereference) is called the prefix of the prefixed view.

If one just removes the "tagged" from the "specific tagged type T", now you have
a lot of text talking about the "class-wide type T'Class" and "a class-wide type
that covers T", which is nonsense for an untagged type. I think we'd have to
have redo the wording to have separate wording for the T'Class case (or possibly
for untagged types) in order for the wording to make real sense. (Every time
I've tried to use wording that ignores issues like this, the group has required
me to change it before we put it into the Standard. You don't get a pass on
that.)

I'm sure we can come up with wording to allow prefixed notation on all types,
should we want to do that, but it's more complex than you are claiming.

****************************************************************

From: Randy Brukardt
Sent: Tuesday, November 5, 2019  7:43 PM

...
> I have yet to find a serious problem with access types that are used
> to implement a private type.  I hope to provide some examples shortly
> to help understand the issue better...

As previously noted, I see no value to restricting the use of access types to
certain contexts; if we allow access types anywhere, all of the complications
occur, so there is no reason to restrict the usage. It's all or nothing for
access types.

Here's some interesting points noted during my research:

From AI-00252:

We originally generalized this to support non-tagged types, but the added
complexity this brought to handling access types seemed more than the
anticipated benefit, since we would have to consider primitives of the access
type itself as well as those of its designated type.

---

From the minutes of the 13th ARG meeting (Leuven, Belgium, May 2001):

Erhard starts a full example, to better understand how access types work in this
context:

package P1 is
   type T1 is ...
   procedure Process (P : access T1);
   type AT1 is access T1'Class;
   procedure Process2 (P : AT1);
end P1;

with P1;
package P2 is
   type T2 is new P1.T1 with ...;
   procedure Process (P : access T2);
   type AT2 is access T2'Class;
end P2;

declare
   Obj : At2;
begin
   Obj.Process;  -- This works.
   Obj.Process2; -- This does not work. (Different access types.) end;

[Randy's 2019 note: I note that the resolution of anonymous access parameters
and parameters of named access types is wildly different if we allow prefixed
views of access types; that's illustrated by this example. Yuck.]

...

Tucker jokes that we don't need infix notation at all if we have this
notation: If A is an Integer, it becomes possible to write:

   if A."-"."/="(3)."not" then ...

[Translation: if not (-A /= 3) then...] Someone notes that we now can have
obfuscated Ada contests, just like C-family languages. This closes the
discussion with laughter.

[Randy's 2019 note: Great. What happened to readability as a concern??]

---

From the minutes of the 14th ARG meeting (Columbia, Maryland, October 2001 [in a
hotel we shared with the Dallas Cowboys, in the area to play the Ravens; I got
run over by Jerry Jones' bodyguards when returning from dinner one night]):

Why do we need this feature?

Proponents say it eliminates the need for using the package name for the
operations, which is an annoyance to many coming from other OOP languages. It
could be more attractive to newcomers.

Those opposed say is that you can get around this problem with use clauses.
Can we afford the ARG time and implementation expense for this feature?

A straw vote to continue work on this AI passes 6-3-1.

Various simplifications are suggested. Erhard suggests not worrying about named
access types. Tucker wonders if restricting this feature to just composite types
would help. Erhard suggests going further and restricting it to tagged types.
Does disallowing elementary and arrays as the prefix help? (That is, the prefix
would be restricted to things for which dot already is allowed.) The group
expresses lukewarm approval for this improvement.

Bob worries that private completed by integer could use this notation outside
the package, but not inside, which seems weird. It is left to Tucker to come up
with a new proposal.

[Randy's 2019 note: It appears from this that the restriction to tagged types
only occurred mainly as an olive branch to those that didn't want to do it at
all.]

---

From the minutes of the 19th ARG meeting (Toulouse, France, June 2003):

...

Jean-Pierre is concerned that this makes it harder to check that all subprograms
are identified by a fully expanded name. In Ada 95, a search for use is
sufficient to insure that, and that is very easy to find.

Randy points out that the inheritance of OOP breaks this anyway -- the routine
is often not (explicitly) declared in the package to which the expanded name
refers. So this doesn't make it much harder to find the declaration (you have to
search the same set of packages either way). Erhard says that this was even true
in Ada 83 with derived type inheritance. Pascal suggests using an ASIS tool to
check this (and other stylistic rules), not grep.

Jean-Pierre is not convinced. He fears that high-integrity users might be
alienated. Alan points out that high-integrity users don't use tagged anyway -
they can grep for tagged if they want (and thus avoid this new feature
completely).

[Randy's 2019 note: expanding the notation to all types would definitely make it
harder to avoid it for those that want to do that. Not sure if this is a real
concern today (J-P's AdaControl tool probably can find all of these uses).]

****************************************************************

From: Randy Brukardt
Sent: Tuesday, November 5, 2019  9:23 PM

...
> I have yet to find a serious problem with access types that are used
> to implement a private type.  I hope to provide some examples shortly
> to help understand the issue better...

My research and premise is described in my other note. I'm only considering the
effects of allowing prefixed notation on all types (as opposed to only record
types or the existing only tagged types) as there appears to be  no benefit to
any additional complication.

The current Ada 2012 rules requires the following tree for resolving selected
notation when the prefix resolves to an object. (Note: This tree is for each
such prefix resolution; a prefix can resolve to multiple things, but each one
has to be resolved by this tree. Also note that I'm ignoring the "current
instance" here; it is a type name but it is treated like an object for the
purposes of resolution.) This tree makes explicit the resolution of implicit
dereferences.

For a prefix that is an object of specific type T or class-wide type
T'Class:

1) If T is a non-array composite type or an access to a non-array composite
   type, then the selector can be a discriminant of T (or the designated type of
   T).
2) If T is a record type or an access to a record type, then the selector can be
   a (regular) component of T (or the designated type of T).
3) If T is a task or protected object, or an access to a task or protected
   object, then the selector can be an entry or protected subprogram of T (or
   the designated type of T).

If any of the above work, do not try any prefixed views. [Aside: This isn't
quite accurate for (3); not worth figuring out the exact rule here, it's not
relevant to my point.]

Let T-Ancestor be the set of declarative regions where an ancestor of T is
declared, and T-Region be the (single) declarative region where T is declared.
Then:

4) If T is a tagged type, then the selector can be a subprogram with the first
   parameter having type T or a classwide type that covers T declared in
   T-Ancestor (Obj.Op(...)).
5) If T is a tagged type, then the selector can be a subprogram with the first
   parameter being an access parameter designating type T or of a classwide type
   that covers T declared in T-Ancestor (Obj'Access.Op(...)).
6) If T is an access to a tagged type TT, then the selector can be a subprogram
   with the first parameter having type TT or a classwide type that covers TT
   declared in TT-Ancestor (Obj.all.Op(...)).
7) If T is an access to a tagged type TT, then the selector can be a subprogram
   with the first parameter being an access parameter designating type TT or of
   a classwide type that covers TT declared in TT-Ancestor
   (Obj.all'Access.Op(...)).

----

Note that for any particular subprogram and type of object prefix, only one of 4
through 7 is possible. This makes it possible to include information about the
(only) possible prefixed view inside of the subprogram declaration in a compiler
symbol table. I believe that information is crucial to providing an efficient
implementation of prefixed views (certainly in Janus/Ada, can't say for sure
about other implementations). It's too expensive to do a search for a type
declaration in each scope to determine if the subprogram is declared in the
right place.

[Disclaimer: I haven't tried to implement prefixed views in Janus/Ada yet - it's
possible the implementation I currently have in mind won't work for some reason.
It's also possible that there is some other implementation that's better,
although that's unlikely.]

Note that for the case of a specific parameter type, we can use T-Region rather
than T-Ancestor (no operation of type T can be declared in an ancestor's region,
unless the ancestor's region is the same as T's region - which is not
interesting). That's a simplification. Note that these rules do not require the
operations to be primitive -- this matters for types declared in subprogram and
blocks (new operations aren't primitive, but are still accessible via Obj.Op).
None of this is particularly relevant to my point, but they do affect the
implementation.

Now, if we allow any type here, then we have to separate the class-wide rules
from the other rules, giving something like:

4) For any type T, then the selector can be a subprogram with the first
   parameter having type T declared in T-Region (Obj.Op(...)).
5) For any type T, then the selector can be a subprogram with the first
   parameter being an access parameter designating type T declared in T-Region
   (Obj'Access.Op(...)).
6) If T is an access to a type TT, then the selector can be a subprogram with
   the first parameter having type TT declared in TT-Region (Obj.all.Op(...)).
7) If T is an access to a type TT, then the selector can be a subprogram with
   the first parameter being an access parameter designating type TT declared in
   TT-Region (Obj.all'Access.Op(...)).
8) If T is a tagged type, then the selector can be a subprogram with the first
   parameter having a classwide type that covers T declared in T-Ancestor
   (Obj.Op(...)).
9) If T is a tagged type, then the selector can be a subprogram with the first
   parameter being an access parameter designating a classwide type that covers
   T declared in T-Ancestor (Obj'Access.Op(...)).
10) If T is an access to a tagged type TT, then the selector can be a subprogram
    with the first parameter having a classwide type that covers TT declared in
    TT-Ancestor (Obj.all.Op(...)).
11) If T is an access to a tagged type TT, then the selector can be a subprogram
    with the first parameter being an access parameter designating a classwide
    type that covers TT declared in TT-Ancestor (Obj.all'Access.Op(...)).

For non-access types, the same holds true: for any particular subprogram and
type of object prefix, exactly one of these cases is possible.

For access types, though, at least two of the cases is possible for a single
subprogram (4 and 6 or 5 and 7). The good news is I can't find any way to have
more than two possible, so it seems practical to retain the symbol table
information.

Note that this just makes the implementation semi-practical. It still would be
many times more expensive than other resolution (I'd guess 5-50 times in usual
cases, but that depends on how many declarations of the selector exist anywhere
in the partition. For a selector like Next, the expense would be on the high
end). I can't say if this is significant, as resolution is not noticeably
expensive in Janus/Ada today, so it's possible that making it much slower on
selected components will not matter. (And I note that this some of this expense
occurs from allowing prefixed notation at all - allowing all types will clearly
makes it worse but adding subprograms to the mix at all adds a lot of overhead
to resolving selected components.)

BTW, this is a new and different implementation approach. The original one would
have been extremely expensive if access types were included, but it was very
expensive even for tagged Obj.Op (only being practical because it didn't add
cost to the vast majority of selected components - that wouldn't be true for
this new approach but that doesn't matter since pretty much everything would
allow it).

Anyway, the bottom line here is that I don't think (assuming the *exact* set of
rules I gave above) that the implementation of unrestricted Obj.Op is
dangerously more expensive than the current rules.


P.S. For future reference (mostly mine), the implementation I have in mind is
essentially that for every interesting prefix, one has to look at the entire
list of declarations with a particular defining identifier. Whether it makes
sense to prune that list beforehand (which we do for other such lists using
visibility -- which doesn't come into play here) is unclear. For many possible
selectors, the pruned list and the entire list will be lengthy and identical
(think any subprogram that exists in every container, like Next; a program using
containers extensively could have dozens of instances and all of them would
provide a candidate). As such, creating a separate pruned list might take longer
than any time that could be saved. OTOH, because of overriding it's possible
that there would be lots of irrelevant decls around for many selectors. Probably
will have to test some of these cases to see which approach is better.

Either way, each subprogram decl would need an indication of the kind of
prefixed notation allowed (if any) and the associated type (there can be only
one for each subprogram). Two such indications would be needed, one for direct
use and one for implicit dereferences. These would be used to make testing
whether a particular prefix could work relatively cheap (and if the possibles
list is actually created, to eliminate items that can't work for any possible
prefix). The alternative of searching through the containing scope to see if the
type is also declared there would be crazy-expensive.

The class-wide cases are more complex but follow the same pattern (the
information would allow prefix notation if the type is that of any ancestor).

Of course, one also has to figure out how to generalize the existing resolution
of names to support the effectively shortened parameter list of a prefixed view.
Currently, everything is tied to a subprogram declaration which presumably
wouldn't exist (with the correct number of parameters) for a prefixed view.
There is also going to be an issue figuring out in the later Check pass when to
insert .all or 'Access or both.

These latter factors is why it's going to take a lot of motivation to want to
dig into this resolution code and related code to try to implement prefixed
views.

****************************************************************

From: Randy Brukardt
Sent: Tuesday, November 5, 2019  9:50 PM

...
> package P1 is
>    type T1 is ...
>    procedure Process (P : access T1);
>    type AT1 is access T1'Class;
>    procedure Process2 (P : AT1);
> end P1;
>
> with P1;
> package P2 is
>    type T2 is new P1.T1 with ...;
>    procedure Process (P : access T2);
>    type AT2 is access T2'Class;
> end P2;
>
> declare
>    Obj : At2;
> begin
>    Obj.Process;  -- This works.
>    Obj.Process2; -- This does not work. (Different access types.) end;
>
> [Randy's 2019 note: I note that the resolution of anonymous access
> parameters and parameters of named access types is wildly different if
> we allow prefixed views of access types; that's illustrated by this
> example.
> Yuck.]

To expand on this a bit:

Ada 2007 and Ada 2012 both added some implicit conversions for anonymous access
types, so this effect already occurs with regular calls. For example, using the
declarations above and assuming universal Obj.Op:

declare
    Obj2 : At2 := ...;
begin
    Obj2.Process;  -- OK. (Actually Obj2.all'Access.Process).
    Obj2.Process2; -- Nope.
                   -- Note: 'Access only tried for anonymous access params
    Process (Obj2); -- OK.
    Process2 (Obj2); -- Nope.
end;

However, the prefixed view rules aren't the same as the implicit conversion
rules. So there are anomolies:

with P1;
package P3 is
   type T3 is new P1.T1 with ..
   procedure Process (P : access T3);
   type GAT3 is access all T3'Class;
   procedure Process3 (P : GAT3);
end P3;

declare
    Obj3 : access P3.T3 := ...;
begin
    Obj3.Process;  -- OK. (Actually Obj3.all'Access.Process).
    Obj3.Process3; -- Nope.
    Process (Obj3); -- OK.
    Process3 (Obj3); -- OK by 8.6(26.1/3)
end;

Note that there is no expected type in 4.1.3, so the implicit conversions of 8.6
don't apply to prefixed views (and they would make some prefixes ambiguous if
applied generally, so we don't want that).

I think that there is also a difference with "covers", which goes mostly one way
for 'Access but goes both ways for implicit conversions. That could cause a
similar anomaly. The extra rules for discriminants for 'Access might also cause
an anomoly; I didn't try to work out if that is possible or not.

These are definitely possible to fix, but would add additional complication both
for the language description and for implementations. And if we don't fix them,
they'll cause complication for users due to the subtle differences. Yuck either
way.

****************************************************************

From: Tucker Taft
Sent: Wednesday, November 6, 2019  7:15 AM

Wow, lots of data!  Thanks, Randy, for assembling all of this.  It will take a
while to study all of this.

****************************************************************

From: Tucker Taft
Sent: Wednesday, November 6, 2019  7:26 AM

> Anyway, the bottom line here is that I don't think (assuming the
> *exact* set of rules I gave above) that the implementation of
> unrestricted Obj.Op is dangerously more expensive than the current rules.

I am glad that your "bottom line" agrees with my much less in-depth sense of
the situation.

****************************************************************

From: Raphael Amiard
Sent: Wednesday, November 6, 2019  5:01 PM

Personally I think object.op should be allowed on every type (even access types).
Ambiguities should be signaled to the users by the compiler if/when they arise.

I see no reason of restricting ourselves here, since Ada had the good taste of
standardizing the notion of primitive operation since the first iteration of
the language. What would be the arguments against that?

****************************************************************

From: Randy Brukardt
Sent: Wednesday, November 6, 2019  5:39 PM

>I see no reason of restricting ourselves here, since Ada had the good taste
>of standardizing the notion of primitive operation since the first iteration
>of the language. What would be the arguments against that?

Not interested in arguing either way here, but I wanted to caution you that
Obj.Op has little to do with primitive operations. Nothing in the Obj.Op rules
ever talk about primitive operations, for instance, rather talking about the
place where a subprogram is declared. (I didn't remember this until my research
yesterday.)

The difference is significant for a type declared in a declarative_part of a
subprogram or a block: subprograms declared with the type are eligible for
Obj.Op notation, but they are not primitive and are not inherited by the
derivation of a descendant type.

One effect of this is that a compiler implementation has to use different
rules/data for determining whether a subprogram is primitive vs. whether it can
be used in prefixed (Obj.Op) notation.

As I noted in one of my messages yesterday, the rules for implicit conversions
and for the resolution of a prefix also differ in subtle ways, so for a
one-parameter procedure, Obj.Op might be legal and Op(Obj) not legal, or vice
versa. I'd guess that these cases are quite minimal for tagged types, but they
become more visible when one allows access types.

Net effect: this is not as simple as it seems. (Either for the language
definition, or for an implementation.) Which makes it come down to the normal
allocation of resources question: is it worth the allocation of X hours of work
to this issue rather than other issues?

****************************************************************

From: Tucker Taft
Sent: Wednesday, November 6, 2019  7:04 PM

> Not interested in arguing either way here, but I wanted to caution you
> that Obj.Op has little to do with primitive operations. Nothing in the
> Obj.Op rules ever talk about primitive operations, for instance,
> rather talking about the place where a subprogram is declared. (I
> didn't remember this until my research yesterday.)

That is a bit misleading.  If the type is declared in the visible part of a
package, then it is synonymous with being primitive, when we are talking
about untagged types (since they don't have class-wide versions).  Frankly,
I would be happy to limit it to types declared in the visible part of a
package, if untagged.  Going beyond that seems unnecessary, especially if
it introduces any new complexity.

****************************************************************

From: Randy Brukardt
Sent: Wednesday, November 6, 2019  7:50 PM

The existing rules for tagged types have this effect (if a tagged type is
declared somewhere other than a package), so an attempt to restrict it to only
types declared in a package would be incompatible with the Ada 2012 definition.

And, as Raphael said, it's hard to justify adding restrictions to the use of
Obj.Op, especially if they are different from the tagged case for no particular
reason. I don't see any significant implementation difficulty with the current
definition, it just is *new* -- one can't reuse anything existing.

I personally don't find this important enough for the work, but if we're going
to do it, we should do it with as little restriction as possible: preferably so
that Op (Obj) and Obj.Op both or neither will resolve (for an Obj that is not
overloaded).

****************************************************************

From: Joey Fish
Sent: Wednesday, November 6, 2019  7:24 PM

I was reminded a bit about something Dmitry brings up from time to time:
multiple-dispatch. It seems to me like there's a bit of overlap (with that idea,
and dot-notation) as the Ada 2005 standard designated [implicitly] a
distinguished parameter/result in a subprogram for Object.Operation syntax.

While it would be decried as ugly and unuseful/verbose of something like

  Function Operation( X : Type_1; Y : Type_2 ) return Type_3

    with Distinguished_Parameter => Y;

it could be used to indicate that Y_Thing.Operation (X_Thing) is how the
subprogram's dot-notation is intended to be used; and if we ever DO implement
multiple-dispatch, or multiple-dispatching parameters, it could also indicate a
precedence. [I thought that there was a precedence issue with multiple-dispatch,
but that might have actually been something on multiple-inheritance I'd read
somewhere.]

****************************************************************

From: Tucker Taft
Sent: Wednesday, March 11, 2020  8:02 PM

I have been reviewing a submission to Ada-Europe and they make a pretty 
compelling argument for adopting the "Uniform Function Call Syntax" (of the 
"D" language, aka "UFCS") that was mentioned earlier by Luke Guest in comments 
on this AI.  If we are considering generalizing prefix notation for calls, 
where F(A, B, C, D) is equivalent to A.F(B, C, D) in some circumstances, you 
could argue that it should be equivalent in *all* circumstances.

What the D language does (see 
https://tour.dlang.org/tour/en/gems/uniform-function-call-syntax-ufcs) is when 
it sees A.F(B, C, D) it first does a lookup of F in the scope associated with 
A, and if that doesn't work, it treats it like a regular call on some visible 
"F".  This approach becomes very handy when you are trying to chain together a 
series of calls, as happens with certain kinds of pipelines or sequences of 
operations that are being combined.  The other option is to use an explicit 
operator like "&" but then you can get into a combinatorial explosion problem,
because the type returned by one call need not be the same as is the 
operand/result of the next call.  The other key point is that the operations 
you want to call may be the result of instantiating a generic, and are 
generally *not* primitives of the type of the first parameter, so requiring 
that the operation be declared in the "scope" associated with the prefix 
doesn't help.

For what it's worth, ParaSail follows this UFCS principle, though I didn't 
know it had a name... ;-)  And ParaSail actually always looks in the scope 
associated with the types of all of the parameters (and the result, for that 
matter), so pulling one parameter out front actually doesn't change the 
lookup process at all.

In any case, more food for thought on this AI.  Probably need to assign it to 
someone if we want it to progress further.  But I guess we might want to talk 
about it in an ARG meeting first, to establish intent.

****************************************************************

From: Randy Brukardt
Sent: Wednesday, March 11, 2020  8:46 PM

> What the D language does (see
> https://tour.dlang.org/tour/en/gems/uniform-function-call-synt
> ax-ufcs) is when it sees A.F(B, C, D) it first does a lookup of F in 
> the scope associated with A, and if that doesn't work, it treats it 
> like a regular call on some visible "F".

Could you explain how this differs from what is currently proposed? "Some
 visible F" doesn't make much sense, since that would seem to ignore the first 
parameter altogether.

Perhaps you mean "some visible F with a first parameter of type A"? (That 
would effectively drop the "primitiveness" requirement on the function, and 
require looking through the universe for a matching function. Of course, 
"looking through the universe" is something that compilers are prepared to do 
anyway, so it might not be terrible.)

However, that would be rather incompatible with the existing rules (much more 
so than any of the previous proposals). There are a lot of routines in the 
universe, and obsolete routines with matching names are not uncommon 
(certainly not in my code).

For example, Claw has a bunch of classwide routines like Move and Resize that 
were (functionally) replaced in later versions with primitive versions (so 
they could be overridden). The original versions (in a separate package) just 
make a dispatching call to the primitive versions now; we kept them to avoid 
breaking all existing code (we did mark them as obsolete).

That package has lots of commonly used routines, so it is very commonly 
withed. The net effect would be that every prefix call to Move or Resize would 
be ambiguous, and qualification could not fix that (since the routines have 
identical profiles). That would mean that one would end up having to fall back 
on use clauses or lengthy expanded names for almost any call to these commonly 
used routines. (I note that package use clauses probably don't work on this 
pair of routines either, because the profiles are functionally identical.)

I really dislike the idea of have reasonable scenarios where the use of prefix 
notation is impossible for an ADT (my preference is either to write all prefix 
calls or all normal calls -- switching back and forth is just confusing to 
readers). And even worse is scenarios where prefix calls are legal in Ada 2012 
that cannot be made legal in Ada 202x without a significant rewrite. So I 
suspect that this would have been a good idea, but it is too late for Ada -- 
it will have to wait for an Ada successor language.

****************************************************************

From: Richard Wai
Sent: Wednesday, March 11, 2020  9:07 PM

I honestly cannot see the value of the trouble. Prefixed calls are just 
syntactic sugar. Prefixed calls for tagged types is about invoking an 
operation ("method") on an object in the OOP sense, so it seems natural in 
that context. But calling a subprogram with some parameters is simply a 
different paradigm. 

In short, something like My_String.Put_Line is a evil, and shouldn't be 
encouraged.

I'm with Randy on most of this, except for the Ada successor thing.

****************************************************************

From: Tucker Taft
Sent: Wednesday, March 11, 2020  9:26 PM

>> What the D language does (see
>> https://tour.dlang.org/tour/en/gems/uniform-function-call-synt
>> ax-ufcs) is when it sees A.F(B, C, D) it first does a lookup of F in 
>> the scope associated with A, and if that doesn't work, it treats it 
>> like a regular call on some visible "F".
> 
> Could you explain how this differs from what is currently proposed? 
> "Some visible F" doesn't make much sense, since that would seem to 
> ignore the first parameter altogether.

I should have said "directly visible F."  That is, you fall back on normal 
overload resolution and visibility if there is no "F" that is a primitive 
or class-wide operation on A.  But you get the advantage of writing:

A.F(...).G(...).H(...)

Presuming F is a primitive of A or directly visible, G is a primitive of 
the result of F, or directly visible, and H is a primitive of the result of G,
or directly visible.

In other languages these pipelines tend to be written as:

    A.F(.....).
       G(.....).
       H(.....)
 
> Perhaps you mean "some visible F with a first parameter of type A"? 
> (That would effectively drop the "primitiveness" requirement on the 
> function, and require looking through the universe for a matching 
> function. Of course, "looking through the universe" is something that 
> compilers are prepared to do anyway, so it might not be terrible.)

No, F would have to be directly visible if it were not a primitive/class-wide 
op of A.

> ...

Anyway, as I said, this was food for thought. ;-)

****************************************************************

From: Yannick Moy
Sent: Thursday, March 12, 2020  3:50 AM

I think your proposal makes a lot of sense. And your later clarification about 
the rule ("primitive or directly visible operation") addresses the concerns of 
Randy.

>The other option is to use an explicit operator like "&" but then you can get 
>into a combinatorial explosion problem, because the type returned by one call 
>need not be the same as is the operand/result of the next call.  

Can you explain the above sentence?

>In any case, more food for thought on this AI.  Probably need to assign it to 
>someone if we want it to progress further.  But I guess we might want to talk 
>about it in an ARG meeting first, to establish intent.

The alternative is to open an RFC on https://github.com/AdaCore/ada-spark-rfcs 
for discussion.

****************************************************************

From: Tucker Taft
Sent: Thursday, March 12, 2020  2:43 PM

>> The other option is to use an explicit operator like "&" but then you can 
>> get into a combinatorial explosion problem, because the type returned by 
>> one call need not be the same as is the operand/result of the next call.  

>Can you explain the above sentence?

This was pretty specific to the kind of streaming framework they were trying 
to create in this Ada-Europe paper, and I don't think I explained it very 
well.  They indicated that if some of the elements of the stream were 
collecting or reducing their input, then their output might be a different 
type than their input, and you would need a bunch of different "&" operators
if that is how you construct the pipeline.

The paper should show up at Ada-Europe at some point.

>> In any case, more food for thought on this AI.  Probably need to assign it 
>> to someone if we want it to progress further.  But I guess we might want to
>> talk about it in an ARG meeting first, to establish intent.

> The alternative is to open an RFC on https://github.com/AdaCore/ada-spark-rfcs 
> for discussion.

For now I'll leave the discussion here on Ada-Comment, since it is very much 
still in the brainstorming phase.

****************************************************************

From: Emmanuel Briot
Sent: Thursday, March 12, 2020  2:46 AM

I am not sure how much I would use this extended notation myself, but the
UFCS page for D shows an interesting example, which roughly translated
to Ada would be:

    A : constant Int_Array := (….)
    B : constant Int_Array := Evens (Divide (Multiply (A, 10), 3));

which is kind of hard to read: you have to read from the inside out to understand
the order of operations, and the argument for divide is quite far away. Instead,
Tuck’s proposal would allow us to write something like:

    A : constant Int_Array := (….)
    B : constant Int_Array := A.Multiply (10).Divide (3).Events;

which fixes both readability issues, something Ada designers are typically
fond of, and rightly so.



Another compelling argument, already mentioned by Tuck, is the case of
generic instances. Those can never be primitive operations, so can never be
called with the prefix notation

    type C is tagged private;
    function Op is new Some_Generic (C);

    C.Op;   —  not possible in Ada12, not a primitive operation

In this case, Op is defined in the same scope as C, so if we do not like Tuck’s
full idea, we could also restrict the search for matching subprograms to those
declared in the same scope as the type, even if they are not primitive operations.
That would also work for the first example above, assuming Divide, Multiply and
Events are declared in the same scope as Int_Array, which in practice is often
the case.

****************************************************************

From: Tucker Taft
Sent: Thursday, March 12, 2020  4:23 PM

> In this case, Op is defined in the same scope as C, so if we do not 
> like Tuck’s full idea, we could also restrict the search for matching 
> subprograms to those declared in the same scope as the type, even if they 
> are not primitive operations.
> That would also work for the first example above, assuming Divide, 
> Multiply and Events are declared in the same scope as Int_Array, which 
> in practice is often the case.

For what it is worth, in the Ada-Europe paper, they were using generics that 
were unlikely to be instantiated "inside" the right scope, so this wouldn't 
solve their problem.

****************************************************************

From: Randy Brukardt
Sent: Thursday, March 12, 2020  5:13 PM

... 
> Another compelling argument, already mentioned by Tuck, is the case of 
> generic instances. Those can never be primitive operations, so can 
> never be called with the prefix notation
> 
>     type C is tagged private;
>     function Op is new Some_Generic (C);
> 
>     C.Op;   -  not possible in Ada12, not a primitive operation

???

Assuming this is in a package spec and that Op has a parameter of type C, and 
that an object of C is used as the prefix ;-), Op *is* a primitive operation, 
and this is legal in Ada 2012. 3.2.3(6) says "any subprograms ...
declared immediately in the same package specification". It doesn't say 
anything about the form of the declaration (renamings are also primitive -- 
that's a bug IMHO but it is what it is).

I'd guess you meant something more like:

    generic
       type Priv is private;
    package Gen is
       function Op (A : Priv) return Natural;
    end Gen;

    type C is tagged private;
    package Inst is new Gen (C);
    Obj : C;

    N : Natural := Obj.Op;  -- not possible in Ada12, not a primitive operation

But this remains illegal in Tucker's proposal unless a use clause is given on 
Inst.

Which of course brings up a quibble here: the need to have use clauses to 
allow this notation means that this extension is not of much value to the 
use-adverse (as those people never have much of anything directly visible).
Not sure how useful such a notation would be (while the tagged prefix 
notation eliminates both the need for use clauses AND the need to figure out 
exactly where the operation you need is declared -- a substantial reason that
it was accepted in the first place). ("use all" makes the same operations 
visible that are directly available in prefix notation anyway, so it doesn't 
help.)

****************************************************************

From: Jean-Pierre Rosen
Sent: Friday, March 13, 2020  1:13 AM

> But you get the advantage of writing:
> 
> A.F(...).G(...).H(...)
> 
> Presuming F is a primitive of A or directly visible, G is a primitive 
> of the result of F, or directly visible, and H is a primitive of the 
> result of G, or directly visible.
> 
> In other languages these pipelines tend to be written as:
> 
>     A.F(.....).
>        G(.....).
>        H(.....)

Which I feel is much more readable. I usually first concentrate on WHAT is
returned before caring about HOW it is obtained. So starting with the 
outermost call makes sense. Oh well, readability is a matter of taste, and how 
you've been educated in the first place...

> I am not sure how much I would use this extended notation myself, but 
> the UFCS page for D shows an interesting example, which roughly 
> translated to Ada would be:
> 
>     A : constant Int_Array := (....)
>     B : constant Int_Array := Evens (Divide (Multiply (A, 10), 3));

No kidding! Any sane Ada programmer would redefine operators and write:

>     A : constant Int_Array := (....)
>     B : constant Int_Array := Evens ((A * 10) / 3);

Which is even better than UFCS. But maybe D is lacking operator redefinition? 
(I didn't check)

****************************************************************

From: Yannick Moy
Sent: Thursday, March 12, 2020  3:50 AM

****************************************************************

Questions? Ask the ACAA Technical Agent