From mboxrd@z Thu Jan 1 00:00:00 1970 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on polar.synack.me X-Spam-Level: X-Spam-Status: No, score=-0.3 required=5.0 tests=BAYES_00, REPLYTO_WITHOUT_TO_CC autolearn=no autolearn_force=no version=3.4.4 X-Google-Language: ENGLISH,ASCII-7-bit X-Google-Thread: 103376,d1df6bc3799debed X-Google-Attributes: gid103376,public From: "John G. Volan" Subject: Re: Syntax for tagged record types (was Re: Not intended for use in medical,) Date: 1997/05/23 Message-ID: <33867D87.21BA@sprintmail.com> X-Deja-AN: 243520644 References: <3.0.32.19970423164855.00746db8@mail.4dcomm.com> <33828299.2A3@world.std.com> <33850721.49BF@sprintmail.com> Organization: Sprint Internet Passport Reply-To: johnvolan@sprintmail.com Newsgroups: comp.lang.ada Date: 1997-05-23T00:00:00+00:00 List-Id: I (John Volan) wrote: >Another issue with discriminants as constructor parameters is that there >is no provision for having multiple overloaded constructors with >different sets of parameters. Jon S Anthony replied: > > This is a non issue for anything other than a limited type. You can > name your constructors whatever you want, they can take any number of > parameters and there can be any number of them. My reply: Just so folks won't be confused, what Jon's referring to here are ordinary Ada functions that happen to return a value of a given type, e.g. type Some_Value_Type is ... -- something non-limited function Make_Some_Value (... from one set of parameters ...) return Some_Value_Type; function Make_Some_Value (... from another set of parameters ...) return Some_Value_Type; ... etc. This then allows a user to construct a value of the given type during an object declaration, in the initialization part: Some_Value : Some_Value_Type := Make_Some_Value (...); The designer of the abstraction can even force users to always call one of these constructor functions, by giving the type an unknown discriminant part, thereby making it an "indefinite" type: type Some_Value_Type (<>) is ... private; -- something non-limited This doesn't mean the actual completion of this type has to have a discriminant, but it does make uninitialized variables illegal: Some_Value : Some_Value_Type; -- compiler error! indefinite type! This restriction is appropriate if the abstraction just doesn't include any notion of a "default" constructor. > For limited types, the only way around this is to hide them behind > access types for them. Right, when you deal with true object-oriented abstractions (as opposed to value-oriented abstract data types), you inevitably have to invoke reference semantics and the identity-vs-state issue. Here's a pattern I like to use for object-oriented types: type Some_Object_Type (<>) is tagged limited private; type Some_Object_Access_Type is access all Some_Object_Type'Class; function Make_Some_Object (... from one set of parameters ...) return Some_Object_Access_Type; function Make_Some_Object (... from another set of parameters ...) return Some_Object_Access_Type; ... procedure Primitive_Operation (Some_Object : access Some_Object_Type; ... parameters ... ); ... etc. Note that: (1) Some_Object_Type is limited, so assignment can't be used to casually copy the state of one object into another. (Althought the abstraction designer could certainly choose to provide subprograms that do deep copies, but calling these subprograms wouldn't be "casual".) (2) On the other hand, Some_Object_Access_Type is (of course) _not_ limited, so assignments can be used to copy object _identities_ (access values, "references" to objects) as much as you please. (3) Some_Object_Type has an unknown discriminant part, so a direct declaration for an object of this type is illegal: Some_Object : Some_Object_Type; -- compiler error! indefinite type! And the users can't get around this by trying to initialize the object to something -- because it's a limited type, so assignment is illegal. Instead, the users are forced to go through one of the constructors defined for the abstraction: Some_Object : Some_Object_Access_Type := Make_Some_Object (...); But note that what we have here is only an _access_ to some object. The abstraction has complete control over where that object actually is. (The object will commonly be allocated on the heap, but that's not necessarily the only possibility.) (4) Since the constructor functions all return values of the access type and not the object type itself, these functions are _not_ primitive subprograms for the object type, so they will _not_ be inherited by derived types ("subclasses"). This is actually a good thing: It has frequently been argued that constructors should not be inheritable. (In fact, C++'s constructors aren't.) (5) If a subclass can't just inherit a constructor, and the parent class doesn't include a default constructor, then the only way the subclass can properly implement a constructor of its own is if it somehow has access to the implementation details of its parent class. That pretty much forces you to put your subclasses into child packages. Giving the base type an unknown discriminant also forces this, because it's illegal to derive a new type unless you can provide a constraint for the discriminant (or "see" that there really isn't one). To make it a little easier to implement child constructors, I like to use a pattern where, for every visible constructor _function_ which I provide to clients, I accompany it with a private constructor _procedure_, which the constructor function calls, and which child constructors can also call: package Base_Class is type Base_Type (<>) is tagged limited private; type Base_Access_Type is access all Base_Type'Class; function Make_Base (...some base set of parameters...) return Base_Access_Type; ... private type Base_Type is tagged limited record ... procedure Make_Base (Base : access Base_Type'Class; ...same base set of parameters...); ... end Base_Class; A Make_... function would typically allocate a new object from somewhere, then call the corresponding hidden Make_... procedure to initialize the object, then return the new access value. Note that I write my Make_... procedures as _classwide_ subprograms, so they aren't inheritable primitives any more than my Make_... functions. A subclass package would look something like this: package Base_Class.Derived_Class is type Derived_Type (<>) is new Base_Type with private; type Derived_Access_Type is access all Derived_Type'Class; function Make_Derived (...base parameters...plus others maybe...) return Derived_Access_Type; ... private type Derived_Type is new Base_Type with record ... procedure Make_Derived (Derived : access Derived_Type'Class; ...base parameters...plus others maybe...); ... end Base_Class.Derived_Class; The derived Make_... procedure for a subclass like this would typically call the base Make_... procedure for its parent class, and then would do whatever additionally initializations are needed for its type extension. Since the derived class is in a child package, it can "see" the hidden base Make_... procedure. And since the base-type object initialized by the base Make_... procedure is a classwide parameter, the derived Make_... procedure can pass its derived-type object into this parameter. > Then things again work just fine - except for > storage management issues. Hence, the need for GC. Limited types are > really extremely useful. Unfortunately, much of this is lost without > GC support. Agreed. ------------------------------------------------------------------------ Internet.Usenet.Put_Signature (Name => "John G. Volan", Home_Email => "johnvolan@sprintmail.com", Slogan => "Ada95: The World's *FIRST* International-Standard OOPL", Disclaimer => "These opinions were never defined, so using them " & "would be erroneous...or is that just nondeterministic now? :-) "); ------------------------------------------------------------------------