Maybe the title is hard to understand, what I mean is that I have a type who inherits operation from another, but only in the private view, and I want to raise the visibility of those operations to the public view.
package essay is
type Cursor (<>) is private;
procedure GoAhead (T: Tabletype; C: in out Cursor);
procedure GoBack (T: Tabletype; C: in out Cursor);
private
PACKAGE Lists IS NEW Lists_Generic(ElementType => Element);
type Cursor is new Lists.Position;
end essay;
GoAhead and GoBack are inherited by Cursor from Position (simple access type, but as a client of Lists_Generic I do not see that).
If I do not declare those operations publically, I can use gohead and goback without conversion in the body of Essay just fine.
But I want theĂą public, I loose access to the inherited version and have to provide bodies and convert to Position to use its procedures.
procedure GoBack (T: Tabletype; C: in out Cursor) is
begin
lists.GoBack (T.Data, Position(C));
end Goback;
Renaming doesn’t work, because they’re different types. Of course i can’t remove the conversion here, or we get infinite recursion.
Is there a more elegant solution ?
Oh I made a mistake, List and tabletype are not the same, no wonder I got it complaining.
But the question still remains:
procedure Main is
package essay is
type AA is private;
function "+" (A, B: AA) return AA;
private
type AA is new Integer;
end essay;
package body essay is
function "+" (A, B: AA) return AA
is (Integer (A) + Integer (B));
-- Can I do away with the conversion ?
end essay;
begin
null;
end main;
There’s a technique I’ve used to use generics+renames to do what you’re trying to do. It involves a little bit of breaking things down before building them up though.
Package Example_1 is
Procedure Some_Operation;
Procedure Other_Operation;
Private
Generic
Some_Parameter : Positive;
Procedure Generic_Operation;
Procedure Some_Operation is new Generic_Operation(3);
Procedure Other_Operation is new Generic_Operation(4);
End Example_1;
This isn’t allowed because the body of the operation is not allowed to be a generic instantiation… at least directly. This is where renames comes in:
Package Example_2 is
Procedure Some_Operation;
Procedure Other_Operation;
Private
Generic
Some_Parameter : Positive;
Procedure Generic_Operation;
Procedure Some_Op is new Generic_Operation(3);
Procedure Other_Op is new Generic_Operation(4);
Procedure Some_Operation renames Some_Op;
Procedure Other_Operation renames Other_Op;
End Example_2;
Now, with generics, there are the formal parameters to consider, but there’s a way that you can say “Given a tagged type X, Y is a descendent-type.”
Package Example_3 is
Type Example is tagged with private;
Private
Type Hidden_Parent is tagged null record;
Procedure Do_It( Object : in out Hidden_Parent );
Type Example is new Hidden_Parent with null record.
Procedure Do_It( Object : in out Example );
Generic
type Parent (<>) is tagged private;
type Child (<>) is new Parent;
Package Scaffolding is
Generic
with Procedure Op( Object : Parent );
Procedure Passthrough( Object : in out Child );
-- BODY:
-- Begin
-- Parent(Object).Op;
-- End Passthrough;
End Scaffolding;
Package Scaffold is new Scafolding
(Child => Example,
Parent => Hidden_Parrent
);
Procedure Actual_Do_It is new Scaffold.Passthrough( Do_It );
Procedure Do_It( Object : in out Example ) renames Actual_Do_It;
End Example_3;
This is an example of static polymorphism, and I’ve used this generic+renames+overloading to do manual static-dispatching for implementing type-conversion in an interpreter: here.
Your second exemple gives me nightmare: see I try to limit OOP as much as possible, for both my sake and the computer’s. I don’t understand the first exemple but it seems like I would need to modify the first original type’s package, which I can’t.
I wouldn’t know how to apply it, because there’s no type conflict involved… do you see how to transform my exemple ?
package essay is
type AA is private;
function "+" (A, B: AA) return AA;
private
type AA_Implementation is new Integer;
function Add (A, B: AA_Implementation) return AA_Implementation renames "+";
type AA is new AA_Implementation;
function "+" (A, B: AA) return AA renames Add;
end essay;
P.S. If you did not try to limit yourself you would notice that methods are reachable regardless visibility while subtypes lack this property. A subtype being a type, formally does not introduce a new type, which creates difficulties you observed.
Historically subtypes (types obtained by constraining) appeared in Ada 83 which was not OO.
Side note, I know you disagreed with my position back then, but a few months back (maybe?) I posted up a question asking thoughts on being able to annotate inherited subprograms in a spec (optionally). This would be one of the scenarios of application for that suggestion. I know I didn’t explain it well back then, but maybe this scenario helps make what I was saying more clear.
A body as renaming of a renaming inherited from an intermediary type, to get back the same operator’s name… takes just two lines and I don’t have to change the original package. Perfect.
Btw it’s not so much that I limit myself, but that I my thought patterns suffer a bit from rigidity. Changing as we speak.
There are Convert functions from any type to any other type in the VM.
The conversion function takes an element (type+value object), and a target type.
The “From” type is taken from the element.
The “To” type is given in the parameter, and passed into the generic.
The generic takes each possible convert yielding the target type as a parameter, defaulted.
The generic’s body accesses the element’s data-field, yielding the value, passing it through the proper convert.
The generic is instantiated for every possible type in the VM.
We use overloading to have the correct conversion function called.
Because we know at the outset the from and to types, we can whittle things down: we operate using the
-- Here we're creating a function that takes the given value and returning
-- the value converted to the indicated type; since we're doing this for all
-- internal types, we have a "conversion" to any of those types.
Function Conversion is new Generic_Conversion( Integer_Type );
Function Conversion is new Generic_Conversion( Array_Type );
Function Conversion is new Generic_Conversion( Hash_Type );
Function Conversion is new Generic_Conversion( String_Type );
Function Conversion is new Generic_Conversion( Real_Type );
Function Conversion is new Generic_Conversion( Pointer_Type );
Function Conversion is new Generic_Conversion( Reference_Type );
Function Conversion is new Generic_Conversion( Fixed_Type );
Function Conversion is new Generic_Conversion( Boolean_Type );
Function Conversion is new Generic_Conversion( Func_Type );
begin
-- Manual static dispatching, YAY!
-- Generics reduced the total length of this function from 170 lines to less than 70.
-- Notice here how the return is an element of the TO type, and how
-- it creates a value which is the proper type and conversion;
-- NOTE: the element is a discriminated/variant record with a type-
-- discriminant and a value; the value's name is RT_TYPENAME.
-- Because RT_TYPENAME has a particular type that a Conversion
-- can produce, that Conversion is used, thus selecting the right function.
return Result : Representation:= new Internal_Representation'
(case To is
when RT_Integer => (Internal_Type => RT_Integer, Integer_Value => Conversion),
when RT_Array => (Internal_Type => RT_Array, Array_Value => Conversion),
when RT_Hash => (Internal_Type => RT_Hash, Hash_Value => Conversion),
when RT_String => (Internal_Type => RT_String, String_Value => Conversion),
when RT_Real => (Internal_Type => RT_Real, Real_Value => Conversion),
when RT_Pointer => (Internal_Type => RT_Pointer, Pointer_Value => Conversion),
when RT_Reference => (Internal_Type => RT_Reference, Reference_Value => Conversion),
when RT_Fixed => (Internal_Type => RT_Fixed, Fixed_Value => Conversion),
when RT_Boolean => (Internal_Type => RT_Boolean, Boolean_Value => Conversion),
when RT_Func => (Internal_Type => RT_Func, Func_Value => Conversion)
);
End Convert;
Not sure what you’re doing, but it seems to me to be close to fighting the language. And some of the solutions below are the sort of thing you wouldn’t want to find while you’re doing maintenance.
If you want something to be publicly visible, make it publicly visible.
Ada 83 was object-based not object-oriented.
As for inheritance, well, you are free to stop using all Ada 83 types and subtypes and go back to FORTRAN IV. Because all of them inherit representation and operations. I seriously encourage you not to inherit +,-,/,* and binary representation of numeric types and implement them from scratch!
There is nothing wrong in hiding concrete implementation and operations you do not want to expose. It is a good software engineering practice.
There are two approaches to this:
Declare operations you want to expose and implement them privately. This is what the OP wanted, done in a reasonably easy way.
Specialization: inherit everything and declare operations you want to hide abstract. This is a more tedious and less clear way.
With tagged type this quite straightforward as you can declare an interface that would include exactly the things you need. Then you inherit from the interface publicly. Privately you inherit from a concrete type implementing the interface. Done.
The OP tried to declare such an interface in the public package part: a type with a dyadic operation +. Nothing more, nothing less. But with non-tagged types interfaces are implicit and automated which produces the problem.
Part of the problem is OO isn’t properly defined. Oriented is described by some authors as a differentiating term but not universally. Object Oriented Design applies perfectly well to Ada 83 with good privacy features available as required. You didn’t say OOP which brings the kind of inheritance that encourages unnatural early abstraction and complexity. Putting Adas subtyping in the same category is simply facetious.
Thinking about it object based isn’t right either because you could very well follow the procedural model whilst using features more often associated with OOP, such as private.
But that’s just it: the object based design doesn’t need dispatching; even the “inherited” operations are all static.
The reason that you’re so willing to say that it’s not well defined isn’t because it’s not well defined, but because Ada properly separates those concepts (the “four pillars of OOP”) into orthogonal features, just like you gave an example for: data-hiding.
This is alsowhy Ada is superior as an introduction to OOP: because those concepts are separate, you can introduce them discretely and in a more purposeful manner, rather than trying to explain all of OOP in a go to the student.
I didn’t mean in the Ada language but rather in the industry. OO predates the “four pillars” and Ada and then there is message passing which I believe a small talk author said was the most important part. Some say go has oo features whilst other vehemently oppose that. There certainly seems to be a shift away from inheritance right now though. I think Ada 83 hit a sweet spot but I am open to having my mind changed.
It is more or less established that OO refers to dynamic polymorphism. Be it message passing or dispatch.
Ada 83 had only static polymorphism in two forms:
Ad-hoc = overloading
Parametric = generics
Ada 95 added dynamic polymorphism in the form of tagged types.
I just wanted to add here that I believe Ada83 did have inheritance. Type derivation (which Ada83 supports I believe) is inheritance without type extension (with Ada95 added). All primitive operations are inherited with type derivation. I know a lot of folks equate type extension with inheritance casually, but they are separate things mechanically.
It’s similar with dynamic operation dispatch and type extension. People often marry the two together. And while I am not familiar of any languages that have type extension and not dynamic operation dispatching, there are definitely languages with dynamic operation dispatch and no type extension.
It’s similar with dynamic operation dispatch and type extension
They are unrelated. Type extension controls representation. Dispatch controls polymorphic operations constructed of multiple bodies. Both representation and overriding the body are specific cases.
The type extension was chosen largely for implementation/laziness reasons, because it allows view conversions between the parent and derived types. Otherwise the conversion must be full. E.g. if Integer would ever be tagged then the tag must be kept out of Integer representation and conversions must create new objects passed by value. This would assume that only Integer’Class would have a tag,
Similarly overriding is not the only way. E.g. in Initialize and Finalize we would really wanted rather extend the body rather than override it completely.
BTW, Ada 83 subtypes do not change the representation which is a serious limitation. You, for example, cannot make an access type a subtype of the target type and thus eliminate the suffix “.all”. [Ada 83 hard-wired this in the cases of arrays and records.] Implicit dereference was added much later as a crude hack for tagged types only.
How would you write that ?
As for the primitive hiding trick, this is exactly what should figure in a list of “how to do”, useful practices not obvious at all from the RM. Heck, I only found those paragraphs in the AARM.
There is this list, in French: Articles techniques
It deserves growing (and translating, which I can do). You can all submit to me your ideas of tricks or little known / underused features, with some exemple and explanation, to add to this list.
Yep, that’s what I was saying in the post your were quoting. I was saying how people often marry the two in their heads, but they are separate things.