diff --git a/considered/rfc-oop-access.rst b/considered/rfc-oop-access.rst new file mode 100644 index 00000000..01c36aff --- /dev/null +++ b/considered/rfc-oop-access.rst @@ -0,0 +1,91 @@ +- Feature Name: Standard OOP model +- Start Date: May 5th 2020 +- RFC PR: +- RFC Issue: + +Summary +======= + +Motivation +========== + +Guide-level explanation +======================= + +This capability is available to all types (including simple records). The idea +here is to have an access type declared implicitely at the same level as the +type and accessible through the 'Ref notation. An attribute 'Unchecked_Free is +also declared, doing unchecked deallocation. 'Unchecked_Free can also be called +directly on the object. For example: + +.. code-block:: ada + + package P is + type T1 is tagged record + procedure P (Self : in out T1); + + procedure P2 (Self : in out T1); + end T1; + end P; + + procedure Some_Procedure is + V : T1'Ref := new T1; + V2 : T1'Ref := new T1; + begin + T1'Unchecked_Free (V); + V2'Unchecked_Free; + end Some_Procedure; + +For homogenity, 'Ref and 'Unchecked_Free are available to all Ada type - +including pointers themsleves. It's now possible to write: + +.. code-block:: ada + + V : T1'Ref'Ref := new T1'Ref; + +Contrary to tagged types, 'Ref access types for a given class object are +compatible in the case of upcast, but need explicit conversions to downcast. +You can write: + +.. code-block:: ada + + package P is + type A is tagged record + procedure P (Self : in out T1); + end A; + + type B is new T1 with record + procedure P (Self : in out T1); + end B; + end P; + + procedure Some_Procedure is + A1 : A'Ref := new B; + A2 : A'Ref; + + B1 : B'Ref := new B; + B2 : B'Ref; + begin + A2 := B1; -- OK, upcast, no need for pointer conversion + B2 := A1; -- Illegal, downcast + B2 := B'Ref (A1); -- OK, explicit downcast. + end Some_Procedure; + +Reference-level explanation +=========================== + +Rationale and alternatives +========================== + +Drawbacks +========= + + +Prior art +========= + +Unresolved questions +==================== + +Future possibilities +==================== diff --git a/considered/rfc-oop-aggregates.rst b/considered/rfc-oop-aggregates.rst new file mode 100644 index 00000000..e8887ea6 --- /dev/null +++ b/considered/rfc-oop-aggregates.rst @@ -0,0 +1,168 @@ +- Feature Name: Standard OOP model +- Start Date: May 5th 2020 +- RFC PR: +- RFC Issue: + +Summary +======= + +Motivation +========== + +Guide-level explanation +======================= + +In the presence of constructors, aggregates values are evaluated and assigned +after the contructor is executed. So the full sequence of evaluation for +fields of a class record is: + +- the constructor +- any value from the aggregate + +This respects the fundamental rule that constructors can never be bypassed. For +example: + +.. code-block:: ada + + function Print_And_Return (S : String) return Integer is + begin + Put_Line (S); + + return 0; + end; + + type R1 is record + V, W : Integer := Print_And_Return ("Default"); + end record; + + type R2 is record + V, W : Integer := Print_And_Return ("Default"); + + procedure R2 (Self : in out R2); + end record; + + V1 : R1 := (1, 2); -- prints Default Default + V2 : R2 := (1, 2); -- also prints Default Default + +This means that units compiled with the new version of Ada will have a specific +backward incompatible change. Specifically, record initialized with an aggregate +used to bypass default initialization, they would not anymore. From a +functional standpoint, this would result in more code as well as different +behavior if the default initialization has side effects. This can be fixed +by offering constructors with the right parameters. These issues could be +identified statically by migration tools. + +There is a specific question on performances impact, as the new semantic would +evaluate default value where the current Ada semantic would not. This can be +solved by not using default initialization in the type, but to provide instead +a constructor that takes parameters to value all components. For example: + +.. code-block:: ada + + type R1 is record + V, W : Integer := Some_Value; + end record; + +Can become: + +.. code-block:: ada + + type R1 is record + V, W : Integer; + + procedure R1 (Self : in out R1; V, W : Integer); + end record; + + type body R1 is record + procedure R1 (Self : in out R1; V, W : Integer) is + begin + Self.V := V; + Self.W := W; + end R; + end record; + +As a consequence, writing: + +.. code-block:: ada + + V1 : R1 := R1'Make(1, 2); + +Can be made compiled as efficiently as before (in particular if the constructors +are inlined) + +In terms of syntax, in the presence of an implicit or explicit parameterless +constructors, aggregates can be written as usual. The parameterless constructor +will be called implicitly before modification of the values by the aggregate. + +If a non-parameterless constructor needs to be called, two syntaxes are available: +- if only some values need to be modified, a delta aggregate can be used +- if all values need to be modified, the syntax is "( with )" + which is very close to the current notation for extension aggregates. For example: + +.. code-block:: ada + + type R is record + V, W : Integer; + + procedure R (Self : in out R); + + procedure R (Self : in out R; V : Integer); + end record; + + type R is record + procedure R (Self : in out R) is + begin + Put_Line ("Default"); + end R; + + procedure R (Self : in out R; V : Integer) is + begin + Put_Line (f"V = {V}"); + end R; + + end record; + + V1 : R := (1, 2); -- prints "Default" + V2 : R := (R'Make (42) with delta V => 1); -- prints "V = 42" + V3 : R := (R'Make (42) with 1, 2); -- also prints "V = 42" + +One of the consequences of the rules above is that it's not possible to use an +aggregate within a constructor as it would create an infinite recursion: + +.. code-block:: ada + + package P is + type T1 is tagged record + procedure T1 (Self : in out T1); + + A, B, C : Integer; + end T1; + end P; + + package body P is + type body T1 is tagged record + procedure T1 (Self : in out T1) is + begin + Self := (1, 2, 3); -- infinite recursion + end T1; + end T1; + end P; + +Reference-level explanation +=========================== + +Rationale and alternatives +========================== + + +Drawbacks +========= + +Prior art +========= + +Unresolved questions +==================== + +Future possibilities +==================== diff --git a/considered/rfc-oop-attributes.rst b/considered/rfc-oop-attributes.rst new file mode 100644 index 00000000..c2038569 --- /dev/null +++ b/considered/rfc-oop-attributes.rst @@ -0,0 +1,80 @@ +- Feature Name: Standard OOP model +- Start Date: +- RFC PR: +- RFC Issue: + +Summary +======= + +Motivation +========== + +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. + +.. 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; + Item : in T); + +This proposal allows to define directly the procedure as being an attribute of +the type: + +.. code-block:: ada + + type T is null record; + + 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.: + +.. code-block:: ada + + type T is tagged record + + 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: + + +.. code-block:: ada + + type T is tagged record + procedure T'Destructor (Self : in out T); + end record; + +Reference-level explanation +=========================== + +Rationale and alternatives +========================== + +Drawbacks +========= + +Prior art +========= + +Unresolved questions +==================== + +Future possibilities +==================== + + diff --git a/considered/rfc-oop-body.rst b/considered/rfc-oop-body.rst new file mode 100644 index 00000000..14c2c7ce --- /dev/null +++ b/considered/rfc-oop-body.rst @@ -0,0 +1,50 @@ +- Feature Name: Standard OOP model +- Start Date: May 5th 2020 +- RFC PR: +- RFC Issue: + +Summary +======= + +Motivation +========== + +Guide-level explanation +======================= + +One limitation of the tagged record to lift under this system is the ability to +declare a primitives in the body of a package. As long as the type itself is +also declared in the body, this should be legal: + +.. code-block:: ada + + package body Pkg is + type T is tagged record + F : Integer; + end T; + + procedure P (Self : in out T; V : Integer); + + procedure P (Self : in out T; V : Integer) is + begin + Self.F := V; + end P; + end Pkg; + +Reference-level explanation +=========================== + +Rationale and alternatives +========================== + +Drawbacks +========= + +Prior art +========= + +Unresolved questions +==================== + +Future possibilities +==================== diff --git a/considered/rfc-oop-fields.rst b/considered/rfc-oop-fields.rst new file mode 100644 index 00000000..bd5ddb7f --- /dev/null +++ b/considered/rfc-oop-fields.rst @@ -0,0 +1,81 @@ +- Feature Name: Standard OOP model +- Start Date: May 5th 2020 +- RFC PR: +- RFC Issue: + +Summary +======= + +Motivation +========== + +Guide-level explanation +======================= + +Components declarations +----------------------- + +A record and tagged record type can now have 3 declarative +region where fields can be declared: + +- the package public view +- the package private view +- the record private view + +A public view that allows for additional fields to be declared is denoted by +`with private` after its declaration. The private view of a record type can +then be split between its own public and private sections: + +For example: + +.. code-block:: ada + + package P is + type R is record + Pub : Integer; + end R + with private; + + type T is tagged record + Pub : Integer; + end T + with private; + + private + + type R is record + Priv : Integer; + private + Hidden : Integer; + end R; + + type T is tagged record + Priv : Integer; + private + Hidden : Integer; + end T; + end P; + +Fields that are private to a type (noted `Hidden` in the above example) can +only be accessed through the primitives, constructors and destructors of that +very type. They are inaccessible to other subprograms, notably non-primitive +or overriden primitives. + +Reference-level explanation +=========================== + +Rationale and alternatives +========================== + +Drawbacks +========= + +Prior art +========= + +Unresolved questions +==================== + +Future possibilities +==================== + diff --git a/considered/rfc-oop-final.rst b/considered/rfc-oop-final.rst new file mode 100644 index 00000000..72a20c5f --- /dev/null +++ b/considered/rfc-oop-final.rst @@ -0,0 +1,97 @@ +- Feature Name: Standard OOP model +- Start Date: May 5th 2020 +- RFC PR: +- RFC Issue: + +Summary +======= + +Motivation +========== + + +Guide-level explanation +======================= + +Tagged records support constant fields, which are field which value cannot be +changed after the constructor call, not even during aggregate which is +considered as a shortcut for assignment. For example: + +.. code-block:: ada + + package P is + type T1 is tagged record + procedure T1 (Self : in out T1; Val : Integer); + + Y : final Integer := 0; + end T1; + end P; + + package body P is + type body T1 is tagged record + procedure T1 (Self : in out T1; Val : Integer) is + begin + -- Y is 0 here + Self.Y := Val; -- Legal + -- Y is val here + end T1; + end T1; + + V : T1 := (Y => 2); -- Illegal, Y is final + end P; + +Final types +----------- + +Tagged record also implement the concept of final types, +which is a type not deriveable. There are two advantages of final types: + +- In terms of design, this makes it clear that this class is not intended to be + derived. It's often the case where derivation is used just to have a class in + a given framework but isn't prepared to be itself modified. +- A very significant one: a final class is effectively a definite type. + As a result, it can be stored on the stack or as a component, + calls to a view of a final class are not dispatching + (the target is statically known). + +.. code-block:: ada + + package P is + type T1 is tagged record + null; + end T1; + + type T2 is final new T1 with record + null; + end T2; + + type T3 is new T2 with record -- Illegal, T2 is final + null; + end T3; + + V1 : T1; -- Illegal, T1 is indefinite + V2 : T2; -- Legal, T2 is final. + end P; + +Reference-level explanation +=========================== + +Rationale and alternatives +========================== + +Global object hierarchy +----------------------- + + +Drawbacks +========= + + +Prior art +========= + +Unresolved questions +==================== + +Future possibilities +==================== diff --git a/considered/rfc-oop-implicit_discriminants.rst b/considered/rfc-oop-implicit_discriminants.rst new file mode 100644 index 00000000..139739f4 --- /dev/null +++ b/considered/rfc-oop-implicit_discriminants.rst @@ -0,0 +1,140 @@ +- Feature Name: Standard OOP model +- Start Date: May 5th 2020 +- RFC PR: +- RFC Issue: + +Summary +======= + +Motivation +========== + +Guide-level explanation +======================= + +Indefinite fields +----------------- + +It is now possible to declare a record as being "indefinite" without specifying +its discriminants, which allows to have indefinite fields, e.g.: + +.. code-block:: Ada + + type T (<>) is record -- T1 is indefinite + X : String; -- it's ok to have an indefinite field here + + procedure T (Self : in out T; Val : String); -- constructor + + procedure T (Self : in out T; Size : Integer); -- constructor + end record; + +Indefinite fields must be provided with a value by the initialization list of +the constructor. For example: + +.. code-block:: Ada + + package P is + + type body T (<>) is record + procedure T (Self : in out T; Val : String) + with Initialize (X => Val) + is + begin + null; + end T; + + procedure T (Self : in out T; Size : Integer) + with Initialize (X => [1 .. Size => <>]) + is + begin + null; + end T; + end record; + end P; + +All other rules for disriminated record apply here (in particular with regards +to constraint checking). + +Implementation note +------------------- + +This feature may (and should) lead to more extensive usage of indefinite types, +e.g.: + +.. code-block:: Ada + + type T (<>) is record -- T1 is indefinite + A : String; + B : String; + C : String; + D : String; + + procedure T (Self : in out T); + end record; + +Implementers need to be careful with implementation penalties. In particular, +computing (and accessing) the field D may require to load the value of the Size +of A, B and C and then add them. This is significantly more expensive than +a situation where D was referenced through a pointer. + +Implementation should consider the size / performance trade-off, and either +store offsets or pointers in the type. The above could be expanded to: + +.. code-block:: Ada + + type T (<>) is record -- T1 is indefinite + _A_Location : access String; + _B_Location : access String; + _C_Location : access String; + _D_Location : access String; + A : String; + B : String; + C : String; + D : String; + + procedure T (Self : in out T); + end record; + + -- X.D is replaced by X._D_Location.all + +or possibly: + +.. code-block:: Ada + + type T (<>) is record -- T1 is indefinite + _A_Offset : Positive range 0 .. 2 ** 16 - 1; -- we don't need 64 bits to store such offset, this could be implementation-defined + _B_Offset : Positive range 0 .. 2 ** 16 - 1; + _C_Offset : Positive range 0 .. 2 ** 16 - 1; + _D_Offset : access String + A : String; + B : String; + C : String; + D : String; + + procedure T (Self : in out T); + end record; + + -- X.D is replaced by (@D + X._D_Offset).all + +Performances benchmarks could help chosing between these alternatives (there +may be others). + + +Reference-level explanation +=========================== + +Rationale and alternatives +========================== + +Drawbacks +========= + +Prior art +========= + +Unresolved questions +==================== + +Future possibilities +==================== + diff --git a/considered/rfc-oop-obsolete.rst b/considered/rfc-oop-obsolete.rst new file mode 100644 index 00000000..ac8b8375 --- /dev/null +++ b/considered/rfc-oop-obsolete.rst @@ -0,0 +1,51 @@ +- Feature Name: Standard OOP model +- Start Date: May 5th 2020 +- RFC PR: +- RFC Issue: + +Summary +======= + +This section summarises Ada OOP capabilities that are rendered obsolete or not +ported in the new model. + +Motivation +========== + +Guide-level explanation +======================= + +Operators and exotic primitives +------------------------------- + +When using the scoped primitive notation, tagged record do not provide +dispatching on multiple parameters, on parameters other than the first, or +dispatching on results. If you declare primitives with references to the type +other than the first parameter, they will not be used +for + +Under the current model, coextensions are replaced by constructors +(it's possible to mandate an object to be used in the construction of the +class) and destructors (that same object can always be destroyed in the +destructor). There is no way to create a coextension on a class record. + +Controlled types are replaced by constructor / destructors and are not allowed. + +Reference-level explanation +=========================== + +Rationale and alternatives +========================== + +Drawbacks +========= + + +Prior art +========= + +Unresolved questions +==================== + +Future possibilities +==================== diff --git a/considered/rfc-oop-primitives.rst b/considered/rfc-oop-primitives.rst new file mode 100644 index 00000000..7f04543d --- /dev/null +++ b/considered/rfc-oop-primitives.rst @@ -0,0 +1,165 @@ +- Feature Name: Standard OOP model +- Start Date: May 5th 2020 +- RFC PR: +- RFC Issue: + +Summary +======= + +Motivation +========== + +Guide-level explanation +======================= + +Primitives and Declarations +--------------------------- + +Record types can be marked with the aspect `Scoped_Primitives` (shortcut for +`Scoped_Primitives => True`). This aspect allows to declare primitives within +the lexical scope of the type. Under this new model, primitives are declared +within the lexical scope of their type. The first parameter of the primitive has +to be of the type of the record. This allows the user to decide on the naming +convention, as well as the mode of such parameter (in, out, in out, access, aliased). +A record and a tagged record can have primitives declared both in the public and +the private part. This is possibility is extended to other components as well. +The existence of a private part needs to be specified in the public part of a +package with the notation "with private". The following demonstrates the above: + + +.. code-block:: ada + + package P is + type R is record + F : Integer; + + procedure P (Self : in out R; V : Integer); + end record + with Scoped_Primitives, private; + + type T is tagged record + F : Integer; + + procedure P (Self : in out T; V : Integer); + end record + with Scoped_Primitives, private; + + private + + type R is record + procedure P2 (Self : in out R; V : Integer); + end record with Scoped_Primitives; + + type T is class record + procedure P2 (Self : in out T; V : Integer); + end record with Scoped_Primitives; + + end P; + + package body P is + + type body R is record + procedure P (Self : in out R; V : Integer) is + begin + Self.F := V; + end record with Scoped_Primitives; + + procedure P2 (Self : in out R; V : Integer) is + begin + Self.F := V + 1; + end P2; + end record with Scoped_Primitiv; + + type body T is tagged record + procedure P (Self : in out T; V : Integer) is + begin + Self.F := V; + end R; + + procedure P2 (Self : in out T; V : Integer) is + begin + Self.F := V + 1; + end P2; + end record with Scoped_Primitives; + + end P; + +Primitives declared within a type can only be called via prefix notation. When +primitives are declared in a scope, there can no longer be primitives declared +ouside of the scope, such declarations are non-primitives. + +Overriding and extensions +------------------------- + +Extension of class record types work similarly to tagged records: + +.. code-block:: ada + + package P is + type T1 is class record + procedure P (Self : in out T1); + end record with Scoped_Primitives; + + type T2 is new T1 with record + procedure P (Self : in out T1); + end record with Scoped_Primitives; + end P; + +Primitives can be marked optionally overriding, following Ada 2005 rules. +Inheritance model is single interitance of a class, multiple inheritance of interfaces. + +Interfaces +---------- + +Interfaces can now be specified with "interface record", but otherwise +operate as other interfaces (no concrete components or primitive): + +.. code-block:: ada + + package P is + type I is interface record + procedure P (Self : in out I) is abstract; + end record with Scoped_Primitives; + end P; + +Operators +--------- + +Operators can be declared as primitives: + +.. code-block:: ada + + package P is + type T1 is tagged record + function "=" (Left, Right : T1) return Boolean; + function "+" (Left, Right : T1) return T1; + end record with Scoped_Primitives; + + type T2 is new T1 with record + procedure "=" (Left : T2; Right : T1); + function "+" (Left : T2, Right : T1) return T1; + end record with Scoped_Primitives; + end P; + +Note that when overriding an operator, only the first parameter changes to the +current class type. + +Reference-level explanation +=========================== + +Rationale and alternatives +========================== + +Drawbacks +========= + + +Prior art +========= + +Unresolved questions +==================== + +Future possibilities +==================== + diff --git a/considered/rfc-oop-protected_visibility.md b/considered/rfc-oop-protected_visibility.md new file mode 100644 index 00000000..9af32d01 --- /dev/null +++ b/considered/rfc-oop-protected_visibility.md @@ -0,0 +1,226 @@ +- Feature Name: oop_protected_visibility +- Start Date: 2024-03-20 +- RFC PR: (leave this empty) +- RFC Issue: (leave this empty) + +Summary +======= + +This RFC introduces a way to obtain an intermediate level of visibility between the "public" and "private" levels, often refered to as "protected" in OOP paradigms. It is a useful level of visibility as will be argued below that is currently only possible to achieve by a perverse use of child packages in a way that forces a naming convention in a context where it doesn't make sense. The proposal is to recognize "protected" visibility as a first class citizen and liberate it from the naming constraints. While the proposal is proposed and argued in the context of OOP, it extends beyond OOP in Ada and applies to all declarations that are subject to the rules of visibility. + +Motivation +========== + +Many OOP paradigms define three levels of visibility associated with the members (fields and methods) of a class: + +- "public" visibility should be applied to members intended to be used by client code using the class. +- "private" visibility should be applied to members intended to be used only by the implementation of the class, and never by client code. +- "protected" visibility should be applied to members intended to be used by code extending the class. + +This model is valuable in the fact that it distinguishes 2 categories of code that can use a class: client code that is a mere User of the class, and extending code that defines a modified version of the class. + +Ada allows developers to implement visibility in the following manner: + +- Declarations in the public part of a package spec are visible to any other code. This matches the "public" visibility of popular OOP paradigms. +- Declarations in the private part of a package spec `P` are visible to the body of that spec, as well as to the specs of nested packages and non-nested child packages, *i.e.* packages with the prefix `P.` in their name. + +Ada-private visibility is a mix of OOP-private and OOP-protected visibility. Indeed extending code can be written into non-nested child packages named `P.*`. + +However that naming constraint is often undesirable and is an obstacle to creating libraries that extend other libraries. + +Consider the following example. A library defines the following type intended for logging: + +```ada +package SomeLib.Logging is + type Logger is tagged null record; + -- A logger that writes message to a logging stream + + procedure Log (Self : Logger; Msg : String); + +private + + procedure Write_Line (Self : Logger; Line : String); + -- primitive intended for use by the implementation, or by extending code + procedure Write (Self : Logger; Text : String); + -- primitive intended for use by the implementation, or by extending code + +end SomeLib.Logging; +``` + +Now if someone wanted to extend the `Logger` type to create a logger with extended functionality, they can do it as follows: + +```ada +with SomeLib.Logging; + +package MyLib.TimestampedLogging is + type TimestampedLogger is new SomeLib.Logging.Logger with null record; + -- A logger that logs messages to a stream, prefixed with a timestamp + + overriding procedure Log (Self: TimestampedLogger; Msg : String); +end MyLib.TimestampedLogging; +``` + +In the implementation of the `TimestampedLogger` ideally we would like to reuse primitives of the parent type as follows: + +```ada +with SomeLib.Logging; + +package body MyLib.TimestampedLogging is + overriding procedure Log (Self: TimestampedLogger; Msg : String) is + Timestamp : String := "[Timestamp obtained somehow]"; + begin + TimestampedLogger'Class (Self).Write (Timestamp & " "); -- error: no selector "Write" for type "TimestampedLogger'Class" defined at mylib-timestampedlogging.ads:4 + SomeLib.Logging.Logger (Self).Write_Line (Msg); -- error: no selector "Write_Line" for type "Logger" defined at somelib-logging.ads:2 + end Log; +end MyLib.TimestampedLogging; +``` + +However we cannot do so because standard Ada visibility does not allow it. + +It is possible to declare the extended type in a child package as follows: + +```ada +-- src/mylib/somelib-logging-timestampedlogging.ads +with SomeLib.Logging; + +package SomeLib.Logging.TimestampedLogging is + type TimestampedLogger is new SomeLib.Logging.Logger with null record; + -- A logger that logs messages to a stream, prefixed with a timestamp + + overriding procedure Log (Self: TimestampedLogger; Msg : String); +end SomeLib.Logging.TimestampedLogging; + + +-- src/mylib/somelib-logging-timestampedlogging.adb +package body SomeLib.Logging.TimestampedLogging is + overriding procedure Log (Self: TimestampedLogger; Msg : String) is + Timestamp : String := "[Timestamp obtained somehow]"; + begin + TimestampedLogger'Class (Self).Write (Timestamp & " "); + SomeLib.Logging.Logger (Self).Write_Line (Msg); + end Log; +end SomeLib.Logging.TimestampedLogging; +``` + +But that forces a naming convention that might be undersirable. It seems inappropriate that a library called `mylib` with all provided packages named `MyLib.*` has to use the name `SomeLib.*` to provide extended functionality. + +Guide-level explanation +======================= + +The proposal is to introduce an aspect `Private_Visibility_On` that applies to a package spec and gives it visibility over the private part of another package, *e.g.* + +```ada +with SomeLib.Logging; + +package MyLib.TimestampedLogging +with + Private_Visibility_On => SomeLib.Logging +is + type TimestampedLogger is new SomeLib.Logging.Logger with null record; + -- A logger that logs messages to a stream, prefixed with a timestamp + + overriding procedure Log (Self: TimestampedLogger; Msg : String); +end MyLib.TimestampedLogging; +``` + +The resulting visibility in `MyLib.TimestampedLogging` is the same as if package `MyLib.TimestampedLogging` was a child package of `SomeLib.Logging`. As a result the following package body becomes valid: + +```ada +with SomeLib.Logging; + +package body MyLib.TimestampedLogging is + overriding procedure Log (Self: TimestampedLogger; Msg : String) is + Timestamp : String := "[Timestamp obtained somehow]"; + begin + TimestampedLogger'Class (Self).Write (Timestamp & " "); + SomeLib.Logging.Logger (Self).Write_Line (Msg); + end Log; +end MyLib.TimestampedLogging; +``` + +Thanks to the `Private_Visibility_On` aspect, the package body has visibility over the private part of the spec of `SomeLib.Logging` and thus can access the primitives `Write` and `Write_Line`. We have thus achieved "protected" visibility in the terms used by popular OOP paradigms. + +The effect of the `Private_Visibility_On` aspect is limited to the package spec where it is introduced and the corresponding package body. This means that non-nested child packages of `MyLib.TimestampedLogging` do not obtain visibility over the private part of `SomeLib.Logging`. This keeps the feature conservative in the attribution of elevated visibility and limits it to a minimum that is sufficient in practice. + +Reference-level explanation +=========================== + +The semantics of `Private_Visibility_On` is proposed to be equivalent to the visibility given to non-nested child packages. This simplifies the definition of this semantics as well as its implementation. + +Moreover the proposed semantics is to limit the elevated visibility to the package spec where it is used and the corresponding body. That also fosters simplicity and reduces impact. + +Rationale and alternatives +========================== + +This design was prefered because it leverages an existing possibility in the language, *i.e.* using a non-nested child package to gain visibility over the private part of a parent package, and recognizes it as a feature for achieving what is refered to as "protected" visibility in popular OOP paradigms. + +Drawbacks +========= + +The proposed makes it easier for any code to obtain visibility over private declarations that were not intended to be visible by the author of the package. There are two reasons why this is acceptable. + +The first reason is that it was already possible to do that before this proposal by using non-nested child packages. Even when considering separate libraries, there is nothing that prevents one library from declaring a child package of another library. The only thing that this proposal is changing is to lift the naming constraint on the package that needs access to the private declarations. + +The second reason is that visibility should not be a way to achieve security, *i.e.* prevent data from being accessed by unauthorized parties. Visibility is a way to convey the intent of the author in making a declaration only revelant to the implementation of a component and not to users of that component. However when other developers which to extend the implementation of that component themselves, it is justified for them to obtain visiblity over the private declarations intended for that purpose. + +Prior art +========= + +**TBD** + +Discuss prior art, both the good and the bad, in relation to this proposal. + +- For language, library, and compiler proposals: Does this feature exist in + other programming languages and what experience have their community had? + +- Papers: Are there any published papers or great posts that discuss this? If + you have some relevant papers to refer to, this can serve as a more detailed + theoretical background. + +This section is intended to encourage you as an author to think about the +lessons from other languages, provide readers of your RFC with a fuller +picture. + +If there is no prior art, that is fine - your ideas are interesting to us +whether they are brand new or if it is an adaptation from other languages. + +Note that while precedent set by other languages is some motivation, it does +not on its own motivate an RFC. + +Unresolved questions +==================== + +**TBD** + +- What parts of the design do you expect to resolve through the RFC process + before this gets merged? + +- What parts of the design do you expect to resolve through the implementation + of this feature before stabilization? + +- What related issues do you consider out of scope for this RFC that could be + addressed in the future independently of the solution that comes out of this + RFC? + +Future possibilities +==================== + +**TBD** + +Think about what the natural extension and evolution of your proposal would +be and how it would affect the language and project as a whole in a holistic +way. Try to use this section as a tool to more fully consider all possible +interactions with the project and language in your proposal. +Also consider how the this all fits into the roadmap for the project +and of the relevant sub-team. + +This is also a good place to "dump ideas", if they are out of scope for the +RFC you are writing but otherwise related. + +If you have tried and cannot think of any future possibilities, +you may simply state that you cannot think of anything. + +Note that having something written down in the future-possibilities section +is not a reason to accept the current or a future RFC; such notes should be +in the section on motivation or rationale in this or subsequent RFCs. +The section merely provides additional information. diff --git a/considered/rfc-oop.rst b/considered/rfc-oop.rst new file mode 100644 index 00000000..9c2a97ac --- /dev/null +++ b/considered/rfc-oop.rst @@ -0,0 +1,140 @@ +- Feature Name: Standard OOP model +- Start Date: May 5th 2020 +- RFC PR: +- RFC Issue: + +Summary +======= + +The objective of this proposal is to draft an OOP model for a hypothetical new version of Ada, closer to the models languages +such as C++ and Java developers are accustomed to, fixing a number of oddities and vulnerabilities of the current Ada language. + +Motivation +========== + +The current Ada OOP model is source of a number of confusions and errors from people accustomed to OOP, in particular in +other languages such as C++ or Java. This proposal aims at adjusting the model to implement missing concept, fix Ada-specific +vulnerabilities and retain some of the Ada advantages. + +This proposal also attempts at keeping capabilities that do not need to be specifically removed to fit within a more intuitive +OOP model, such as e.g. discriminants and aggregates. + +Experience shows that whenever possible, capabilities that can be enabled to regular records should as well. A good exemple of that +is the trend observed of people declaring Ada types tagged just for the purpose of having access to prefix notation, while such notation +does not require inheritance or dispatching. + +Guide-level explanation +======================= + +Reference-level explanation +=========================== + +The proposal is split between the following parts: + +Currently ready for prototyping: + +- `Constructors `_ +- `Destructors `_ +- `Super cast `_ +- `First Controlling `_ + +Under discussion: + +- `Component declaration `_ +- `Primitives declaration `_ +- `Attributes declaration `_ +- `Dispatching `_ +- `New access types `_ +- `Final classes and primitives `_ +- `Body restriction lift `_ +- `Obsolete features `_ + + +Rationale and alternatives +========================== + +Global object hierarchy +----------------------- + +We consider a global object hierarchy: + +All class object implicitely derives from a top level object, +Ada.Classes.Object, defined as follows: + +.. code-block:: ada + + package Ada.Classes is + type Object is class record + function Image (Self : Object) return String; + + function Hash (Self : Object) return Integer; + end Object; + end Ada.Classes; + +Other top level primitives may be needed here. + +However, there are several elements that argue against this design: + +- the language that implement that (Java) initially introduced that as a way + to workaround lack of genericity and `void *` notation. Ada provides + genericity, and in the extreme cases where `void *` is required, + `System.Address` is a reasonable replacement. +- As opposed to Java, many types in Ada are not objects. This concept would then + be far less ubiquitous. + +As a consequence, the identified use case ended up being to narrow to justify +the effort. + + + +Drawbacks +========= + + +Prior art +========= + +This proposal is heavily influence by C++, C# and Java (which arguably have influenced one another quite a lot). + +Unresolved questions +==================== + +This proposal relies on the unified record syntax proposal, and will need to be updated in light of potential +revamped access model and finalization models. + +A number of the capabilities of the standard run-time library rely today on tagged type. A thorough review should be made to +identify which should be removed (e.g. controlled type), which should be migrated, and which can actually be implemented without +relying on classes altogether (things such as streams or pools come to mind). The removal of coextensions types also supposes a +different model for general iteration, as it currently relies on user-defined references (implemented through coextensions). + +Future possibilities +==================== + +Some of the notations introduced could be extended to other types, such as protected or tasks types. + +The "with private;" notation should also be extended to nested packages, allowing to differenciate to nest the private part of a +nested package in the private part of its enclosing package. + +The scoped primitive notation is currently specific to record types. It could be extended to all types (which would have the effect +or re-enabling the possibility to complete a simple private type by a record). + +Move semantics as defined by C++ would be a very useful extension of the current +model, but has broader applicability and should be discussed separately. + +Given the fact that a class is now a syntactical scope, we could also consider +to allow classes to be their own compilation units. This would fit a number +of architectures inherited from other programming languages, which require in +Ada to create an package for a single type. + +A new syntax was considered to allow to override assignment: + +.. code-block:: ada + + type T is null record; + + procedure ":=" (Destination : in out T; Source : T); + +The difference with copy constructor was that it works on a previously +initialized type. At this stage however, the assignment semantic will be +destroying the destination object then calling the copy constructor with the +source in parameter. \ No newline at end of file