-
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?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,68 +5,357 @@ | |
| Summary | ||
| ======= | ||
|
|
||
| This RFC introduces a shorthand notation to declare subprogram-valued aspects. | ||
|
|
||
| Motivation | ||
| ========== | ||
|
|
||
| Currently, to specify a subprogram-valued aspect for a type, one needs to first | ||
| declare a subprogram, that most of the times is never referred elsewhere, and | ||
| then link it to the type via an aspect specification or attribute definition | ||
| clause. This pattern creates unnecessary boilerplate code, disconnects the | ||
| aspect specification from the denoted subprogram declaration, and introduces | ||
| potential name resolution issues. | ||
|
|
||
| Guide-level explanation | ||
| ======================= | ||
|
|
||
| Today, specifying a subprogram attribute of a type requires to declare a | ||
| subprogram and then to use an aspect or an attribute to link it to a type, e.g. | ||
| Subprogram-valued Aspect Declarations | ||
| ------------------------------------- | ||
|
|
||
| Currently, specifying a subprogram-valued aspect for a type requires to first | ||
| declare the subprogram and then apply the corresponding aspect specification or | ||
| attribute definition clause. For example: | ||
|
|
||
| .. code-block:: ada | ||
|
|
||
| type T is null record; | ||
|
|
||
| for T'Write use My_Write; | ||
|
|
||
| procedure My_Write( | ||
| Stream : not null access Ada.Streams.Root_Stream_Type'Class; | ||
| procedure My_Write | ||
| (Stream : not null access Ada.Streams.Root_Stream_Type'Class; | ||
| Item : in T); | ||
|
|
||
| Or via an aspect specification: | ||
|
|
||
| .. code-block:: ada | ||
|
|
||
| type T is null record with Write => My_Write; | ||
|
|
||
| procedure My_Write | ||
| (Stream : not null access Ada.Streams.Root_Stream_Type'Class; | ||
| Item : in T); | ||
|
|
||
| This proposal allows to define directly the procedure as being an attribute of | ||
| the type: | ||
| This proposal introduces **subprogram-valued aspect declarations** that allow | ||
| to specify a subprogram as an aspect value directly within the subprogram | ||
| declaration. | ||
| For example, the above can be rewritten as: | ||
|
|
||
| .. code-block:: ada | ||
|
|
||
| type T is null record; | ||
|
|
||
| procedure T'Write( | ||
| Stream : not null access Ada.Streams.Root_Stream_Type'Class; | ||
| procedure T'Write | ||
| (Stream : not null access Ada.Streams.Root_Stream_Type'Class; | ||
| Item : in T); | ||
|
|
||
| In addition, this can be used as a scoped attribute, possibly with re-ordering | ||
| of parameters to ensure that the first parameter is conceptually the one | ||
| "owning" the primitive, e.g.: | ||
| The code above both declares the subprogram ``T'Write`` and specifies it as the | ||
| ``Write`` aspect of type ``T``. | ||
| Intuitively, this shorthand notation translates to the following aspect | ||
| specification: | ||
|
|
||
| .. code-block:: ada | ||
|
|
||
| type T is null record with Write => @'Write; | ||
|
|
||
| Where "@" denotes the enclosing type name, cf. ``T`` in this case. When such | ||
| subprogram is inherited, "@" will also denote the derived type name. | ||
|
|
||
| A Derived Map Example | ||
| ^^^^^^^^^^^^^^^^^^^^^ | ||
|
|
||
| With the subprogram-valued aspect declarations, we could write a hierarchy of a | ||
| map as follows: | ||
|
|
||
| .. code-block:: ada | ||
|
|
||
| package P is | ||
| type T is tagged null record; | ||
|
|
||
| -- T has exactly two keys: Natural and Boolean | ||
| function T'Constant_Indexing (X : T; I : Natural) return Natural; | ||
| function T'Constant_Indexing (X : T; B : Boolean) return Natural; | ||
| end P; | ||
|
|
||
| .. code-block:: ada | ||
|
|
||
| with P; | ||
|
|
||
| package Q is | ||
| type T2 is new P.T with null record; | ||
|
|
||
| -- The behavior of the Natural key is overridden | ||
| overriding -- Optional | ||
| function T2'Constant_Indexing (X : T2; I : Natural) return Natural; | ||
|
|
||
| -- The Boolean key is implicitly inherited from P.T | ||
| -- function T2'Constant_Indexing (X : T2; B : Boolean) return Natural; | ||
|
|
||
| -- Additionally, T2 supports a Float key | ||
| function T2'Constant_Indexing (X : T2; F : Float) return Float; | ||
| end Q; | ||
|
|
||
| The aspect specification on ``T`` is ``... => @'Constant_Indexing``, where "@" | ||
| denotes both ``T`` and ``T2``. | ||
| As you can see from the example above, the usual rules of inheritance apply. | ||
|
|
||
| There is no clash between subprogram-valued aspect declarations for the same | ||
| aspect name, in the same scope *but* for different types, since the aspect | ||
| specification is qualified by "@", which denotes the enclosing type name. | ||
|
|
||
| .. code-block:: ada | ||
|
|
||
| type A is tagged null record; | ||
| procedure A'Constant_Indexing (X : A; I : Natural) return Natural; | ||
|
|
||
| type B is tagged null record; | ||
| procedure B'Constant_Indexing (X : B; I : Natural) return Natural; | ||
|
|
||
| Which intuitively translates to: | ||
|
|
||
| - ``type A ... with Constant_Indexing => @'Constant_Indexing`` | ||
| where "@" denotes only ``A``, and | ||
|
|
||
| - ``type B ... with Constant_Indexing => @'Constant_Indexing`` | ||
| where "@" denotes only ``B``. | ||
|
|
||
| As expected, ``A'Constant_Indexing`` and ``B'Constant_Indexing`` are not | ||
| homonyms. | ||
|
|
||
| Primitive or Nonprimitive? | ||
| -------------------------- | ||
|
|
||
| Subprogram-valued aspect declarations can be used to define both primitive and | ||
| nonprimitive operations of a type. | ||
| For instance, we could define a container type as: | ||
|
|
||
| .. code-block:: ada | ||
|
|
||
| type Container is tagged record | ||
| ... | ||
| end record; | ||
| type Cursor is new Positive; | ||
|
|
||
| function Container'First (C : Container) return Cursor; | ||
| function Container'Next (C : Container; Pos : Cursor) return Cursor; | ||
| function Container'Has_Element (C : Container; Pos : Cursor) return Boolean; | ||
| function Container'Element (C : Container; Pos : Cursor) return Integer; | ||
|
|
||
| All the subprogram-valued aspect | ||
| declarations of ``Container`` are primitive operations. | ||
| The usual rules of primitive operations apply. | ||
|
Comment on lines
+139
to
+157
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you give an second example, for the nonprimitive case? (The one already here is a translation of the GNAT-specific
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would occur for all aspects that require a subprogram as value and that subprogram needs not to have a profile that will make them primitive. Currently, we can find such examples with class-wide aspects, such as: type T is tagged null record;
-- nonprimitive
procedure T'Class'Write
(Stream : not null access Ada.Streams.Root_Stream_Type'Class;
Item : T'Class) is null;But in the future more may be added, and I couldn't find anything in the RM that requires a subprogram used as aspect value that needs to be primitive. |
||
|
|
||
| Scoped Records | ||
| -------------- | ||
|
|
||
| In relation to the scoped records and class types proposed in the *RFC OOP | ||
| primitives*, | ||
| subprogram-valued aspect declarations can be used accordingly: | ||
|
|
||
| .. code-block:: ada | ||
|
|
||
| type T is tagged record | ||
|
|
||
| procedure T'Write( | ||
| Stream : not null access Ada.Streams.Root_Stream_Type'Class; | ||
| procedure T'Write | ||
| (Stream : not null access Ada.Streams.Root_Stream_Type'Class; | ||
| Item : in T); | ||
|
|
||
| end T; | ||
|
|
||
| This provide a seamless solution in particular to scope a destructor: | ||
|
|
||
| This provide a seamless solution in particular to scope oop primitives: | ||
|
|
||
| .. code-block:: ada | ||
|
|
||
| type T is tagged record | ||
| type T is class record | ||
| procedure T'Constructor (Self : in out T); | ||
| procedure T'Destructor (Self : in out T); | ||
|
|
||
| procedure T'Write | ||
| (Stream : not null access Ada.Streams.Root_Stream_Type'Class; | ||
| Item : in T); | ||
| end record; | ||
|
|
||
| This approach is quite natural and consistent with the rest of the proposal: | ||
| all aspect specifications are gathered together within the scoped environment. | ||
|
|
||
| Unfortunately, the subprogram-valued aspect declarations inside a scoped class | ||
| may clash with the class type rules where each subprogram is required to be | ||
| primitive and following the rules of First_Controlling_Parameter | ||
| (e.g., see ``T'Write`` above | ||
| where ``Item`` is the second formal, not the first). The rule's motivation | ||
| comes, for instance, | ||
| from the fact that class primitives are called with the prefix notation, and | ||
| that no primitive operation should be defined afterwards. | ||
|
|
||
| To solve this issue, we allow (only) subprogram-valued aspect declarations | ||
| inside scoped environments to be nonprimitive and not following the | ||
| First_Controlling_Parameter rule. Since, these subprograms are never called | ||
| with the prefix notation, this relaxation is safe. | ||
|
|
||
| Hierarchy with Write, Read, Output, and Put_Image | ||
| ------------------------------------------------- | ||
|
|
||
| Here it follows an example of usage of subprogram-valued aspect declarations | ||
| with a simple hierarchy involving a combination of ``Write``, ``Read``, | ||
| ``Output``, and | ||
| ``Put_Image`` aspects. | ||
|
|
||
| .. code-block:: ada | ||
|
|
||
| with Ada.Strings.Text_Buffers; use Ada.Strings.Text_Buffers; | ||
| with Ada.Streams; use Ada.Streams; | ||
|
|
||
| package Geometry is | ||
| type Shape is tagged record | ||
| X, Y : Integer; | ||
| end record; | ||
|
|
||
| procedure Shape'Read | ||
| (Stream : not null access Root_Stream_Type'Class; | ||
| Item : out Shape); | ||
| procedure Shape'Output | ||
| (Stream : not null access Root_Stream_Type'Class; | ||
| Item : in Shape); | ||
| procedure Shape'Put_Image | ||
| (Buffer : in out Root_Buffer_Type'Class; | ||
| Item : Shape); | ||
|
|
||
| type Circle is new Shape with record | ||
| Radius : Integer; | ||
| end record; | ||
|
|
||
| overriding | ||
| procedure Circle'Read | ||
| (Stream : not null access Root_Stream_Type'Class; | ||
| Item : out Circle); | ||
| procedure Circle'Write | ||
| (Stream : not null access Root_Stream_Type'Class; | ||
| Item : in Circle); | ||
| overriding | ||
| procedure Circle'Put_Image | ||
| (Buffer : in out Root_Buffer_Type'Class; | ||
| Item : Circle); | ||
| end Geometry; | ||
|
|
||
| ... | ||
|
|
||
| with Ada.Strings.Text_Buffers; use Ada.Strings.Text_Buffers; | ||
| with Ada.Streams; use Ada.Streams; | ||
| with Ada.Text_IO; use Ada.Text_IO; | ||
|
|
||
| package body Geometry is | ||
| procedure Shape'Read | ||
| (Stream : not null access Root_Stream_Type'Class; | ||
| Item : out Shape) is | ||
| begin | ||
| Put_Line ("Shape'Read called"); | ||
| Integer'Read (Stream, Item.X); | ||
| Integer'Read (Stream, Item.Y); | ||
| end Shape'Read; | ||
|
|
||
| procedure Shape'Output | ||
| (Stream : not null access Root_Stream_Type'Class; | ||
| Item : in Shape) is | ||
| begin | ||
| Put_Line ("Shape'Output called"); | ||
| Integer'Output (Stream, Item.X); | ||
| Integer'Output (Stream, Item.Y); | ||
| end Shape'Output; | ||
|
|
||
| procedure Shape'Put_Image | ||
| (Buffer : in out Root_Buffer_Type'Class; | ||
| Item : Shape) is | ||
| begin | ||
| Put_Line ("Shape'Put_Image called"); | ||
| Integer'Put_Image (Buffer, Item.X); | ||
| Integer'Put_Image (Buffer, Item.Y); | ||
| end Shape'Put_Image; | ||
|
|
||
| overriding | ||
| procedure Circle'Read | ||
| (Stream : not null access Root_Stream_Type'Class; | ||
| Item : out Circle) is | ||
| begin | ||
| Put_Line ("Circle'Read called"); | ||
| Integer'Read (Stream, Item.Radius); | ||
| Shape'Read (Stream, Shape (Item)); | ||
| end Circle'Read; | ||
|
|
||
| procedure Circle'Write | ||
| (Stream : not null access Root_Stream_Type'Class; | ||
| Item : in Circle) is | ||
| begin | ||
| Put_Line ("Circle'Write called"); | ||
| Integer'Write (Stream, Item.Radius); | ||
| Shape'Output (Stream, Shape (Item)); -- Mixing Write and Output here | ||
| end Circle'Write; | ||
|
|
||
| overriding | ||
| procedure Circle'Put_Image | ||
| (Buffer : in out Root_Buffer_Type'Class; | ||
| Item : Circle) is | ||
| begin | ||
| Put_Line (Shape(Item)'Image); | ||
| Put_Line ("Circle'Put_Image called"); | ||
| Integer'Put_Image (Buffer, Item.Radius); | ||
| end Circle'Put_Image; | ||
| end Geometry; | ||
|
|
||
| Reference-level explanation | ||
| =========================== | ||
|
|
||
| This proposal introduces syntactic sugar for the declaration and specification | ||
| of subprogram-valued aspects. It is fully backward compatible with existing Ada | ||
| code. | ||
|
|
||
| When a subprogram-valued aspect declaration is encountered, it is equivalent | ||
| to an aspect specification where the aspect value is "@'<Aspect_Name>", where | ||
| "@" denotes the the name of the enclosing type and any of its derived types. | ||
|
|
||
| Rationale and alternatives | ||
| ========================== | ||
|
|
||
| While the notation "@'<Aspect_Name>" is not a syntactic Ada construct and it is | ||
| used above in thi proposal at an intuitive level for the aspect specifications, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typo: "thi" -> "this. |
||
| a syntactic ``@'<Aspect_Name>`` used in the subprogram name could further | ||
| shorten our notation. This second use of ``@`` denotes the enclosing | ||
| type name; no need to account for derived types here. | ||
| The big advantage is when the type name is long; for instance, we could write: | ||
|
|
||
| .. code-block:: ada | ||
|
|
||
| type Indexable_Container_With_A_Long_Name is tagged record | ||
| function @'Constant_Indexing | ||
| (Container : aliased @; | ||
| Position : Offset) return Constant_Reference_Type; | ||
| function @'Constant_Indexing | ||
| (Container : aliased @; | ||
| Position : Custom_Offset) return Constant_Reference_Type; | ||
| function @'Variable_Indexing | ||
| (Container : aliased in out @; | ||
| Position : Offset) return Reference_Type; | ||
| end record; | ||
|
|
||
| This use of ``@`` transcends the current proposal and should be discussed in a | ||
| separate RFC. | ||
|
|
||
| Drawbacks | ||
| ========= | ||
|
|
||
| The IDE support for this shorthand notation might be complex as it would need | ||
| to connect aspect calls with their (potential) declarations. | ||
|
|
||
| Prior art | ||
| ========= | ||
|
|
||
|
|
@@ -76,4 +365,3 @@ Unresolved questions | |
| Future possibilities | ||
| ==================== | ||
|
|
||
|
|
||
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.
Typo: "referred elsewhere" -> "referred to elsewhere".