-
Notifications
You must be signed in to change notification settings - Fork 30
Subprogram-valued Aspect Declarations #175
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
af115f7 to
fa84d22
Compare
fa84d22 to
6258c6f
Compare
| Item : in T); | ||
| end record; | ||
| Unfortunately, the direct attribute definitions inside a scoped record clashes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Old terminology "direct attribute definitions". I should keep it consistent and use the one Tucker suggested: "subprogram-valued aspect declarations". I'll fix that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you point me to the discussion where the new name appeared?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The discussion happened internally with Tucker, I'll point out here the important bits:
- Any attribute that you can specify with an attribute definition clause is considered an "aspect" with the same name, so using the term "aspect" instead of "attribute" or "attribute or aspect" is correct ARM terminology; RM 13.3(5/3).
- Since the proposal aims primarily at aspects/attributes that are in the form of subprograms, subprogram should be in the name.
Therefore: subprogram-valued aspect declaration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, thanks for the explanation.
Personally, I associate "aspect" to the idea that the specification is syntactically together with the entity definition, as opposed to pragmas and attribute definitions that can happen later
where the freezing rules permit. So the declarations introduced by this RFC would rather fit into the second category, but it's not a big deal.
Another nit is that "declarations" sounds as bit like we're declaring new aspects, whereas we are specifying values of aspects predefined by the language. So I would maybe have expected "specifications" instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggested "declaration" because unlike aspect specifications or attribute definitions, this is actually declaring a subprogram, and it requires an associated body to be provided. So that makes it pretty different from any other "specification" or "definition". Also, generally "specification" is part of a larger syntactic construct, whereas "declarations" tend to be a complete syntactic construct, ending with a ";".
| Under the hood, the compiler expands this subprogram declaration into an | ||
| *internal* subprogram declaration and corresponding aspect specification. The |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Under the hood, the compiler expands this subprogram declaration into an
internal subprogram declaration and corresponding aspect specification
I think this should be rephrased to be more at the "language specification" level and less at the "compiler implementation" level. For example:
This specifies the Write attribute of T to be an anonymous subprogram that has the profile and body of the procedure declaration.
However there is no occurrence of "anonymous subprogram" in the reference manual, even though there are many "anonymous type" and a few "anonymous object". So this can surely be improved.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll make sure to rephrased everything more at the "language specification" level.
"Anonymous" could be misleading as it could refer to functions without an identifier rather than something for internal use, that's why I used the keyword "internal" but yeah no occurrence in the RM of internal subprograms neither. Maybe I should avoid a keyword at all and referred to this subprogram as the one "denoted by the subprogram-valued aspect declaration"? This could become verbose though.
| called directly. The name of the internal subprogram is determined solely by | ||
| the aspect name, meaning that the internal subprogram denoted by | ||
| ``T'Write`` will have the same name of another declaration like ``U'Write``. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could pass unnoticed, let me highlight it with a comment: having the internal subprogram's name determined solely by the aspect name means that for two different types, declaring the same aspect with this proposal would create two homonym programs, even though declared for different types.
This is essential in type derivation since it permits overloadings, but it may produce name resolution errors that we didn't anticipate and that the user may find counterintuitive since the name would be different in the source code but not in the compiled program. I couldn't find any of such problem while writing this RFC but maybe by pointing out this small detail someone else will.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is essential in type derivation since it permits overloadings,
Can you give an example?
it may produce name resolution errors that we didn't anticipate and that the user may find counterintuitive since the name would be different in the source code but not in the compiled program.
But using the feature for two different types in the same scope is expected to work, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've a few ideas about how "nonoverridable aspects that denote subprograms" should be handled. I mean the same aspects that 13.1.1 (18.4/6) applies to in the current draft of Ada 202Y.
My feeling is that the current Ada rules about those aspects follows an underlying mental model that is rather more simple than the rules themselves. With this extension, we have an opportunity to make that mental model stand out more clearly.
I'll use Constant_Indexing as an example. Even though Constant_Indexing is a nonoverridable aspect, the subprograms it denotes can very much be overridden. For example:
package P is
type T is tagged null record with Constant_Indexing => Index;
function Index (X : T; I : Natural) return Natural;
end P;
package body P is
function Index (X : T; I : Natural) return Natural is
begin
return 2 * I;
end Index;
end P;
with P;
package Q is
type T2 is new P.T with null record;
overriding
function Index (X : T2; I : Natural) return Natural;
end Q;
package body Q is
function Index (X : T2; I : Natural) return Natural is
begin
return 3 * I;
end Index;
end Q;What Constant_Indexing being nonoverridable does prevent, however, is "removing an indexing profile" that exists for the parent type. For example, the following is rejected:
package P is
type T is tagged null record with Constant_Indexing => Index;
function Index (X : T; I : Natural) return Natural;
end P;
package body P is
function Index (X : T; I : Natural) return Natural is
begin
return 2 * I;
end Index;
end P;
with P;
package Q is
type T2 is new P.T with null record with Constant_Indexing => Index2;
function Index2 (X : T2; I : Float) return Float;
end Q;
package body Q is
function Index2 (X : T2; I : Float) return Float is
begin
return 2.0 * I;
end Index2;
end Q;That's, under my interpretation, because (X : T; I : Natural) return Natural is removed from the indexing profiles in the type derivation.
Now, this is the mental model I think this suggests:
- To each type, we can associate a finite map. The keys are "the function profiles that match the constraint of
Constant_Indexingfor the type". (The associated values are functions complete with bodies, but that's less important.) - When we derive a type
T2from a typeT, we start off with the same map asT. To form the map ofT2, we're allowed to add toT's map, to overwrite its elements, but we're not allowed to remove elements from it.
We can use that model to interpret the meaning of subprogram-valued aspect declarations for Constant_Indexing. For example:
package P is
type T is tagged null record;
function T'Constant_Indexing (X : T; I : Natural) return Natural;
function T'Constant_Indexing (X : T; B : Boolean) return Natural;
end P;
package body P is
function T'Constant_Indexing (X : T; I : Natural) return Natural is
begin
return 2 * I;
end T'Constant_Indexing;
function T'Constant_Indexing (X : T; B : Boolean) return Natural is
begin
return (if B then 2 else 3);
end T'Constant_Indexing;
end P;
-- Given the package defined above, `P.T`'s map has exactly two keys,
-- "(X : T; I : Natural) return Natural" and "(X : T; B : Boolean) return Natural".
with P;
package Q is
type T2 is new P.T with null record;
-- We overwrite the value for "(X : T{,2}; I : Natural) return Natural".
function T2'Constant_Indexing (X : T2; I : Natural) return Natural;
-- We add a new key, "(X : T{,2}; F : Float) return Float".
function T2'Constant_Indexing (X : T2; F : Float) return Float;
end Q;
package body Q is
function T2'Constant_Indexing (X : T2; I : Natural) return Natural is
begin
return 3 * I;
end T2'Constant_Indexing;
function T2'Constant_Indexing (X : T2; F : Float) return Float is
begin
return 4.0 * F;
end T2'Constant_Indexing;
end Q;The map for Q.T2 has three profiles here and I find it pretty clear.
My point is that as long that we can find a way to describe this feature that follows this general interface, it would be adequate and we don't necessarily have to use an equivalence rule in terms of an implicit name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with Ronan's point of view, that the type specified as the prefix of the name is part of the name from an overload resolution point of view, and can be used to distinguish at a call site. On the other hand, as far as what is the equivalent aspect specification, I would suggest it is something like:
with Constant_Indexing => @'Constant_Indexing
where "@" is filled in with the associated type's name. So the "name" specified for the aspect is non-overridable, but a special syntax is used to refer to a subprogram that is declared with one of these proposed new subprogram-valued aspect declarations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks Ronan for the detailed example you presented above, I also agree with your analysis.
And to your suggestion Tucker, Instead of "@" is filled in with the associated type's name, I'd rephrase it as: in @'Constant_Indexing the placeholder @ is filled with the name of the ancestor of the associated type (allowing itself as "first" ancestor here) which first specified Constant_Indexing. This modification is required to make T'Constant_Indexing and T2'Constant_Indexing share the same implicit name, but not with Other'Constant_Indexing to avoid name clashes with other unrelated entities.
I don't know if this is going against Ronan's idea of not having to use an equivalence rule in terms of an implicit name (in this case the implicit name is the special @'Constant_Indexing here). But I think with this use of @ we could give a more intuitive and less case-by-case definition for this proposal, it would be simply the equivalence with its extension in terms of @ and then we define what @ is replaced by. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks Ronan for the detailed example you presented above, I also agree with your analysis.
And to your suggestion Tucker, Instead of "@" is filled in with the associated type's name, I'd rephrase it as: in
@'Constant_Indexingthe placeholder@is filled with the name of the ancestor of the associated type (allowing itself as "first" ancestor here) which first specifiedConstant_Indexing. This modification is required to makeT'Constant_IndexingandT2'Constant_Indexingshare the same implicit name, but not withOther'Constant_Indexingto avoid name clashes with other unrelated entities.
Actually, I realize what I wrote was ambiguous. What I meant is that the name used in the equivalent aspect specification is literally "@'<aspect_name>" but when you write the subprogram-valued declaration, the programmer replaces the "@" with the particular type name for which it is being declared. Furthermore, when one of these subprograms is inherited, the @ is replaced with the new (derived) type's name in the implicit declaration, similar to the way the formal parameter types are replaced.
I don't know if this is going against Ronan's idea of not having to use an equivalence rule in terms of an implicit name (in this case the implicit name is the special
@'Constant_Indexinghere). But I think with this use of@we could give a more intuitive and less case-by-case definition for this proposal, it would be simply the equivalence with its extension in terms of@and then we define what@is replaced by. What do you think?
Yes, that is what I was trying to suggest.
| denoted subprogram. Even though an aspect call (e.g., ``T'Write (...)``) is not | ||
| *strictly speaking* | ||
| a subprogram call, under the hood the compiler transparently resolves it to the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even though an aspect call (e.g.,
T'Write (...)) is not
strictly speaking
a subprogram call
My understanding of the language is a bit different. I would say this actually is a regular subprogram call, the prefix of which is an attribute name, and the resolution rules for attribute names are a bit more complicated than for a direct name, for example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will rephrase with this.
| Item : in T); | ||
| end record; | ||
| Unfortunately, the direct attribute definitions inside a scoped record clashes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, thanks for the explanation.
Personally, I associate "aspect" to the idea that the specification is syntactically together with the entity definition, as opposed to pragmas and attribute definitions that can happen later
where the freezing rules permit. So the declarations introduced by this RFC would rather fit into the second category, but it's not a big deal.
Another nit is that "declarations" sounds as bit like we're declaring new aspects, whereas we are specifying values of aspects predefined by the language. So I would maybe have expected "specifications" instead.
| called directly. The name of the internal subprogram is determined solely by | ||
| the aspect name, meaning that the internal subprogram denoted by | ||
| ``T'Write`` will have the same name of another declaration like ``U'Write``. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is essential in type derivation since it permits overloadings,
Can you give an example?
it may produce name resolution errors that we didn't anticipate and that the user may find counterintuitive since the name would be different in the source code but not in the compiled program.
But using the feature for two different types in the same scope is expected to work, right?
Part of #159