Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Proposal: Constructors, Assignment [LONG]

9 views
Skip to first unread message

Dmitry A. Kazakov

unread,
Dec 24, 2002, 6:16:38 AM12/24/02
to
The proposal possibly solves the problems:

1. Constructors with parameters for all user types
2. Initialization of limited types
3. Assignments for all user types
4. Ability to do things like stream attributes
5. Expressions for discriminants hidden within a constructor

The general idea is to split constructor (and assignment) into two
separate subroutines. One is used to get constraints, another is to
finish construction. The subroutine for getting constraints returns them
as an anonymous record type (an attribute is used to denote it) with the
discriminants set to the constraints.

Example1:

type Socket (Host_Name_Length : Positive) is tagged limited
record
Host_Name : String (1..Host_Name_Length);
. . .; -- Other components
end record;
function Get_Constraint (Host_Name : String)
return Socket'Constraint;
for Socket'Get_Constraints use Get_Constraint;
procedure Initialize (Object : in out Socket; Host_Name : String);
for Socket'Initialize use Initialize;

[ Maybe to prevent "syntactic sugar diabetes", we could allow to override
these and all others subroutine attributes just like:
function Socket'Get_Constraints (Host_Name : String)
return Socket'Constraint;
]
...
function Get_Constraint (Host_Name : String)
return Socket'Constraint is
begin
return (Host_Name => Host_Name'Length);
end Get_Constraint;
...
procedure Initialize
(Object : in out Socket; Host_Name : String) is
begin
Object.Name := Host_Name;
Open_Connection (Host_Name);
end Initialize;
...
Connection : Socket ("127.0.0.1");

Example2:

type Char_String is array (Integer range <>) of Unsigned_8;
function Get_Bounds (Text : String)
return Char_String'Constraints;
for Char_String'Get_Constraints use Get_Bounds;
procedure Set (Object : in out Char_String; Text : String);
for Char_String'Initialize use Set;
...
function Get_Bounds (Text : String)
return Char_String'Constraints is
begin
return (First_1 => 1, Last_1 => Text'Length);
end Get_Bounds;
...
procedure Set (Object : in out Char_String; Text : String) is
To : Integer := Object'First;
begin
for From in Text'Range loop
Object (To) := Unsigned_8 (Character'Pos (Text (From)));
end loop;
end Set;
...
X : Char_String ("some text"); -- Constructor
Y : Char_String := "some text"; -- Assignment

More formal

1. Attribute S'Constraints.

For any subtype S there is an attribute S'Constraints, defined as an
anonymous record type:

type S'Constraints [(<the-list-of-constraints>)] is null record;

[VARIANT for any tagged type]

1.1 For a constrained type <the-list-of-constraints> is empty.
1.2 For an unconstrained array type it is the array bounds (named in
some nice way).
1.3 For an unconstrained discriminated record, task, protected type it is
the discriminant part of the type.
1.4 For a class-wide type it is Tag.

For example:

type User (Name_Length : Positive) is tagged record
Name : String (1..Name_Length);
ID : User_ID;
end record;

User'Constraint is
type * (Name_Length : Positive) is null record;

User'Class'Constraint is
type * (Type_Tag : Tag) is null record;

2. Attribute S'Get_Constraints denotes a set of overloaded functions
having the following parameter profiles:

function S'Get_Constraints [(<parameter-list>)]
return S'Constraints;

2.1 For a constrained type:
function S'Get_Constraints return S'Constraints;
is defined and returns null record;

2.2 For an unconstrained array type:
function S'Get_Constraints (<bounds-list>)
return S'Constraints;
is defined and returns S'Constraints with the discriminants set to the
bounds from the list.

2.3 For an unconstrained discriminated type
function S'Get_Constraints (<discriminant-list>)
return S'Constraints;
is defined and returns S'Constraint with discriminants set from the
list.

2.4. For a class-wide type it is not defined. It can be defined
(S'Class'Get_Constraint), then S'Get_Constraints and S'Initialize have
to be defined as well.

Additionally for non-limited types:
function S'Get_Constraints (Source : S)
return S'Constraints;
is defined and returns S'Constraints with the discriminants extracted
from the constraints of Source

When a derived type overrides discriminants, it has to override
S'Get_Constraints as well.

3. Attribute S'Initialize denotes a set of overloaded procedures:

procedure S'Initialize (Object : in out S [; (<parameter-list>)]);

4. Constructors

4.1 Specific types. A constructor is defined if a pair
S'Get_Constraints, S'Initialize is defined such that they have
conformant parameter lists, the first parameter of S'Initialize is not
counted.

For a specific subtype S the constructor works as follows:

1. S'Get_Constraint is called to get all necessary constraints
2. Memory is allocated according to the constraints
3. S'Initialize is called to finish construction

4.2 Class-wide types. A constructor is defined if
S'Class'Get_Constraints is defined (and thus S'Get_Constraints and
S'Initialize are defined for all descendants of S). For a class-wide
subtype S'Class the constructor works as follows:

1. S'Class'Get_Constraint is called to get tag
2. Dispatching call to S'Get_Constraint [to get the constraints]
3. Memory is allocated according to the constraints
4. Dispatching call to S'Initialize [to finish construction]

4.3 Any constructor of a subtype S can be used in:

Object_Of_S : S (<parameter-list>);
new S ((<parameter-list>);
subtype Subtype_Of_S is S ((<parameter-list>);

5. Assignment

A constructor of S with the parameter list consisting of single
parameter [VARIANT having the type S] is called copy-constructor.
For a non-limited type for each copy-constructor an assignment
is generated:

5.1 Specific types

1. S'Get_Constraint (Source) is called to get new constraints
2. New constraints are checked against the constraints of Target
3. Target is finalized
4. Constraints are overridden
5. S'Initialize (Target, Source) is called

5.2 Class-wide types

1. S'Class'Get_Constraint (Source) is called to get new tag
2. The new tag is checked against Target'Tag
3. S'Get_Constraint (Source) is called to get new constraints

4. New constraints are checked against the constraints of Target
5. Target is finalized
6. Tag and constraints are overridden
7. S'Initialize (Target, Source) is called

--
Regards,
Dmitry A. Kazakov
www.dmitry-kazakov.de

Nick Roberts

unread,
Dec 26, 2002, 5:11:42 PM12/26/02
to
Forgive me for not quoting Dmitry's post.

I take the view that the 'object constructor problem' that Ada is sometimes
accused of suffering from is nearly always solved (in a better way than
languages which have special constructor functions) by the old chestnut of:
declaring the type as private (and indefinite) in its own package
specification; providing whichever constructor functions as primitive
operations of this type are appropriate.

The implementation of the type must use an unsophisticated discriminant
scheme, but this is hidden, and the constructor functions translate from the
appropriate parameters of construction to the implementational ones. The
fact that the result of a function is permitted to be of an indefinite type
allows the user of the package to declare an object of the type without a
constraint, and initialise it using the appropriate function (the result of
which provides the constraint).

I'm pretty sure Dmitry is well aware of this technique. However, I think
many members of the ARG would feel that the proposer of a functional
construction scheme (for the next language revision) needs to demonstrate
some pretty significant advantage the proposal has over the above general
approach.

Example 1 that Dmitry gave might be rehashed thus:


package Session_Management is

type Internet_Session(<>) is private;

function Open (Host: in String) return Internet_Session;

...

private

type Internet_Session (
Host_Name_Length: Natural
Traceback_Length: Natural) is
new Ada.Finalization.Controlled with
record
Host_Name: String(1..Host_Name_Length);
Traceback: ?.Node_Array(1..Traceback_Length);
Connection: ?.IP_Connection
...
end record;

end;

package body Session_Management is

function Open (Host: in String) return Internet_Session;
begin
Open_Connection(Session.Connection,Host);
declare
Result: Internet_Session := (
Host'Length,
Get_Node_Count(Session.Connection),
... );
begin
...
return Result;
end;
exception
...
end Open;

...

end Session_Management;


I've indulged myself in adding a detail -- the storage of a node
traceback -- to illustrate the idea that not just the parameters of a
constructor function can directly affect the structure of the type, but also
any arbitrary circumstances of its construction (at the time it is
constructed). I've also indulged myself in changing the names of things a
bit. These are only a minor touches.

Possibly the most relevant point is that the type (Internet_Session) is not
limited, and cannot be (because an initialisation is required and limited
types cannot be initialised).

If a limited type is required (and it probably is, realistically, for this
example), the typical technique is to make the type's implementation an
access value (referencing a totally private 'payload' type which actually
contains the relevant data, and which can be indefinite). The public
specification of the type can then be definite, so that the user can declare
an object of the type without having to initialise it, and then maybe call a
procedure to get it into a proper (initial) state (which may well involve
allocating an object of the payload type). Of course, this is usually
precisely the situation for the Ada.*_IO.File_Type types.

It could be argued that the indirection of access this causes is
inefficient, but not (or very rarely) realistically, in my opinion.

Hope this all makes sense!

--
Nick Roberts


Dmitry A. Kazakov

unread,
Dec 27, 2002, 12:43:28 PM12/27/02
to
Nick Roberts wrote:

The first objection is that this schema requires maintenance of two
unrelated (to the compiler) hierarchies of types: the implementation types
and the handles to them. (*)

So, let we want to derive from Internet_Session, say,
My_Proprietary_Protocol_Session?

--
The idea that a constructor is just a function is IMO badly wrong. Limited
types is just one example why. Another example is class-wide types. How to
construct a class-wide object from scratch? To write a class-wide function?
But then it will never ever dispatch. A dispatching one? But the type is
unknown and there is no object. This is why Ada's stream attributes are
made hard-wired. This is also why assignment is a problem.

And of course, an unnecessary language complexity. This and that
work-arounds for initialization, construction, assignment etc, very often
lead a programmer to unexpected and undesired side effects to the design.

------------------
(*) Ada offers no tools to ease this task, when the handle type is not an
anonymous access type. [which BTW is not allowed to be OUT, INOUT or
result]

It is definitely necessary to have something to solve this, but no language
I know addresses this. And it is not very clear how to do it anyway.

--
Merry Christmas,
Dmitry A. Kazakov
www.dmitry-kazakov.de

Randy Brukardt

unread,
Dec 27, 2002, 3:17:03 PM12/27/02
to
I think that for this proposal to get serious consideration, you'll have
to give rules to plug the many holes in it:

-- You'd need to carefully explain why solutions based on functions (as
Nick Roberts described) are not sufficient;

-- You'll need to explain how this works for controlled types (an
incompatibility there is simply not going to be tolerated);

-- You'll need to determine the rules for these in generics. Your
current description would be a giant contract model violation, and that
needs to be fixed. In particular, for a generic formal private type:
generic
type Priv is private;
what 'Get_Constructor and 'Initialize routines can be called? The fact
that these are different in profile is simply not going to fly here (and
I doubt that banning the use of constructors in generic bodies is going
to be an acceptable solution).

-- You'll need to explain how this works in the face of disappearing
components on an assignment. That has been the problem that has killed
all previous proposals like this one. See
http://www.adaic.org/standards/95aarm/html/AA-7-6.html, paragraphs
17.a-17.h for a discussion.


I'd prefer making the function solution work. There is a proposal to
make it possible to initialize limited objects with a function call,
which would eliminate that objection.

Your other objection:

>The idea that a constructor is just a function is IMO badly wrong.
Limited
>types is just one example why. Another example is class-wide types. How
to
>construct a class-wide object from scratch? To write a class-wide
function?
>But then it will never ever dispatch. A dispatching one? But the type
is
>unknown and there is no object. This is why Ada's stream attributes are
>made hard-wired. This is also why assignment is a problem.


The bug, IMHO, is that you can't write T'Class'Input in Ada. Its
terrible that magic is needed there, because its clear that there are
other cases where that sort of code would be useful.

We investigated some solutions to that problem in the past. The primary
problem is how to get the object created for a particular value of
Ada.Tags.Tag. A direct approach doesn't work, because you don't know how
to initialize the discriminants (they can be different for extensions
than for the base type). However, an approach based on using the tag
value to control dispatching of a function (rather than a result object)
does work. And it is very simple to implement (as compilers are
essentially dispatching on a value of Ada.Tags.Tag anyway). However,
most people thought that inventing syntax to solve this problem was too
heavy, and there really isn't any alternative. So nothing much has been
done on this problem.

The basic idea is to somehow figure a value of Ada.Tags.Tag, then use it
to control dispatching on a function that returns the correct kind of
object. This requires a special call:

Obj : T'Class := Func (<args>) use Tag_Value;

With these two capabilities, I believe that functions provide
appropriate constructors, and you don't necessarily need to use a
'handle' implementation. (I suspect that in practice, you often will
want to use a handle implementation anyway, but I certainly agree that
you shouldn't be forced into it.) Leveraging existing language features
is far preferable to inventing a whole new mechanism.

Randy Brukardt.


Dmitry A. Kazakov

unread,
Dec 29, 2002, 8:43:07 AM12/29/02
to
Randy Brukardt wrote:

Thank you for the response.

> I think that for this proposal to get serious consideration, you'll have
> to give rules to plug the many holes in it:
>
> -- You'd need to carefully explain why solutions based on functions (as
> Nick Roberts described) are not sufficient;

They cannot work when no assignment exists. Otherwise one should invent a
sort of pickwickian assignment.

> -- You'll need to explain how this works for controlled types (an
> incompatibility there is simply not going to be tolerated);

I do not think that there is any incompatibility. Initialize can be treated
as an overriding of 'Initialize.

> -- You'll need to determine the rules for these in generics. Your
> current description would be a giant contract model violation, and that
> needs to be fixed. In particular, for a generic formal private type:
> generic
> type Priv is private;
> what 'Get_Constructor and 'Initialize routines can be called? The fact
> that these are different in profile is simply not going to fly here (and
> I doubt that banning the use of constructors in generic bodies is going
> to be an acceptable solution).

It is not a ban. It is just a contract. Priv is private, as such it has only
the standard copy-constructor (because there is an assignment). No more.
Why should we expect that every user-defined operation on any possible
actual for Priv should be visible in the generic body? If that is an intent
then one should do:

generic
type Priv is new Type_That_Has_Interesting_Constructors with private;

Here Priv can use all primitive operations defined on
Type_That_Has_Interesting_Constructors and user-defined constructors too.

I suppose the fact that this works for tagged types only is not a fault of
my proposal. (:-))

An we still can have nasty, but working:

generic
type Priv is private;

with function My_Favorite_Operation (X : Priv) is <>;
...
with function Priv'Get_Constraints (...) return ... is <>;
with procedure Priv'Initialize (...) is <>;

> -- You'll need to explain how this works in the face of disappearing
> components on an assignment. That has been the problem that has killed
> all previous proposals like this one. See
> http://www.adaic.org/standards/95aarm/html/AA-7-6.html, paragraphs
> 17.a-17.h for a discussion.

It works same way it works for the controlled types. Compare:

A. Controlled type assignment:

A.1 Check constraints (obtained from source)
A.2 Finalize target
A.3 Copy bit-wise
A.4 Call Adjust

B. Assignment generated from a constructor

B.1 Get constraints from the parameters (call to 'Get_Constraints)
B.2 Check constraints
B.3 Finalize target
B.5 Call 'Initialize

My point is that one can always generate an assignment from a constructor. I
do not propose to allow user-defined assignments written in a form a
subroutine. I propose that all assignments be generated out of
constructors, user-defined ones inclusive.

> I'd prefer making the function solution work. There is a proposal to
> make it possible to initialize limited objects with a function call,
> which would eliminate that objection.

Yes it definitely would, because a function which result initialises an
unconstructed object is just another name for constructor. However there
would be also questions:

1. Will you use ":=" for limited types within a declaration with a problem
to explain what is that, or just "rename"?

2. What to do with "new" for limited types, when a constructor have to be
called?

3. I suppose that constructors will be allowed for Limited_Controlled only?
So arrays, untagged types, tasks and protected object will remain out of
reach.

The question is, will dispatching without an object be useful in cases other
than construction? This proposal requires new syntax. Mine

Obj : T'Class (<args>);

requires only new attributes. And gives

new T'Class (<args>);

subtype Specific_T is T (<args>);

almost for free.

> With these two capabilities, I believe that functions provide
> appropriate constructors, and you don't necessarily need to use a
> 'handle' implementation. (I suspect that in practice, you often will
> want to use a handle implementation anyway, but I certainly agree that
> you shouldn't be forced into it.) Leveraging existing language features
> is far preferable to inventing a whole new mechanism.

Though it is another question, I think that there is a need to have
something like user-defined anonymous access types, with the operations
becoming primitive for the pointed type. But it is probably far more
difficult than just constructors.

--
Happy New Year,
Dmitry A. Kazakov
www.dmitry-kazakov.de

Nick Roberts

unread,
Dec 29, 2002, 1:45:21 PM12/29/02
to
"Dmitry A. Kazakov" <mai...@dmitry-kazakov.de> wrote in message
news:aumu4o$76buu$1...@ID-77047.news.dfncis.de...

> > -- You'd need to carefully explain why solutions based on functions (as
> > Nick Roberts described) are not sufficient;
>
> They cannot work when no assignment exists. Otherwise one should invent a
> sort of pickwickian assignment.

Yes, Dmitry, but it is this Pickwickian assignment that has been mooted, and
it is necessary for you to demonstrate that your proposal is superior.

To be precise, interpreting Randy's suggestion, my suggestion would be for
(Ada be changed in the next revision so that) an object declaration which
includes a subtype indication that denotes a class-wide type whose root type
is limited (tagged) to be permitted to include an (explicit) initialisation
expression, in which case special rules would apply to the dynamic semantics
for the elaboration of the declaration: (1) the subtype inidication is
elaborated; (2) the expression is evaluated and converted to the nominal
subtype; (3) the object is created (constrained according to the result from
(2)); (4) the value of the result of (2) is copied (bitwise) into the object
(no call to Initialize or Adjust will be made).

For example:


package Session_Management is

type Internet_Session is abstract tagged limited private;

function Open (Host: in String) return Internet_Session'Class;

...

private
type Internet_Session is abstract new Ada.Finalization.Limited_Controlled
with null record;
end;

package body Session_Management is

type Simple_Session (


Host_Name_Length: Natural
Traceback_Length: Natural) is

new Internet_Session with


record
Host_Name: String(1..Host_Name_Length);
Traceback: ?.Node_Array(1..Traceback_Length);
Connection: ?.IP_Connection
...
end record;

function Open (Host: in String) return Internet_Session'Class;
begin
Open_Connection(Session.Connection,Host);
declare
Result: Simple_Session := (


Host'Length,
Get_Node_Count(Session.Connection),
... );
begin
...
return Result;
end;
exception
...
end Open;

...

end Session_Management;

...

My_Sess: Internet_Session'Class := Open("ftp.cdrom.com");


The advantages of this idea, I think, are: it is a relatively simple change
to the Ada standard (although it is still not entirely simple); a function
such as 'Open' (call it a 'constructor function' if you wish, but it is
really a normal function) can be used to create temporary objects (that are
a part of a bigger expression) as well as for intialising objects; it seems
to me to be a natural and readily understood way of solving the problem (the
only twist being the change in the rules for a limited tagged type); (in
common with your propoal) all existing legal Ada source text will remain
totally unaffected. It seems to bear some similarility to the new rule for
aggregates.

My example above also demonstrates the use of an abstract type, which has
the advantage that the implementation (of the 'Open' function) can return
any type it likes (derived from Internet_Session). The user is happily
oblivious to which actual type is returned; the implementation can be
changed to return (further) different types according to new requirements
without disturbing the user (recompilation or relinking will be required,
but no changes to the user's source text). The type could be made concrete,
and more details about the type could then be revealed to the user (e.g. the
discriminants, the other record components), should that be considered more
appropriate.

> > I'd prefer making the function solution work. There is a proposal to
> > make it possible to initialize limited objects with a function call,
> > which would eliminate that objection.
>
> Yes it definitely would, because a function which result initialises an
> unconstructed object is just another name for constructor. However there
> would be also questions:
>
> 1. Will you use ":=" for limited types within a declaration with a problem
> to explain what is that, or just "rename"?

The use of ":=" would be my preference, since it does not require any change
to the syntax of object declarations (RM95 3.3.1).

> 2. What to do with "new" for limited types, when a constructor have to be
> called?

There was never any problem with allocator initialisation, because the
result is of an access type (which is always definite).

> 3. I suppose that constructors will be allowed for Limited_Controlled
only?
> So arrays, untagged types, tasks and protected object will remain out of
> reach.

My suggestion is that the new rules should only apply to object declarations
for limited class-wide (tagged) subtypes, because they are illegal now, so
we can be certain that existing legal Ada source text will not be affected
by the new rules.

It may be convenient, but it is not really necessary for protected or task
types to be included, because a tagged (record) type can always include a
component of the requisite protected or task type. The question does not
arise for array types, because they are not (able to be) limited (and so
always have assignment defined). Any definite type can be initialised by a
procedure to which it is passed as an 'in out' parameter. I think there may
be a case for the inclusion of indefinite limited untagged types.

Now, what am I missing? :-)

--
Nick Roberts

Dmitry A. Kazakov

unread,
Dec 30, 2002, 7:23:30 AM12/30/02
to
Nick Roberts wrote:

> "Dmitry A. Kazakov" <mai...@dmitry-kazakov.de> wrote in message
> news:aumu4o$76buu$1...@ID-77047.news.dfncis.de...
>
>> > -- You'd need to carefully explain why solutions based on functions (as
>> > Nick Roberts described) are not sufficient;
>>
>> They cannot work when no assignment exists. Otherwise one should invent a
>> sort of pickwickian assignment.
>
> Yes, Dmitry, but it is this Pickwickian assignment that has been mooted,
> and it is necessary for you to demonstrate that your proposal is superior.

That's what I am trying to do. (:-))

The problem is not only the limited types. The problem is more general. It
is user-construction/destruction for ALL user-defined types. It is
user-assignment for ALL non-limited user-defined types. I do not think that
people are very happy with Ada.Finalization. It looks like a hack. However,
it is an excellent hack because it lets almost everything open for any
better solution, should we find it, of course. (:-))

> To be precise, interpreting Randy's suggestion, my suggestion would be for
> (Ada be changed in the next revision so that) an object declaration which
> includes a subtype indication that denotes a class-wide type whose root
> type is limited (tagged) to be permitted to include an (explicit)
> initialisation expression, in which case special rules would apply to the
> dynamic semantics for the elaboration of the declaration: (1) the subtype
> inidication is elaborated; (2) the expression is evaluated and converted
> to the nominal subtype; (3) the object is created (constrained according
> to the result from (2)); (4) the value of the result of (2) is copied
> (bitwise) into the object (no call to Initialize or Adjust will be made).

1. Your proposal is based on a bitwise copy[-constructor] (4). This means
that you have to ensure that ANY [limited tagged] object CAN be copied this
way. What will you do with the following:

type Internet_Session is abstract tagged limited private;

type Internet_Session_Ptr is access all Internet_Session'Class;


private
type Internet_Session is
abstract new Ada.Finalization.Limited_Controlled with

record
Previous : Internet_Session_Ptr; -- In the global list
Next : Internet_Session_Ptr; -- of sessions
end record;

The construction of an Internet_Session inserts it in a global double-linked
list of all sessions. Such an object cannot be copied bitwise. To solve
this and similar cases you have either to construct objects in-place (my
proposal) or to provide Adjust, i.e. a full-sized assignment.

2. Why is it allowed for class-wide types only? What should I do to
initialize a specific limited type?

3. Should specific types be allowed to be initialized, what will you do with
composite types of them? For instance, let an array of limited types be
initialized? It is limited, but alas untagged, so any kind of
initialization is impossible, right?

> The advantages of this idea, I think, are: it is a relatively simple
> change to the Ada standard (although it is still not entirely simple); a
> function such as 'Open' (call it a 'constructor function' if you wish, but
> it is really a normal function) can be used to create temporary objects
> (that are a part of a bigger expression) as well as for intialising
> objects; it seems to me to be a natural and readily understood way of
> solving the problem (the only twist being the change in the rules for a
> limited tagged type); (in common with your propoal) all existing legal Ada
> source text will remain totally unaffected.
> It seems to bear some similarility to the new rule for aggregates.

Is there an AI for the rule?

> My example above also demonstrates the use of an abstract type, which has
> the advantage that the implementation (of the 'Open' function) can return
> any type it likes (derived from Internet_Session). The user is happily
> oblivious to which actual type is returned; the implementation can be
> changed to return (further) different types according to new requirements
> without disturbing the user (recompilation or relinking will be required,
> but no changes to the user's source text). The type could be made
> concrete, and more details about the type could then be revealed to the
> user (e.g. the discriminants, the other record components), should that be
> considered more appropriate.
>
>> > I'd prefer making the function solution work. There is a proposal to
>> > make it possible to initialize limited objects with a function call,
>> > which would eliminate that objection.
>>
>> Yes it definitely would, because a function which result initialises an
>> unconstructed object is just another name for constructor. However there
>> would be also questions:
>>
>> 1. Will you use ":=" for limited types within a declaration with a
>> problem to explain what is that, or just "rename"?
>
> The use of ":=" would be my preference, since it does not require any
> change to the syntax of object declarations (RM95 3.3.1).
>
>> 2. What to do with "new" for limited types, when a constructor have to be
>> called?
>
> There was never any problem with allocator initialisation, because the
> result is of an access type (which is always definite).

The problem is that either T'Class'Input or your construction-function
creates a stack allocated object. So to allow heap objects as well, the
developer of a limited type have to provide two different
construction-functions: one to return T, another to return access T. This
definitely breaks Ada's symmetry in object allocation models and is a
burden for developers.

>> 3. I suppose that constructors will be allowed for Limited_Controlled
> only?
>> So arrays, untagged types, tasks and protected object will remain out of
>> reach.
>
> My suggestion is that the new rules should only apply to object
> declarations for limited class-wide (tagged) subtypes, because they are
> illegal now, so we can be certain that existing legal Ada source text will
> not be affected by the new rules.
>
> It may be convenient, but it is not really necessary for protected or task
> types to be included, because a tagged (record) type can always include a
> component of the requisite protected or task type.

It is arguable. Access discriminats are very frequent for tasks and
protected objects. A construction/destruction of tasks and protected
objects could provide a nice way to create the objects referenced by a task
during task construction and delete them upon destruction. This would be
much more better design than to pack the task into a record. Another
advantage would be that the entry points of a task could be exposed as
entry points, not as proxy subroutine calls, so a user of a task might
directly use timed entry calls etc on them.

> The question does not
> arise for array types, because they are not (able to be) limited (and so
> always have assignment defined).

Array of limited components is limited.

> Any definite type can be initialised by a
> procedure to which it is passed as an 'in out' parameter. I think there
> may be a case for the inclusion of indefinite limited untagged types.

You also can view it another way: we can initialize by either a function
[+copying] or a procedure [in-place]. For functions we have a way to do it
within a declaration:

X : My_Type := expression (<args>);

For procedures we have nothing (except the predefined constructor with the
values of the discriminants as the parameters). You can consider my
proposal as an attempt to close this gap:

X : My_Type (<args>);

Robert A Duff

unread,
Dec 30, 2002, 10:14:34 AM12/30/02
to
"Dmitry A. Kazakov" <mai...@dmitry-kazakov.de> writes:

[various stuff about constructors]

You should look at AI-287, which says that aggregates are allowed for
limited types. Also, AI-318 which allows constructor functions for
limited types.

I don't see any reason to add a new feature. It seems to me that the
problem is that you can't write a constructor function for a limited
type (i.e., a function that creates a new object local to itself, and
returns it), and you can't initialize limited objects. So the solution
is simply to remove these restrictions. To build on your example:

type Internet_Session is
abstract new Ada.Finalization.Limited_Controlled with
record

Count: Integer;


Previous : Internet_Session_Ptr; -- In the global list
Next : Internet_Session_Ptr; -- of sessions
end record;

type Session_Ptr is access Internet_Session;

function Make_Session(Count: Integer) return Internet_Session is
Result: Internet_Session
:= (Count => Count,
Previous | Next => <>); -- use default "null"
begin
Result.Previous := ...;
...
return Result;
end Make_Session;

Then clients could initialize stack and heap objects by calling the
constructor function:

This_Session: Internet_Session := Make_Session(Count => 123);
That_Session: Session_Ptr := new Internet_Session'(Make_Session(123));

The above aggregate is currently illegal, and the "return Result" won't
work because it violates accessibility rules. And the two initial
values are currently illegal. The two AI's would allow the above.

I'm glossing over some details that are probably documented either in
the AI's, or in the minutes of some ARG meeting.

The main point is that limited types are *too* limited. Their purpose
is to prevent *copying* (assignment statements). But initialization is
*not* like an assignment, it need not involve a copy, and it should be
allowed for limited types.

> The problem is not only the limited types. The problem is more general. It
> is user-construction/destruction for ALL user-defined types.

I don't understand that. Functions work fine as constructors for
non-limited types. The problem is for limited types.

As for destructors, well Ada.Finalization works OK, although it has some
flaws. Those flaws seem unrelated to the constructor-for-limited-type
issue.

>...It is

> user-assignment for ALL non-limited user-defined types.

That seems like a totally unrelated issue.

> 1. Your proposal is based on a bitwise copy[-constructor]...

The whole point of limited types is that they *cannot* be copied.
Access discriminants don't work if copies are made. Locks in protected
objects don't work. Various data structures involving pointers don't
work, as your example showed. Etc.

It is important that the Result object of Make_Session above be built in
place, in its final destination. It cannot be built on the local stack
frame and then moved elsewhere. This requires that the compiler pass in
a Storage_Pool object telling it where to allocate. A special
Storage_Pool could mean "on the stack", but in the That_Session
initialization above, the object must be allocated in the storage pool
belonging to Session_Ptr, which could be user defined.

- Bob

Dmitry A. Kazakov

unread,
Dec 31, 2002, 8:02:19 AM12/31/02
to
Robert A Duff wrote:

> "Dmitry A. Kazakov" <mai...@dmitry-kazakov.de> writes:
>
> [various stuff about constructors]
>

I do not understand how could this work. According to Nick Roberts the
result of Make_Session is copied as-is. But the object returned by
Make_Session has to be pointed from OUTSIDE:

function Make_Session (Count: Integer) return Internet_Session is
Result : Internet_Session (Count);
begin
--
-- Place the new object in front of the list
--
Result.Previous := Global_List_Header'Unchecked_Access;
Result.Next := Global_List_Header.Next;
Global_List_Header.Next := Result'Unchecked_Access;
Global_List_Header.Next.Previous := Result'Unchecked_Access;
return Result;
end Make_Session;

How to fix this?

> I'm glossing over some details that are probably documented either in
> the AI's, or in the minutes of some ARG meeting.
>
> The main point is that limited types are *too* limited. Their purpose
> is to prevent *copying* (assignment statements). But initialization is
> *not* like an assignment, it need not involve a copy, and it should be
> allowed for limited types.

Absolutely. The rest is easy, it is just to explain why "not-copying" have
to be spelled as copying:

X : Obj := Func (<args>);

Isn't it misleading? We already have functions that are not functions. Now
we will have even more non-functions, and also non-results of
non-assignments. I wonder why all this is not counted as a "new feature"?

>> The problem is not only the limited types. The problem is more general.
>> It is user-construction/destruction for ALL user-defined types.
>
> I don't understand that. Functions work fine as constructors for
> non-limited types. The problem is for limited types.

There should be a regular way to construct/destruct ANY user-defined type
including class-wides. There should be a way to ensure that only the
user-defined constructors are applied. The proposal does not respond this.
(<>) as the discriminants does not work for arrays, simple types. It also
has nasty consequences. Class-wide objects cannot be constructed.
Destruction is impossible as before if the type was not derived from a
controlled type. If I derive from Root_Stream_Type then I cannot define
neither initialization nor finalization for my stream object.

> As for destructors, well Ada.Finalization works OK, although it has some
> flaws. Those flaws seem unrelated to the constructor-for-limited-type
> issue.
>
>>...It is
>> user-assignment for ALL non-limited user-defined types.
>
> That seems like a totally unrelated issue.

My proposal was to provide true constructors. If you have constructors,
assignment comes for free. Limited types was just an example.

>> 1. Your proposal is based on a bitwise copy[-constructor]...
>
> The whole point of limited types is that they *cannot* be copied.
> Access discriminants don't work if copies are made. Locks in protected
> objects don't work. Various data structures involving pointers don't
> work, as your example showed. Etc.
>
> It is important that the Result object of Make_Session above be built in
> place, in its final destination. It cannot be built on the local stack
> frame and then moved elsewhere. This requires that the compiler pass in
> a Storage_Pool object telling it where to allocate. A special
> Storage_Pool could mean "on the stack", but in the That_Session
> initialization above, the object must be allocated in the storage pool
> belonging to Session_Ptr, which could be user defined.

So it seems that your view differs from Nick's one. OK.

1. How will the complier solve this puzzle:

function Make_Session (Count: Integer) return Internet_Session is
Result1 : Internet_Session (Count);
Result2 : Internet_Session (Count);
begin
... -- construct both
-- now drop one of them
if <something> then
return Result1;
else
return Result2;
end if;
end Make_Session;

Then from your description follows that functions returning limited objects
will be generated in some special way. All of them? What if I rename the
result of such Pickwickean function? Where will it be allocated?

2. How a task type will be constructed?

3. Let Internet_Session is a component of another limited type with another
construction function. How do I call Make_Session from that function?

Randy Brukardt

unread,
Dec 31, 2002, 7:28:35 PM12/31/02
to
Dmitry A. Kazakov wrote in message ...

>I do not understand how could this work. According to Nick Roberts the
>result of Make_Session is copied as-is. But the object returned by
>Make_Session has to be pointed from OUTSIDE:


Right. That's the "new feature" in the proposal. And it's relatively
cheap, since compilers typically handle composite functions by creating
memory at the point of the call and passing it in to the function with a
hidden parameter. Might as well make that explicit.

>function Make_Session (Count: Integer) return Internet_Session is
> Result : Internet_Session (Count);
>begin
>--
>-- Place the new object in front of the list
>--
> Result.Previous := Global_List_Header'Unchecked_Access;
> Result.Next := Global_List_Header.Next;
> Global_List_Header.Next := Result'Unchecked_Access;
> Global_List_Header.Next.Previous := Result'Unchecked_Access;
> return Result;
>end Make_Session;
>
>How to fix this?


The syntax of the proposal wasn't determined, but the basic idea was
something like:

function Make_Session (Count: Integer) return Internet_Session is

Result : return Internet_Session (Count);


begin
--
-- Place the new object in front of the list
--
Result.Previous := Global_List_Header'Unchecked_Access;
Result.Next := Global_List_Header.Next;
Global_List_Header.Next := Result'Unchecked_Access;
Global_List_Header.Next.Previous := Result'Unchecked_Access;
return Result;
end Make_Session;

With this declaration, "Result" is passed in implicitly from the caller
for a limited type, and it must be returned. Returning some other object
or none at all is an error. There can be only one such object (only one
object can be passed in).

>...


>Absolutely. The rest is easy, it is just to explain why "not-copying"
have
>to be spelled as copying:
>
>X : Obj := Func (<args>);
>
>Isn't it misleading? We already have functions that are not functions.
Now
>we will have even more non-functions, and also non-results of
>non-assignments. I wonder why all this is not counted as a "new
feature"?


Because we already have it in Ada 95 for aggregates (see AI-83 and the
corrigendum). So we're just extending what's already available.

>...


>Class-wide objects cannot be constructed.

I don't get this at all. What's wrong with:

A : T'Class := Construct_Class (...);

where the definition of Construct_Class uses some mechanism to dispatch
properly.

(Note that you virtually always end up with a case statement in
Construct_Class, because you somehow have to get from a set of arbitrary
parameter values to some tag. There is no automatic way to do that in
any programming language that I know of. If you're really clever, you
can use T'Class'Input to do this, but its a real hack.)

Randy.

Randy Brukardt

unread,
Dec 31, 2002, 7:54:50 PM12/31/02
to
Dmitry A. Kazakov wrote in message ...
>Randy Brukardt wrote:
>> The basic idea is to somehow figure a value of Ada.Tags.Tag, then use
it
>> to control dispatching on a function that returns the correct kind of
>> object. This requires a special call:
>>
>> Obj : T'Class := Func (<args>) use Tag_Value;
>
>The question is, will dispatching without an object be useful in cases
other
>than construction?

Yes, I think so. Two other examples have come up:
-- Calling the parent operation (which Ada does not have any
convinient syntax for - currently you have to name the parent
explicitly, which is a source of bugs);
-- Handling "least common denominator" comparison.

These aren't wildly compelling, admittedly.

> This proposal requires new syntax. Mine
>
> Obj : T'Class (<args>);
>
>requires only new attributes.

Adding syntax to a modern compiler is cheap. Either its table driven or
recursive descent; both are quite simple. OTOH, semantics still have to
be implemented by hand. The concern about syntax comes from the cost of
modifying other tools. If there is a way to avoid new syntax without
changing the semantics much, then that is to be preferred. But if we're
talking about implementing simple syntax and semantics compared to
implementing complex semantics only, I'll take a simple syntax change
any day. And your proposal comes under complex semantics, and would be
very expensive to implement in Janus/Ada.

Take your "only attributes" claim. In Janus/Ada (and probably other
compilers), attributes are completely manually implemented. Every
attribute is a bundle of special case code. It's especially expensive
for attributes which can be specified. For instance, stream attributes
(which these resemble) have dedicated components in type records which
hold the current definition. Type records are initialized in a large
variety of places in the compiler, and all of these would need to be
updated (Ada 2005 will help that somewhat, but of course the compiler is
implemented in Ada 95). So your "only new attributes" proposal would
take many times more work to implement. Indeed, it would be impossible
to implement without a wholesale restructuring of the compiler.

Secondly, you are suggesting that somehow:


Obj : T'Class (<args>);

is somehow the same as
Obj : T'Class (<discriminants>);

Essentially, you are saying that a discriminant constraint has the same
semantics as a parameter list. But that clearly isn't true in the RM as
written. The rules for these are defined in different places and are
subtly different. Changing them to be the same would be very difficult
without introducing dangerous incompatibilities. While that arguably
might be better for new users, it could be a disaster for existing Ada
users (and code). And it certainly would be a heck of a lot of work.

>Though it is another question, I think that there is a need to have
>something like user-defined anonymous access types, with the operations
>becoming primitive for the pointed type. But it is probably far more
>difficult than just constructors.


Well, there is a proposal for "named anonymous access types" (AI-230).
But the whole idea of a named anonymous type is an oxymoron, and that
turns off a lot of people. And there are quite a few semantic problems
with the proposal. So I wouldn't expect that proposal to be included in
Ada 2005.

Randy.

Dmitry A. Kazakov

unread,
Jan 1, 2003, 9:13:44 AM1/1/03
to
Randy Brukardt wrote:

I see, the old FORTRAN's idea to expose the result as a variable! I am very
impressed, that such a thing managed to come through. (:-))

I suppose it will be allowed for all types? Then it would be interesting to
know how it will work with by-value types:

function Set_Bit (Value : Boolean) return Boolean is
Result : return Boollean := Value;
begin
return Result;
end Set_Bit;

... provided that the result is an element of a packed array. Somehow,
somewhere the "in-place" requirement have to be relaxed. I wonder how many
pages one will add to RM.

Then there will be much work required to deal with things like:

function Make_Session (Count: Integer) return Internet_Session is

begin
declare
Result1 : return Internet_Session (24);
begin
if ... then
return Result1;
end if;
end;
declare
Result2 : return Internet_Session (1000); - another size
begin
if ... then
return Result2;
end if;
end;
declare
Result3 : Internet_Session (10); -- I forgot "return"
begin
return Result3;
end;
end Make_Session;

I am not sure, but to determine whether there is only one result, could be
equivalent to halting problem. So some sort of run-time support will be
well required to determine this. Or will it be classified as one more bound
error?

>>...
>>Absolutely. The rest is easy, it is just to explain why "not-copying"
> have
>>to be spelled as copying:
>>
>>X : Obj := Func (<args>);
>>
>>Isn't it misleading? We already have functions that are not functions.
> Now
>>we will have even more non-functions, and also non-results of
>>non-assignments. I wonder why all this is not counted as a "new
> feature"?
>
> Because we already have it in Ada 95 for aggregates (see AI-83 and the
> corrigendum). So we're just extending what's already available.
>
>>...
>>Class-wide objects cannot be constructed.
>
> I don't get this at all. What's wrong with:
>
> A : T'Class := Construct_Class (...);
>
> where the definition of Construct_Class uses some mechanism to dispatch
> properly.
>
> (Note that you virtually always end up with a case statement in
> Construct_Class, because you somehow have to get from a set of arbitrary
> parameter values to some tag. There is no automatic way to do that in
> any programming language that I know of. If you're really clever, you
> can use T'Class'Input to do this, but its a real hack.)

Yes, this is why my proposal offers a way to write things working like
T'Class'Input. When T'Class is created it (1) calls a user-defined function
to determine Tag, then (2) it dispatches to a user-defined function to get
constraints and finally (3) it dispatches to a user-defined Initialize.

As I understood from your and other's responses the new call syntax "Func
(...) use Tag" would give 1. 2-3 would be covered by new "FORTRAN"
functions and an ability to "assign" what cannot be assigned. So there
indeed will be a way to write Construct_Class without a case statement:

function Construct_Class (...) return T'Class is
begin
-- figure out the tag
declare
Result : return T'Class := Construct_Object (...) use <tag>;
begin
retrun Result;
end;
end Construct_Class;

function Construct_Object (...) return T is
begin
-- figure out the discriminants
declare
Result : return T := T (<discriminants>);
begin
-- initialize other Result's fields
retrun Result;
end;
end Construct_Object;

Formally it should work. (?)

Dmitry A. Kazakov

unread,
Jan 1, 2003, 9:13:47 AM1/1/03
to
Randy Brukardt wrote:

> Dmitry A. Kazakov wrote in message ...
>>Randy Brukardt wrote:
>>> The basic idea is to somehow figure a value of Ada.Tags.Tag, then use
> it
>>> to control dispatching on a function that returns the correct kind of
>>> object. This requires a special call:
>>>
>>> Obj : T'Class := Func (<args>) use Tag_Value;
>>
>>The question is, will dispatching without an object be useful in cases
> other
>>than construction?

Why this sort of syntax was chosen? I think more consequently in Ada's
spirit would be sort of:

Obj : T'Class (Tag_Value) := Func (<args>);

Isn't tag a natural constraint/discriminant of T'Class?

> Yes, I think so. Two other examples have come up:
> -- Calling the parent operation (which Ada does not have any
> convinient syntax for - currently you have to name the parent
> explicitly, which is a source of bugs);

Yes it would be nice. However you will need something to get a parent's type
tag from the type tag.

> -- Handling "least common denominator" comparison.

BTW. Why not to allow tag comparisons >, >=, <, <=? I mean:

function ">" (Left, Right : Tag) return Boolean;

A>B if B is a descendant of A, but not of A type.

> These aren't wildly compelling, admittedly.
>
>> This proposal requires new syntax. Mine
>>
>> Obj : T'Class (<args>);
>>
>>requires only new attributes.
>
> Adding syntax to a modern compiler is cheap. Either its table driven or
> recursive descent; both are quite simple. OTOH, semantics still have to
> be implemented by hand. The concern about syntax comes from the cost of
> modifying other tools. If there is a way to avoid new syntax without
> changing the semantics much, then that is to be preferred. But if we're
> talking about implementing simple syntax and semantics compared to
> implementing complex semantics only, I'll take a simple syntax change
> any day. And your proposal comes under complex semantics, and would be
> very expensive to implement in Janus/Ada.
>
> Take your "only attributes" claim. In Janus/Ada (and probably other
> compilers), attributes are completely manually implemented. Every
> attribute is a bundle of special case code. It's especially expensive
> for attributes which can be specified. For instance, stream attributes
> (which these resemble) have dedicated components in type records which
> hold the current definition. Type records are initialized in a large
> variety of places in the compiler, and all of these would need to be
> updated (Ada 2005 will help that somewhat, but of course the compiler is
> implemented in Ada 95). So your "only new attributes" proposal would
> take many times more work to implement. Indeed, it would be impossible
> to implement without a wholesale restructuring of the compiler.

You know it better, of course. However, in a long term perspective most of
attributes have to become just dispatching operations. Should all types be
tagged in the sense that T'Class exists and one can derive with overriding
its operations, then implementing of attributes would be much more easier.

> Secondly, you are suggesting that somehow:
> Obj : T'Class (<args>);
> is somehow the same as
> Obj : T'Class (<discriminants>);
>
> Essentially, you are saying that a discriminant constraint has the same
> semantics as a parameter list.

We could turn it another way: the semantics of the parameter list in a
constraint is same as one of a discriminant constraint.

> But that clearly isn't true in the RM as
> written. The rules for these are defined in different places and are
> subtly different. Changing them to be the same would be very difficult
> without introducing dangerous incompatibilities. While that arguably
> might be better for new users, it could be a disaster for existing Ada
> users (and code). And it certainly would be a heck of a lot of work.
>
>>Though it is another question, I think that there is a need to have
>>something like user-defined anonymous access types, with the operations
>>becoming primitive for the pointed type. But it is probably far more
>>difficult than just constructors.
>
> Well, there is a proposal for "named anonymous access types" (AI-230).
> But the whole idea of a named anonymous type is an oxymoron, and that
> turns off a lot of people. And there are quite a few semantic problems
> with the proposal. So I wouldn't expect that proposal to be included in
> Ada 2005.

Sigh. This could be a real breakthrough for OO programming and also an
excellent response to GC-obsessed folks.

Randy Brukardt

unread,
Jan 2, 2003, 2:36:56 PM1/2/03
to
Dmitry A. Kazakov wrote in message ...
>Randy Brukardt wrote:
>> Dmitry A. Kazakov wrote in message ...
>>>Randy Brukardt wrote:
>>>> The basic idea is to somehow figure a value of Ada.Tags.Tag, then
use
>> it
>>>> to control dispatching on a function that returns the correct kind
of
>>>> object. This requires a special call:
>>>>
>>>> Obj : T'Class := Func (<args>) use Tag_Value;
>>>
>>>The question is, will dispatching without an object be useful in
cases other
>>>than construction?
>
>Why this sort of syntax was chosen?

I chose it only because it has a natural, cheap, and obvious
implementation. I was hoping someone would have a better suggestion (but
that turned out to be "kill the idea").

>I think more consequently in Ada's
>spirit would be sort of:
>
> Obj : T'Class (Tag_Value) := Func (<args>);
>
>Isn't tag a natural constraint/discriminant of T'Class?


Yes, but this doesn't work, because you don't know the discriminants for
the object. Indeed, you don't even know how many there are or their
types (since you can change the discriminants on extensions, including
adding or removing them), so you can't even give them if you want to.
People would have prefered this sort of solution, but it just doesn't
work.

>> Yes, I think so. Two other examples have come up:
>> -- Calling the parent operation (which Ada does not have any
>> convinient syntax for - currently you have to name the parent
>> explicitly, which is a source of bugs);
>
>Yes it would be nice. However you will need something to get a parent's
type
>tag from the type tag.

I proposed an attribute for that purpose: T'Parent_Tag. For Janus/Ada,
the parent tag is stored in every tag, so this operation is very cheap.
However, there were concerns about the fact that there are potentially
two (related) parents for every type: the parent of the partial view and
the parent of the full view. I think the attribute has to go to the
parent of the full view, and since it is dynamic, the "privateness
breaking" of that is not a real concern.

>> -- Handling "least common denominator" comparison.
>
>BTW. Why not to allow tag comparisons >, >=, <, <=? I mean:
>
> function ">" (Left, Right : Tag) return Boolean;
>
>A>B if B is a descendant of A, but not of A type.


These certainly are allowed if you write them yourself. Even if you had
them, you still need a way to control the dispatching based on the
result (once you've figured out the LCD).

Randy.

Randy Brukardt

unread,
Jan 2, 2003, 2:44:42 PM1/2/03
to
Dmitry A. Kazakov wrote in message ...
>Randy Brukardt wrote:
>I suppose it will be allowed for all types? Then it would be
interesting to
>know how it will work with by-value types:
>
>function Set_Bit (Value : Boolean) return Boolean is
> Result : return Boollean := Value;
>begin
> return Result;
>end Set_Bit;
>
>... provided that the result is an element of a packed array. Somehow,
>somewhere the "in-place" requirement have to be relaxed. I wonder how
many
>pages one will add to RM.

I don't know, it wasn't my idea. I'd suspect that by-copy types would be
handled by-copy, thus you couldn't tell if it was in-place or not.

>Then there will be much work required to deal with things like:
>
>function Make_Session (Count: Integer) return Internet_Session is
>begin
> declare
> Result1 : return Internet_Session (24);
> begin
> if ... then
> return Result1;
> end if;
> end;
> declare
> Result2 : return Internet_Session (1000); - another size
> begin
> if ... then
> return Result2;
> end if;
> end;
> declare
> Result3 : Internet_Session (10); -- I forgot "return"
> begin
> return Result3;
> end;
>end Make_Session;


No, this is illegal. There can only be one result object declared. And
if there is one, returning anything else is illegal. So the declaration
of Result2 and the return of Result3 are illegal.

>I am not sure, but to determine whether there is only one result, could
be
>equivalent to halting problem. So some sort of run-time support will be
>well required to determine this. Or will it be classified as one more
bound
>error?


No, these are all compile-time checks. No run-time penalty at all.
Indeed, this is cheaper than Ada 95, where there is a run-time check
that the result of a function is "accessible". (And that check prevents
doing much useful with the function.)

Randy.

Dmitry A. Kazakov

unread,
Jan 3, 2003, 8:20:57 AM1/3/03
to
Randy Brukardt wrote:

I need not know them. The call to Func has to be dispatching, so the
function will return all discriminants. It should work exactly as

Obj : T'Class := Func (<args>) use Tag_Value;

> Indeed, you don't even know how many there are or their


> types (since you can change the discriminants on extensions, including
> adding or removing them), so you can't even give them if you want to.

Yes, so the type T'Class (Tag_Value) should be made indefinite to prevent
object declarations like:

Obj : T'Class (Tag_Value);

Or do you think that things like:

procedure Foo1 (X : T'Class) is
procedure Foo2 (Y : T'Class (X'Tag)) is
...

could become a problem?

>>BTW. Why not to allow tag comparisons >, >=, <, <=? I mean:
>>
>> function ">" (Left, Right : Tag) return Boolean;
>>
>>A>B if B is a descendant of A, but not of A type.
>
> These certainly are allowed if you write them yourself.

How? I think they should be predefined for all tags along a path in a type
tree. If they of different paths, Constraint_Error have to propagate.

> Even if you had
> them, you still need a way to control the dispatching based on the
> result (once you've figured out the LCD).

Not always. I have in mind simulation of multiple dispatch for binary
operations. I could declare one as:

function "+" (Left : T; Right : T'Class) return T'Class;

Should I have "<=" for tags to figure out which argument is closer to T an
implementation could be very straightforward:

type New_T is new T with private;

function "+" (Left : New_T; Right : T'Class) return T'Class is
begin
if Right'Tag <= New_T'Tag then
-- Right is closer to T than Left or same as Left
declare
Result : New_T := ...;
begin
... -- Sum a New_T and one of its ancestors
return Result;
end;
else
-- Right is more closer to T than Left
return Right = Left; -- Dispatch on Right
end if;
end "=";

There is a work-around using "in", but it is nasty because it requires a
hidden parameter to prevent infinite recursion.

Dmitry A. Kazakov

unread,
Jan 3, 2003, 8:21:03 AM1/3/03
to
Randy Brukardt wrote:

> No, this is illegal. There can only be one result object declared. And
> if there is one, returning anything else is illegal. So the declaration
> of Result2 and the return of Result3 are illegal.
>
>>I am not sure, but to determine whether there is only one result, could
>>be equivalent to halting problem. So some sort of run-time support will
>>be well required to determine this. Or will it be classified as one more
>>bound error?
>
> No, these are all compile-time checks. No run-time penalty at all.

And this?

function Make_Session (Count: Integer) return Internet_Session is
begin

for Index in Integer'Range loop
declare
Result : return Internet_Session (Index);
begin
if HALT (x) then
return Result;
end if;
end;
end loop;
end Make_Session;

I really dislike this construction. The single possible advantage of this
feature I see, is that maybe, it could allow results for entry points,
maybe not.

Randy Brukardt

unread,
Jan 3, 2003, 2:29:51 PM1/3/03
to
Dmitry A. Kazakov wrote in message ...


That would raise Program_Error if Halt was never true, because there was
no return in the function. That of course is the same as the current Ada
rule.

Randy.

Robert A Duff

unread,
Jan 3, 2003, 3:50:51 PM1/3/03
to
> Dmitry A. Kazakov wrote in message ...

> >function Make_Session (Count: Integer) return Internet_Session is
> >begin


> > for Index in Integer'Range loop
> > declare
> > Result : return Internet_Session (Index);
> > begin
> > if HALT (x) then
> > return Result;
> > end if;
> > end;
> > end loop;
> >end Make_Session;

There were various rules, such as disallowing the "return object" in a
nested block. I don't remember what they all were. Look at the AI
and/or ARG minutes if you care.

The idea is basically just like in Pascal, where there's a special
return object, conceptually declared as a local of the function.
To specify what to return, assign into this special object.
Or, if limited, *initialize* this object.

Various syntaxes were discussed for defining the special object --
including allowing it to be implicitly declared and/or denoting it via
an attribute.

Pascal uses the name of the function to denote this special object.
That's a bad idea, because it's ambiguous with a function call
of zero parameters.

IMHO, having a special object is better than the traditional Ada way (a
return statement), and I wish the language had used this mechanism in
the first place. This is because a return statement causes a transfer
of control. It's not quite as bad as a goto, but it seems to me that
one should not be required to do a jump when it's not needed. The usual
(and easiest to understand) case is when the "return" is actually at the
end of execution anyway, and no jump is needed. When a jump-to-end *is*
needed, that case should look "special" in the code. Combining "set
result" with "jump to end" is a bad idea.

At least, it's a bad idea if you're forced to use it. I wouldn't mind
making "return X" be a shorthand for "<result obj> := X; return;".

Also, I find myself declaring a "Result" object anyway, quite often.
It would be convenient to get it for free.

- Bob

Dmitry A. Kazakov

unread,
Jan 4, 2003, 7:53:04 AM1/4/03
to
Robert A Duff wrote:

>> Dmitry A. Kazakov wrote in message ...
>
>> >function Make_Session (Count: Integer) return Internet_Session is
>> >begin
>> > for Index in Integer'Range loop
>> > declare
>> > Result : return Internet_Session (Index);
>> > begin
>> > if HALT (x) then
>> > return Result;
>> > end if;
>> > end;
>> > end loop;
>> >end Make_Session;
>
> There were various rules, such as disallowing the "return object" in a
> nested block. I don't remember what they all were. Look at the AI
> and/or ARG minutes if you care.
>
> The idea is basically just like in Pascal, where there's a special
> return object, conceptually declared as a local of the function.

We will have numerous problems with combining variable-sized objects and
by-reference parameter passing. This only would add unnecessary complexity
to the language. It also would raise a question, why OUT parameters of
procedures and entry points cannot act this way.

> To specify what to return, assign into this special object.
> Or, if limited, *initialize* this object.

The problem is to ensure that this would happen exactly once, for *some* of
result types.

> Various syntaxes were discussed for defining the special object --
> including allowing it to be implicitly declared and/or denoting it via
> an attribute.
>
> Pascal uses the name of the function to denote this special object.
> That's a bad idea, because it's ambiguous with a function call
> of zero parameters.
>
> IMHO, having a special object is better than the traditional Ada way (a
> return statement), and I wish the language had used this mechanism in
> the first place. This is because a return statement causes a transfer
> of control.

But exactly this ensures that you cannot do it twice.

> It's not quite as bad as a goto, but it seems to me that
> one should not be required to do a jump when it's not needed. The usual
> (and easiest to understand) case is when the "return" is actually at the
> end of execution anyway, and no jump is needed. When a jump-to-end *is*
> needed, that case should look "special" in the code. Combining "set
> result" with "jump to end" is a bad idea.

I disagree. IMO it is the basic idea of a subroutine having a result. If you
want to separate "set" and "return", there are procedures for this.

> At least, it's a bad idea if you're forced to use it. I wouldn't mind
> making "return X" be a shorthand for "<result obj> := X; return;".

But in a comparable case:

"X := Y;" is a shorthand for "Finalize (X); Copy_constructor (X, Y);"

the decision made is directly opposite!

> Also, I find myself declaring a "Result" object anyway, quite often.
> It would be convenient to get it for free.

--

0 new messages