Home > database >  What is the non-synchronized equivalent to a protected type in Ada?
What is the non-synchronized equivalent to a protected type in Ada?

Time:09-22

Ada has a construct called "protected types", where you have a collection of variables and subprograms associated with a type, and the subprograms have implicit synchronization. These types can be instantiated and each instance will have its own memory where the variables live. This looks a lot like the class/object duality in mainstream OOP languages such as C and Java, minus inheritance, plus mandatory implicit synchronization.

Is there an equivalent to this construct, minus the synchronization? If not, what's the rationale behind this design choice?

To be entirely clear, I'm aware that Ada supports different styles of OOP without any kind of synchronization. My question is about the specific style of OOP I mentioned - as it is one of the most common styles found in mainstream languages, and is indeed also present in Ada in some form.

To further clarify the question, which had been intentionally (and misguidedly) left open-ended, I am aware that the answer is "packages". But then, consider the following:

  • We have packages, which are units containing variables and subprograms, of which several instances can be created
  • We have types, which are enums or projections/mod of built-it types (I know this is a very approximate definition, specifics don't really matter here)
  • We have protected types, which are... units containing variables and subprograms, of which several instances can be created. Plus, they have synchronization.

This begs the thought: why "protected types" and not "protected packages"? This thought is the origin of the present question.

CodePudding user response:

OOP is a set of concepts in programming without any dependence in a particular syntax. According to the Ada 95 Rationale: "Type extension in Ada 95 builds upon the existing Ada 83 concept of a derived type. In Ada 83, a derived type inherited the operations of its parent and could add new operations; however, it was not possible to add new components to the type. The whole mechanism was thus somewhat static. By contrast, in Ada 95 a derived type can also be extended to add new components."

In Ada a type is a type, independently of it providing OOP features or not. Ada 95 provided extension on top of other POO features already provided by Ada 83 types. The advantage of that is that you can turn easily a non-tagged type to a tagged type, if you later need type-extension, without affecting current uses of the type. This also avoids introducing hidden features in the OOP syntax, like friend classes (types sharing the package), static members (global package variables), the implicit this, or const at the end of a method to indicate that this object is not modified, etc.

Why protected types do not follow this pattern? They probably follow that of Ada 83 task types, but the latter don't have a private part, so it is still inconsistent. The design probably chose syntax of task types as inspiration, but added private part for efficiency (that was the main concern: "protected types allows a more efficient implementation of standard problems of shared data access").

CodePudding user response:

So this is an answer to the title of this question: "What is the non-synchronized equivalent to a protected type in Ada?"

I'm adding this mainly for people searching this question looking for the answer to the topic's title question.

Take a simple example protected type:

protected type My_Type is

    procedure Set_Value(Value : Integer);
    function Get_Value return Integer;
    
private

    The_Value : Integer := 0;
    
end My_Type;

protected body My_Type is

    procedure Set_Value(Value : Integer) is
    begin
        The_Value := Value;
    end Set_Value;
    
    function Get_Value return Integer is
    begin
        return The_Value;
    end Get_Value;
    
end My_Type;

The equivalent non synchronized version would be to use a record type (or a tagged record if you want type extension) within a package paired with the operations on that type:

package My_Types is 

    -- For type extension use:
    -- type My_Type is tagged private;
    type My_Type is private;
    procedure Set_Value(Self : in out My_Type; Value : Integer);
    function Get_Value(Self : My_Type) return Integer;
    
private

    -- For type extension use:
    -- type My_Type is tagged record
    type My_Type is record
        The_Value : Integer := 0;
    end record;
    
end My_Types;

package body My_Types is

    procedure Set_Value(Self : in out My_Type; Value : Integer) is
    begin
        Self.The_Value := Value;
    end Set_Value;
    
    function Get_Value(Self : My_Type) return Integer is
    begin
        return Self.The_Value;
    end Get_Value;
    -- Alternate syntax:
    -- function Get_Value(Self : My_Type) return Integer is (Self.The_Value);
    
end My_Types;

Operations declared within a package that operate on a type in the package before that type is frozen are "associated" to that type (Ada calls them primitive operations). This includes functions that return those types.

For the "why" each layout was chosen differently, I don't really know. It might be helpful to take a look at the bottom of the following page and look through all the comments/emails/discussions of the ARG (credit to Simon Wright for the initial link):

http://archive.adaic.com/standards/ada95.html

If I had to guess without fully reading those sections Simon pointed me to (I will get to reading them all the way through), I would wager it has to do with the fact that records existed as is before protected types existed and protected types were thought more of an extension of the tasking model, so they iterated on the task type layout for protected type. Some of what I did read(here and here) already led me to believe they ran into some existing issues (either technical or philosophical) trying to layout protected types more like records.

Note that protected types do not give the full set of "information hiding" capabilities as most programmers expect, such as public vs private member variables (only private for protected types).

Credit to Simon Wright for the links I provided

CodePudding user response:

The standard way to define a complete type (data operations) is Ada is with a package containing the type declaration (often private) and the subprograms for the type.

In general, encapsulation and information hiding (package) are orthogonal to types and subprograms in Ada. In many commonly used languages, encapsulation and information hiding are provided only by the class construct.

CodePudding user response:

This is a bit of a ramble round the topic ...

If you had a protected package, what parts of its contents would be synchronised? Any variable, spec or body? any type? child packages? And, to be able to create multiple instances of the package, it’d have to be generic. How then could you create an instance within a record? I think it needs to be a type.

As I understand it, there’s not really a parallel to package in C , so you’d have to say protected class Foo ... which seems hard to distinguish from a protected type.

Given packages, which already encapsulate everything else, I guess the design team could have gone with something like

type P is record
   ...
end record;
pragma Protected (P);

where primitive operations of P would be synchronised, but you then have the problem of clarity (primitiveness being easy to get wrong) and of visibility (you really don’t want any of the components of P being accessible from outside). What syntax do we use for entry operations? Protected types seem a reasonable compromise.

CodePudding user response:

Is there an equivalent to this construct, minus the synchronization? If not, what's the rationale behind this design choice?

Ok, the other answers are really quite good, but here's the simple answer: Ada defines a “type” as a set of values and a set of operations on those values; the notion of “subtype” is likewise defined as a type with an additional (possibly null) set of constraints on its values. — This leads to the ability to say “Subtype Natural is Integer 0..Integer'Last;” — In Ada 83 there was no way to add values to a type, but there was type-derivation where you could 'inherit' a type, possibly adding other operations and/or altering representational items. (Thus you could have “Type Native_Data is array(1..10, 1..200) of Integer;” and “Type External_Data is new Native_Data;” with "For External_Data'Convention use Fortran;"1 and convert between native and external formats via conversion: Data:= Native_Data( From_Disk(File => "Import.dat") ).)

So, Ada95 built atop type-derivation allowing more values which are the type-extension (as well as the more operations). — Ada95 also extended the library/compilation-units structure from a 'flat' notion to a hierarchical one, but the basic unit of organization was (and is still) the package.

Now, we get to protected types, protected types are synchronization types, the data encapsulated into the construct and manipulated via accessors and mutators — this construct is pretty much the bastard child of packages and tasks: it is structured reminiscent of the package and has the queue-like access (entries, functions. procedures) of tasks, albeit a bit more 'exposed'/explicit than the implicit nature of task entries and the rendezvous.

So then, what is a protected type without synchronization?
Simple, a regular type.

This begs the thought: why "protected types" and not "protected packages"?

While I'm sure that the above provides enough information for you to suss things out, the simple answer is this:
Packages are really interfaces (in the general notion, not the keyword/tagged-type notion) and namespaces: they declare the public view and also segregate the private implementation, as well as encapsulating the scope of the things within.

Thus a “Protected Package” would essentially be the protected type "but with namespacing" — and thus be a really redundant construct, not to mention that one of the motivating factors for protected types was the ability to drop the active thread of control required from tasks for synchronization: all that can be handled by the compiler inserting the proper queuing/bookkeeping around accesses without any of the complexity (and timing/scheduling impositions) that a task would require — so there would have to be special rules for a "protected package" either disallowing Task or requiring some special form, which would add complexity to the compiler.

1 — Fortran uses column-major ordering for its multidimensional arrays, Ada uses row-major ordering [I don't recall if this is required by the LRM]; this 'trick' allows you to have the compiler handle the "trans-positioning", as well as using the type-system to keep track of which is which. (You can use this with things like network-format vs native-format in protocols, too.)

  • Related