* Dispatching and generics - language lawyer question
@ 2002-07-22 23:13 Adam Beneschan
2002-07-23 15:42 ` Stephen Leake
0 siblings, 1 reply; 20+ messages in thread
From: Adam Beneschan @ 2002-07-22 23:13 UTC (permalink / raw)
I have a couple questions about what's supposed to happen when a
generic is instantiated with a class-wide type as an actual type. I'm
hoping that someone who really understands the intricacies of the
language will be kind enough to answer, and provide chapter and verse
from the RM to explain the correct answer.
(1) What is the correct output of the program below?
(2) What would be the correct output if "private" in line [A] is
changed to
"tagged private"?
I'm pretty sure the answer to (1) is FALSE, since the "=" on line [B]
in the instance denotes the predefined "=" declared for T1 in the
generic, and not the "=" "defined" for the class-wide type, by
8.3(13). I'm less clear about the answer to (2). Here, the "="
implicitly declared for T1 is a dispatching operation, and thus should
dispatch if a controlling operand is of a class-wide type (3.9.2(5)),
but it's not clear to me whether the operands in this case are treated
as if they were of the formal type (which is not class-wide) or of the
actual type (which is class-wide).
Thanks in advance for your help.
-- Adam
generic
type T1(<>) is private; -- [A]
package Pak1 is
type T1_Access is access T1;
function Check_Equal (X, Y : T1_Access) return boolean;
end Pak1;
package body Pak1 is
function Check_Equal (X, Y : T1_Access) return boolean is
begin
return X.all = Y.all; -- [B]
end Check_Equal;
end Pak1;
package Pak2 is
type Root_Type is tagged record
F1 : Integer;
end record;
end Pak2;
with Pak2;
package Pak3 is
type Child is new Pak2.Root_Type with record
F2 : Integer;
end record;
function "=" (Left, Right : Child) return boolean;
end Pak3;
package body Pak3 is
function "=" (Left, Right : Child) return boolean is
begin
return Left.F1 = Right.F1 and then
abs Left.F2 = abs Right.F2;
end "=";
end Pak3;
with Pak1;
with Pak2;
with Pak3;
with Text_IO;
procedure Test62 is
package Pak1_Inst is new Pak1 (Pak2.Root_Type'Class);
A1 : Pak1_Inst.T1_Access;
A2 : Pak1_Inst.T1_Access;
B : Boolean;
begin
A1 := new Pak3.Child' (F1 => 4, F2 => 6);
A2 := new Pak3.Child' (F1 => 4, F2 => -6);
B := Pak1_Inst.Check_Equal (A1, A2);
Text_IO.Put_Line (Boolean'Image (B));
end Test62;
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: Dispatching and generics - language lawyer question
2002-07-22 23:13 Dispatching and generics - language lawyer question Adam Beneschan
@ 2002-07-23 15:42 ` Stephen Leake
2002-07-24 15:37 ` Stephen Leake
0 siblings, 1 reply; 20+ messages in thread
From: Stephen Leake @ 2002-07-23 15:42 UTC (permalink / raw)
adam@irvine.com (Adam Beneschan) writes:
> I have a couple questions about what's supposed to happen when a
> generic is instantiated with a class-wide type as an actual type. I'm
> hoping that someone who really understands the intricacies of the
> language will be kind enough to answer, and provide chapter and verse
> from the RM to explain the correct answer.
>
> (1) What is the correct output of the program below?
GNAT 3.15a1 says "TRUE". Not a definitive answer, but possibly helpful :).
> (2) What would be the correct output if "private" in line [A] is
> changed to "tagged private"?
GNAT 3.15a1 also says "TRUE" for this.
> I'm pretty sure the answer to (1) is FALSE, since the "=" on line
> [B] in the instance denotes the predefined "=" declared for T1 in
> the generic, and not the "=" "defined" for the class-wide type, by
> 8.3(13).
I think the controlling paragraph is 12.5.1 (21). I think the behavior
exhibited by GNAT 3.15a1 agrees with this. Note that both variations
of your formal type declaration are "formal private types"; the
presence of "tagged" does not matter here. There is different behavior
for a "formal derived type", which is indicated by the keyword "new".
There is a problem with "reemergence" of predefined operators for
generic formal types, but I don't have an example of when it occurs. I
didn't see a discussion of this in Cohen's "Ada as a Second Language",
either. Nor in the Rationale. But maybe I just didn't look hard
enough.
You might try reading the Rationale for generics; it has some good
stuff in it. It's online at www.adapower.com; click the "resources"
button (I hate html frames!).
--
-- Stephe
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: Dispatching and generics - language lawyer question
@ 2002-07-24 5:33 Grein, Christoph
2002-07-24 22:55 ` Robert A Duff
2002-07-25 0:40 ` Robert Dewar
0 siblings, 2 replies; 20+ messages in thread
From: Grein, Christoph @ 2002-07-24 5:33 UTC (permalink / raw)
> There is a problem with "reemergence" of predefined operators ...
For untagged types, _redefined_ equality does not correctly compose and
_predefined_ equality reemerges in generics if not properly transferred by a
formal parameter:
type X is something;
function "=" (L, R: X) return Boolean; -- redefine
type Y is record
C: X;
end record;
A, B: Y;
L: Boolean := A = B; -- here predefined "=" on component C is used, not the
-- equality redefined above
generic
type T is private;
--with function "=" (L, R: T) return Boolean is <>;
package P is ...
package P_on_X is new P (X); -- here also predefined "=" on X is used,
-- not the equality redefined above
You have to uncomment the generic parameter function to prevent reemergence.
This whole astonishing rule has been introduced because of compatibility with
Ada 83 (note there were no tagged types in Ada 83 so there reemergence could
safely be avoided).
In Ada 83, it was not possible to redefine "=" without using a trick (I do not
remember the exact way to do it, I never did this; you have to use generics with
the a limited private formal type). When you used this trick to redefine
equality, also the predefined equality reemerged as in the cases above. Thus for
compatibility, we have this rule in Ada 95.
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: Dispatching and generics - language lawyer question
2002-07-23 15:42 ` Stephen Leake
@ 2002-07-24 15:37 ` Stephen Leake
0 siblings, 0 replies; 20+ messages in thread
From: Stephen Leake @ 2002-07-24 15:37 UTC (permalink / raw)
Stephen Leake <stephen.a.leake.1@gsfc.nasa.gov> writes:
> There is a problem with "reemergence" of predefined operators for
> generic formal types, but I don't have an example of when it occurs. I
> didn't see a discussion of this in Cohen's "Ada as a Second Language",
> either. Nor in the Rationale. But maybe I just didn't look hard
> enough.
Based on a hint from Christoph Grein, I wrote the following code to
demonstrate the "reemergence problem". It occurs for non-tagged types;
for tagged types, Ada 95 fixed the problem.
with Ada.Text_IO; use Ada.Text_IO;
procedure Reemerge
is
type X is new Integer;
function "=" (L, R: X) return Boolean; -- redefine
function "=" (L, R: X) return Boolean
is begin
Put_Line ("in redefined ""="" (X)");
return Integer (L + R) = 0;
end "=";
begin
Type_Compose :
declare
type Y is record
C : X;
end record;
A : Y := (C => 1);
B : Y := (C => 1);
begin
Put_Line ("Type_Compose");
Put_Line ("A = B => " & Boolean'Image (A = B));
-- Here predefined "=" on component C is used, not the
-- equality redefined above
New_Line;
Put_Line ("A.C = B.C => " & Boolean'Image (A.C = B.C));
-- Here the equality redefined above is used.
end Type_Compose;
New_Line;
Generic_Reemerge:
declare
generic
type T is private;
procedure Compare (L, R : in T);
-- Print a message with the result of "=" (L, R).
procedure Compare (L, R : in T)
is begin
Put_Line ("L = R => " & Boolean'Image (L = R));
end Compare;
procedure Compare_X is new Compare (X);
begin
Put_Line ("Generic_Reemerge");
Compare_X (1, 1);
end Generic_Reemerge;
New_Line;
Generic_Correct:
declare
generic
type T is private;
with function "=" (L, R : in T) return Boolean is <>;
procedure Compare (L, R : in T);
-- Print a message with the result of "=" (L, R).
procedure Compare (L, R : in T)
is begin
Put_Line ("L = R => " & Boolean'Image (L = R));
end Compare;
procedure Compare_X is new Compare (X);
begin
Put_Line ("Generic_Correct");
Compare_X (1, 1);
end Generic_Correct;
New_Line;
Generic_Tagged:
declare
package Tagged_Type is
type Y is tagged record
Z : Integer;
end record;
function "=" (L, R : in Y) return Boolean;
end Tagged_Type;
package body Tagged_Type is
function "=" (L, R : in Y) return Boolean
is begin
return L.Z + R.Z = 0;
end "=";
end Tagged_Type;
generic
type T is private;
procedure Compare (L, R : in T);
-- Print a message with the result of "=" (L, R).
procedure Compare (L, R : in T)
is begin
Put_Line ("L = R => " & Boolean'Image (L = R));
end Compare;
procedure Compare_Y is new Compare (Tagged_Type.Y);
begin
Put_Line ("Generic_Tagged");
Compare_Y ((Z => 1), (Z => 1));
end Generic_Tagged;
end Reemerge;
--
-- Stephe
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: Dispatching and generics - language lawyer question
2002-07-24 5:33 Grein, Christoph
@ 2002-07-24 22:55 ` Robert A Duff
2002-07-25 15:46 ` Ben Brosgol
2002-07-25 0:40 ` Robert Dewar
1 sibling, 1 reply; 20+ messages in thread
From: Robert A Duff @ 2002-07-24 22:55 UTC (permalink / raw)
"Grein, Christoph" <christoph.grein@eurocopter.com> writes:
> For untagged types, _redefined_ equality does not correctly compose and
> _predefined_ equality reemerges in generics if not properly transferred by a
> formal parameter:
What do you think the rule *should* be? It does seem bad that
predefined "=" reemerges. And of course the rule for "/=" should be the
same as for "=".
What about "<"? Should the predefined one reemerge? If a generic
sorting package calls the primitive "<" of the generic formal type,
it would seem desirable to use the user-defined one, rather than the
predefined one.
But what about "+" and "mod" and so forth? Package Text_IO.Integer_IO
probably uses the predefined "mod" to format the number as a string.
But the *spec* of that package doesn't say that -- it just says the
string is formatted according to some rules. If the predefined "mod"
did *not* reemerge, then a user-defined "mod" operator would break
Text_IO.Integer_IO. You may say, "tough luck; that's the programmer's
fault". But how should the semantics of Integer_IO be defined in the
RM? Surely the use of "mod" there is an implementation detail.
- Bob
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: Dispatching and generics - language lawyer question
2002-07-24 5:33 Grein, Christoph
2002-07-24 22:55 ` Robert A Duff
@ 2002-07-25 0:40 ` Robert Dewar
1 sibling, 0 replies; 20+ messages in thread
From: Robert Dewar @ 2002-07-25 0:40 UTC (permalink / raw)
"Grein, Christoph" <christoph.grein@eurocopter.com> wrote in message news:<mailman.1027489082.2047.comp.lang.ada@ada.eu.org>...
> When you used this trick to redefine
> equality, also the predefined equality reemerged as in
> the cases above. Thus for
> compatibility, we have this rule in Ada 95.
The compatibility consideration here does not relate solely
to the use of the trick for redefining equality.
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: Dispatching and generics - language lawyer question
2002-07-24 22:55 ` Robert A Duff
@ 2002-07-25 15:46 ` Ben Brosgol
2002-07-29 20:38 ` Robert A Duff
0 siblings, 1 reply; 20+ messages in thread
From: Ben Brosgol @ 2002-07-25 15:46 UTC (permalink / raw)
> > For untagged types, _redefined_ equality does not correctly compose and
> > _predefined_ equality reemerges in generics if not properly transferred
by a
> > formal parameter:
>
> But what about "+" and "mod" and so forth? Package Text_IO.Integer_IO
> probably uses the predefined "mod" to format the number as a string.
> But the *spec* of that package doesn't say that -- it just says the
> string is formatted according to some rules. If the predefined "mod"
> did *not* reemerge, then a user-defined "mod" operator would break
> Text_IO.Integer_IO. You may say, "tough luck; that's the programmer's
> fault". But how should the semantics of Integer_IO be defined in the
> RM? Surely the use of "mod" there is an implementation detail.
As another interesting case, suppose that you declare an integer type T with
"mod" specified as abstract. If "mod" on the formal type is invoked from
the generic body, then either the instantiation Ada.Text_IO.Integer_IO(T)
would need to be rejected (a rather flagrant violation of the "contract
model") or else the predefined "mod" would need to reemerge.
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: Dispatching and generics - language lawyer question
2002-07-25 15:46 ` Ben Brosgol
@ 2002-07-29 20:38 ` Robert A Duff
2002-07-31 22:52 ` Dmitry A.Kazakov
0 siblings, 1 reply; 20+ messages in thread
From: Robert A Duff @ 2002-07-29 20:38 UTC (permalink / raw)
Hi, Ben.
"Ben Brosgol" <brosgol@world.std.com> writes:
> As another interesting case, suppose that you declare an integer type T with
> "mod" specified as abstract. If "mod" on the formal type is invoked from
> the generic body, then either the instantiation Ada.Text_IO.Integer_IO(T)
> would need to be rejected (a rather flagrant violation of the "contract
> model") or else the predefined "mod" would need to reemerge.
Yes, that *is* an interesting case, which I hadn't thought of.
Note that for tagged types, the generic declares whether it wants to
accept types with abstract primitive ops, so the instantiation can be
rejected without violating the contract model. It's uncomfortable that
there are misc. rules like this that distinguish tagged types from
untagged types. Could we do better if designing the language from
scratch? (I think many such rules are needed for compatibility with Ada
83.) I'm not sure what the right answer is in this case.
I'm disappointed that nobody answered my previous question -- what
should the rules about predefined operators in generics be? I don't
like the current Ada rules (which require reemergence of the "wrong"
operator), but the alternatives are uncomfortable in various ways.
Christoph Grein, or anybody else want to comment?
Here's an interesting case: a generic Sort routine, that takes "<" as a
parameter. (Or it could be called "Less_Than" -- it doesn't matter what
it's called, or whether it's an operator symbol.) The Sort routine
might require that the "<" behave in certain ways (like if A<B and B<C
both return True, then A<C should always return True). Is there any way
to specify that (other than via comments)? Is there some way to design
the language so that the generic Sort could formally require such
properties of "<"?
This is similar to the Text_IO.Integer_IO example, where the code wants
to assume that "mod" behaves according to some mathematical rules.
(I'm not even sure how to state those rules precisely.)
- Bob
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: Dispatching and generics - language lawyer question
2002-07-31 22:52 ` Dmitry A.Kazakov
@ 2002-07-31 20:18 ` Robert A Duff
2002-08-02 1:15 ` Dmitry A.Kazakov
2002-08-13 22:50 ` Randy Brukardt
0 siblings, 2 replies; 20+ messages in thread
From: Robert A Duff @ 2002-07-31 20:18 UTC (permalink / raw)
Dmitry A.Kazakov <mailbox@dmitry-kazakov.de> writes:
> [away from Ada 95] There should be only tagged types and no untagged ones.
> Then I suppose the formal derived types will do the whole work, because the
> set of all available operations will be defined by the root type. All types
> tagged means only that T'Class exists for all but class-wide types. The tag
> should be kept in the instances of T'Class and class-wide pointers, but not
> in the values of T, so no performance hit when objects are specific.
This is a reasonable idea (not new -- I and probably others have thought
of it before).
As you imply, there are two ways to implement tags: (1) Store the tag
with each object. Then an access value (or a pass-by-reference
parameter) can be represented as the address of the object. Or (2),
do not store the tag with each object, but gin it up when necessary.
This requires access values and parameters to be a pair -- the tag, plus
the address of the object. When you do X'Access, the compiler creates
the pair.
Neither method is uniformly better: there are some programs that will be
more efficient with method (1), and some more efficient with method (2).
This indicates that the user should have the option. I would suggest
the default (in this hypothetical language) should be (2), and a
per-type pragma could request (1) for that type and its descendants.
This preserves the Bauer Principle -- if you never say 'Class, you never
have any overhead for storing or manipulating tags at run time.
Of course, having options like that complicates compilers.
For most of the Ada 9X design, either (1) or (2) were feasible (for
tagged types). However, a last-minute change to the language made (2)
infeasible. Namely, the fact that all tagged parameters are aliased.
As far as I know, all existing compilers use (1).
Note that the distinction between (1) and (2) is analogous to the
compiler storing array bounds with the array, or with pointers to the
array. Neither one is uniformly better. I believe GNAT allows a user
choice, and I think all other compilers store bounds with the array
(analogous to (1)).
> > I'm disappointed that nobody answered my previous question -- what
> > should the rules about predefined operators in generics be? I don't
> > like the current Ada rules (which require reemergence of the "wrong"
> > operator), but the alternatives are uncomfortable in various ways.
> > Christoph Grein, or anybody else want to comment?
> >
> > Here's an interesting case: a generic Sort routine, that takes "<" as a
> > parameter. (Or it could be called "Less_Than" -- it doesn't matter what
> > it's called, or whether it's an operator symbol.) The Sort routine
> > might require that the "<" behave in certain ways (like if A<B and B<C
> > both return True, then A<C should always return True). Is there any way
> > to specify that (other than via comments)? Is there some way to design
> > the language so that the generic Sort could formally require such
> > properties of "<"?
>
> [far away form Ada 95] You should simply have different root types.
> Transitive_Ordered should be a[!] root type for "all" types with transitive
> "<". "All" means the types you want to pass the actual parameter. Of course
> then the language should allow creating supertypes on-fly, because one
> cannot foresee all possible root types like Ada tries with its
> classification of formal types. Let Integer is not a descendant of
> Transitive_Ordered. Then one could create a "proxy" type which is
> simultaneously a supertype of Integer and a subtype of Transitive_Ordered.
> [The compiler should check that "<" be implemented.] So Integer would
> become a subtype Transitive_Ordered in the given context and thus could be
> used as an actual parameter of the generic. This of course would require MI
> and dynamically maintained dispatch tables.
Again, good ideas. But they don't solve the problem I was thinking of.
By the way, I don't see why you need dynamically maintained dispatch
tables. To me, "on the fly" means "somewhere later in the code" --
i.e. in some module other than where the type is first declared.
That need not imply "at run time", I think.
The notion of dynamically maintained dispatch tables scares me: I hope
that "<" on type T does not dispatch to different subprogram bodies
at different times (for the same type tag)!
It seems like adding new operations to a type should only be allowed at
the same nesting level as the original type declaration. I shudder to
think of the run-time overhead of adding an operation in a recursive
procedure. ("Adding new op" is essentially the same thing as "adding a
new parent type", here.)
But anyway, as you explain below, none of this solves the problem. The
compiler checks that "<" exists with the right parameter types, but that
doesn't *prove* its transitive (whether or not the Transitive_Ordered
idea is used).
> > This is similar to the Text_IO.Integer_IO example, where the code wants
> > to assume that "mod" behaves according to some mathematical rules.
> > (I'm not even sure how to state those rules precisely.)
>
> Well, this is about LSP. No one can enforce semantics of an operation.
Exactly my point. For *this* issue, all of the above good ideas are
equivalent to simply changing the language so that predefined operators
do not reemerge.
> However, the difference between what we have in Ada now and what I would
> like to have, is that presently operations of a private formal type are
> matched by their profiles. This warranties absolutely nothing.
Right, it warranties nothing.
>... With generic
> derived types, you would have a subtype relation which assumes a
> meaningfull implementation of the root type operations.
But this also warranties nothing. One must "assume" that operations do
what they're supposed to do; the compiler can't prove it. That's no
different from the current case, where "mod" could be redefined to do
something weird, thus breaking Text_IO.Integer_IO (I mean, if we changed
the rules to avoid the reemergence).
You still haven't answered my question: What should the RM say that
Integer_IO.Put does? (Or if it were user-defined, what should the
comment on its spec say?) I don't care whether the language removes
reemergence, or all your nice ideas above, including about formal
derived types, are adopted -- either way, it's difficult to define the
semantics of Put if it might be calling some user-defined version of
"mod".
The current RM doesn't even mention that Put calls "mod". In fact,
there are various ways of implementing Put, some of which call "mod" and
some of which don't. Surely, Put calling "mod" is an implementation
detail?
- Bob
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: Dispatching and generics - language lawyer question
2002-07-29 20:38 ` Robert A Duff
@ 2002-07-31 22:52 ` Dmitry A.Kazakov
2002-07-31 20:18 ` Robert A Duff
0 siblings, 1 reply; 20+ messages in thread
From: Dmitry A.Kazakov @ 2002-07-31 22:52 UTC (permalink / raw)
Robert A Duff wrote:
> "Ben Brosgol" <brosgol@world.std.com> writes:
>
>> As another interesting case, suppose that you declare an integer type T
>> with
>> "mod" specified as abstract. If "mod" on the formal type is invoked from
>> the generic body, then either the instantiation Ada.Text_IO.Integer_IO(T)
>> would need to be rejected (a rather flagrant violation of the "contract
>> model") or else the predefined "mod" would need to reemerge.
>
> Yes, that *is* an interesting case, which I hadn't thought of.
>
> Note that for tagged types, the generic declares whether it wants to
> accept types with abstract primitive ops, so the instantiation can be
> rejected without violating the contract model. It's uncomfortable that
> there are misc. rules like this that distinguish tagged types from
> untagged types. Could we do better if designing the language from
> scratch? (I think many such rules are needed for compatibility with Ada
> 83.) I'm not sure what the right answer is in this case.
[away from Ada 95] There should be only tagged types and no untagged ones.
Then I suppose the formal derived types will do the whole work, because the
set of all available operations will be defined by the root type. All types
tagged means only that T'Class exists for all but class-wide types. The tag
should be kept in the instances of T'Class and class-wide pointers, but not
in the values of T, so no performance hit when objects are specific.
> I'm disappointed that nobody answered my previous question -- what
> should the rules about predefined operators in generics be? I don't
> like the current Ada rules (which require reemergence of the "wrong"
> operator), but the alternatives are uncomfortable in various ways.
> Christoph Grein, or anybody else want to comment?
>
> Here's an interesting case: a generic Sort routine, that takes "<" as a
> parameter. (Or it could be called "Less_Than" -- it doesn't matter what
> it's called, or whether it's an operator symbol.) The Sort routine
> might require that the "<" behave in certain ways (like if A<B and B<C
> both return True, then A<C should always return True). Is there any way
> to specify that (other than via comments)? Is there some way to design
> the language so that the generic Sort could formally require such
> properties of "<"?
[far away form Ada 95] You should simply have different root types.
Transitive_Ordered should be a[!] root type for "all" types with transitive
"<". "All" means the types you want to pass the actual parameter. Of course
then the language should allow creating supertypes on-fly, because one
cannot foresee all possible root types like Ada tries with its
classification of formal types. Let Integer is not a descendant of
Transitive_Ordered. Then one could create a "proxy" type which is
simultaneously a supertype of Integer and a subtype of Transitive_Ordered.
[The compiler should check that "<" be implemented.] So Integer would
become a subtype Transitive_Ordered in the given context and thus could be
used as an actual parameter of the generic. This of course would require MI
and dynamically maintained dispatch tables.
> This is similar to the Text_IO.Integer_IO example, where the code wants
> to assume that "mod" behaves according to some mathematical rules.
> (I'm not even sure how to state those rules precisely.)
Well, this is about LSP. No one can enforce semantics of an operation.
However, the difference between what we have in Ada now and what I would
like to have, is that presently operations of a private formal type are
matched by their profiles. This warranties absolutely nothing. With generic
derived types, you would have a subtype relation which assumes a
meaningfull implementation of the root type operations.
--
Regards,
Dmitry Kazakov
www.dmitry-kazakov.de
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: Dispatching and generics - language lawyer question
2002-08-02 1:15 ` Dmitry A.Kazakov
@ 2002-08-01 16:30 ` Hyman Rosen
2002-08-02 23:42 ` Dmitry A.Kazakov
0 siblings, 1 reply; 20+ messages in thread
From: Hyman Rosen @ 2002-08-01 16:30 UTC (permalink / raw)
Dmitry A.Kazakov wrote:
> I think that (1) would cause many problems with MI. When tag is a part of
> the value and then view conversions are problematic.
> There is also redispatch which is IMO incompatible with (1). I think
> redispatch must be removed, because when object identification is really
> necessary (to redispatch) one could use a solution similar to J.-P. Rosen
> trick.
C++ uses (1), storing multiple "tags" (actually, virtual
table pointers) in the object if necessary, and supports
redispatch just fine. The scheme used by the new version
of g++ is described at
<http://www.codesourcery.com/cxx-abi/abi.html>.
I doubt that the language designers, assuming they do add
MI to Ada, would be so silly as to deliberately choose to
implement it in a way that could never be compatible with
C++ object layout.
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: Dispatching and generics - language lawyer question
2002-07-31 20:18 ` Robert A Duff
@ 2002-08-02 1:15 ` Dmitry A.Kazakov
2002-08-01 16:30 ` Hyman Rosen
2002-08-13 22:50 ` Randy Brukardt
1 sibling, 1 reply; 20+ messages in thread
From: Dmitry A.Kazakov @ 2002-08-02 1:15 UTC (permalink / raw)
Robert A Duff wrote:
> Dmitry A.Kazakov <mailbox@dmitry-kazakov.de> writes:
>
>> [away from Ada 95] There should be only tagged types and no untagged
>> [ones.
>> Then I suppose the formal derived types will do the whole work, because
>> the set of all available operations will be defined by the root type. All
>> types tagged means only that T'Class exists for all but class-wide types.
>> The tag should be kept in the instances of T'Class and class-wide
>> pointers, but not in the values of T, so no performance hit when objects
>> are specific.
>
> This is a reasonable idea (not new -- I and probably others have thought
> of it before).
>
> As you imply, there are two ways to implement tags: (1) Store the tag
> with each object. Then an access value (or a pass-by-reference
> parameter) can be represented as the address of the object. Or (2),
> do not store the tag with each object, but gin it up when necessary.
> This requires access values and parameters to be a pair -- the tag, plus
> the address of the object. When you do X'Access, the compiler creates
> the pair.
>
> Neither method is uniformly better: there are some programs that will be
> more efficient with method (1), and some more efficient with method (2).
> This indicates that the user should have the option. I would suggest
> the default (in this hypothetical language) should be (2), and a
> per-type pragma could request (1) for that type and its descendants.
> This preserves the Bauer Principle -- if you never say 'Class, you never
> have any overhead for storing or manipulating tags at run time.
>
> Of course, having options like that complicates compilers.
I think that (1) would cause many problems with MI. When tag is a part of
the value and then view conversions are problematic.
> For most of the Ada 9X design, either (1) or (2) were feasible (for
> tagged types). However, a last-minute change to the language made (2)
> infeasible. Namely, the fact that all tagged parameters are aliased.
There is also redispatch which is IMO incompatible with (1). I think
redispatch must be removed, because when object identification is really
necessary (to redispatch) one could use a solution similar to J.-P. Rosen
trick.
> As far as I know, all existing compilers use (1).
>
> Note that the distinction between (1) and (2) is analogous to the
> compiler storing array bounds with the array, or with pointers to the
> array. Neither one is uniformly better. I believe GNAT allows a user
> choice, and I think all other compilers store bounds with the array
> (analogous to (1)).
Yes. Same with discriminants. I have only a vague idea, that there should
be way to separate all things like that from values. The goal is of course
to make arrays, strings etc user-defined types without additional overhead.
Then of course you should be able to derive from an abstract array and
provide an implementation which is internally not an array at all. So the
client will never know.
>> > I'm disappointed that nobody answered my previous question -- what
>> > should the rules about predefined operators in generics be? I don't
>> > like the current Ada rules (which require reemergence of the "wrong"
>> > operator), but the alternatives are uncomfortable in various ways.
>> > Christoph Grein, or anybody else want to comment?
>> >
>> > Here's an interesting case: a generic Sort routine, that takes "<" as a
>> > parameter. (Or it could be called "Less_Than" -- it doesn't matter
>> > what
>> > it's called, or whether it's an operator symbol.) The Sort routine
>> > might require that the "<" behave in certain ways (like if A<B and B<C
>> > both return True, then A<C should always return True). Is there any
>> > way
>> > to specify that (other than via comments)? Is there some way to design
>> > the language so that the generic Sort could formally require such
>> > properties of "<"?
>>
>> [far away form Ada 95] You should simply have different root types.
>> Transitive_Ordered should be a[!] root type for "all" types with
>> transitive "<". "All" means the types you want to pass the actual
>> parameter. Of course then the language should allow creating supertypes
>> on-fly, because one cannot foresee all possible root types like Ada tries
>> with its classification of formal types. Let Integer is not a descendant
>> of Transitive_Ordered. Then one could create a "proxy" type which is
>> simultaneously a supertype of Integer and a subtype of
>> Transitive_Ordered.
>> [The compiler should check that "<" be implemented.] So Integer would
>> become a subtype Transitive_Ordered in the given context and thus could
>> be used as an actual parameter of the generic. This of course would
>> require MI and dynamically maintained dispatch tables.
>
> Again, good ideas. But they don't solve the problem I was thinking of.
>
> By the way, I don't see why you need dynamically maintained dispatch
> tables. To me, "on the fly" means "somewhere later in the code" --
> i.e. in some module other than where the type is first declared.
> That need not imply "at run time", I think.
>
> The notion of dynamically maintained dispatch tables scares me: I hope
> that "<" on type T does not dispatch to different subprogram bodies
> at different times (for the same type tag)!
>
> It seems like adding new operations to a type should only be allowed at
> the same nesting level as the original type declaration. I shudder to
> think of the run-time overhead of adding an operation in a recursive
> procedure. ("Adding new op" is essentially the same thing as "adding a
> new parent type", here.)
Yes, I also do not think that one could override in any context without a
heavy penalty. I think if that would be allowed then no expression would be
static when used in a subprogram. No, I meant another thing. If a derived
type is created in some nested context and overrides, say "<", then the
dispatching table of "<" should be modified once when the type is
elaborated and restored when the context is finalized.
> But anyway, as you explain below, none of this solves the problem. The
> compiler checks that "<" exists with the right parameter types, but that
> doesn't *prove* its transitive (whether or not the Transitive_Ordered
> idea is used).
>
>> > This is similar to the Text_IO.Integer_IO example, where the code wants
>> > to assume that "mod" behaves according to some mathematical rules.
>> > (I'm not even sure how to state those rules precisely.)
>>
>> Well, this is about LSP. No one can enforce semantics of an operation.
>
> Exactly my point. For *this* issue, all of the above good ideas are
> equivalent to simply changing the language so that predefined operators
> do not reemerge.
One should distinguish overloading and overriding. If ">" is overridden
then a reemergence would clearly violate LSP. On contrary in the case of
overloading reemergence would enforce LSP. So logically, the reemergency
rule is OK so far the type is untagged [but there should be no such (:-))].
But look at this:
package Some_Type is
type X is tagged null record;
function "<" (A, B : X) return Boolean;
end Some_Type;
package body Some_Type is
function "<" (A, B : X) return Boolean is
begin
return True;
end "<";
end Some_Type;
with Ada.Text_IO; use Ada.Text_IO;
with Some_Type; use Some_Type;
procedure Test is
XX : X;
generic procedure Compare (A : X);
procedure Compare (A : X) is
begin
if A < A then
Put_Line ("Reemerged");
else
Put_Line ("Overloaded");
end if;
end Compare;
procedure G1 is new Compare;
procedure N1 (A : X) is
begin
if A < A then
Put_Line ("Reemerged");
else
Put_Line ("Overloaded");
end if;
end N1;
function "<" (A, B : X) return Boolean is
begin
return False;
end "<";
procedure G2 is new Compare;
procedure N2 (A : X) is
begin
if A < A then
Put_Line ("Reemerged");
else
Put_Line ("Overloaded");
end if;
end N2;
begin
G1 (XX); -- Reemerged
N1 (XX); -- Reemerged
G2 (XX); -- Reemerged
N2 (XX); -- Overloaded
end Test;
Is THIS OK? I do not think so. The first thought is to prohibit overloading
of primitive operations.
>> However, the difference between what we have in Ada now and what I would
>> like to have, is that presently operations of a private formal type are
>> matched by their profiles. This warranties absolutely nothing.
>
> Right, it warranties nothing.
>
>>... With generic
>> derived types, you would have a subtype relation which assumes a
>> meaningfull implementation of the root type operations.
>
> But this also warranties nothing. One must "assume" that operations do
> what they're supposed to do; the compiler can't prove it.
To some extent it could. Let we introduce, as many proposed, pre-,
post-conditions and invariants. Then some of requirements on operations of
Transitive_Order could be expressed in their terms. Then (do not ask me
how) the compiler could check that a pre-condition of a derived type
declared to be a "strong subtype" is not stronger than it was for the base
type etc.
This will still warranty nothing, but it is much like
Unchecked_Deallocation. How do you warranty that it is not called twice?
One thing is when the compiler silently uses ">", just because it has a
right name and profile. Another thing is when the compiler says: sorry guy,
">" is abstract, please, override it, even if the overriding would be a
simple renaming, even if some idiot would call a TRAP instruction from the
overriding.
> That's no
> different from the current case, where "mod" could be redefined to do
> something weird, thus breaking Text_IO.Integer_IO (I mean, if we changed
> the rules to avoid the reemergence).
>
> You still haven't answered my question: What should the RM say that
> Integer_IO.Put does? (Or if it were user-defined, what should the
> comment on its spec say?) I don't care whether the language removes
> reemergence, or all your nice ideas above, including about formal
> derived types, are adopted -- either way, it's difficult to define the
> semantics of Put if it might be calling some user-defined version of
> "mod".
The precondition of Integer_IO is that the generic parameter is of some
integer type. It means that all what is said about integer types in RM
should be true for it. RM could say that for an integer type for any x and
any y/=0 there are d and r of the type that x=y*d+r and etc (*). There is a
related question: how to make preconditions as weak as possible?
Definitely, Put does not need *all* integer operations. How to express
that? To refine further the type hierarchy like with Transitive_Order would
be a heavy burden for a programmer, because then he/she should instantiate
each procedure of the package separately depending on what subset of
operations (subtype) the procedure require.
(*) You can always write in RM: "it is a bounded error if" and a draconian
requirement follows (:-))
Also any polymorphic solution (generic or class-wide) may face
substitutability problems because of overriding. Thus for simple things
like Integer_IO it could be better to have a non-polymorphic one. I mean
user-defined type conversions and subtyping through conversions. In fact we
do it manually on daily basis when write something like:
type X is range ...;
A : X;
Put (Integer'Image (Integer (A)));
Let we have some integer type Int_Out which cannot have instances
(variables). Then let's declare Put on it. Then if I would like to make an
output for X, I would declare X a subtype of IO_Int by providing a
conversion X->Int_Out. The compiler would apply the conversion should I
write Put (X). This suffers no substitutability problems (**), because Put
will always deal with an Int_Out.
(**) If Int_Out is capable to represent all values of the derived type.
> The current RM doesn't even mention that Put calls "mod". In fact,
> there are various ways of implementing Put, some of which call "mod" and
> some of which don't. Surely, Put calling "mod" is an implementation
> detail?
Yes. Yet it is consistent, because what is not an implementation detail is
that Put *may* call "mod" expecting a meaningfull behaviour from it.
--
Regards,
Dmitry Kazakov
www.dmitry-kazakov.de
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: Dispatching and generics - language lawyer question
2002-08-02 23:42 ` Dmitry A.Kazakov
@ 2002-08-02 15:49 ` Hyman Rosen
2002-08-02 17:48 ` Stephen Leake
` (2 more replies)
0 siblings, 3 replies; 20+ messages in thread
From: Hyman Rosen @ 2002-08-02 15:49 UTC (permalink / raw)
Dmitry A.Kazakov wrote:
> So in C++ int will never be a class, worse to C++.
It's never going be a class in Ada either.
> I doubt that it is worth effors to keep objects Ada
> and C++ compatible.
I think that the GNAT creators would disagree with you.
As far as I know, they make corresponding Ada and C++
objects layout compatible. Perhaps one of them will
explain why.
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: Dispatching and generics - language lawyer question
2002-08-02 15:49 ` Hyman Rosen
@ 2002-08-02 17:48 ` Stephen Leake
2002-08-10 3:03 ` Warren W. Gay VE3WWG
2002-08-05 11:15 ` Dmitry A. Kazakov
2002-08-12 12:44 ` Robert Dewar
2 siblings, 1 reply; 20+ messages in thread
From: Stephen Leake @ 2002-08-02 17:48 UTC (permalink / raw)
Hyman Rosen <hyrosen@mail.com> writes:
> Dmitry A.Kazakov wrote:
> > So in C++ int will never be a class, worse to C++.
>
> It's never going be a class in Ada either.
>
> > I doubt that it is worth effors to keep objects Ada
> > and C++ compatible.
>
> I think that the GNAT creators would disagree with you.
> As far as I know, they make corresponding Ada and C++
> objects layout compatible. Perhaps one of them will
> explain why.
It allows you to derive a new Ada type from a C++ class, and vice
versa. Then a dispatching call from a C++ routine can call an Ada
body, and vice versa.
It allows _full_ mixed language programming with Ada and C++. This is
a _very_ good feature. The alternative requires a C interface between
C++ and Ada, which is simply not as good.
--
-- Stephe
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: Dispatching and generics - language lawyer question
2002-08-01 16:30 ` Hyman Rosen
@ 2002-08-02 23:42 ` Dmitry A.Kazakov
2002-08-02 15:49 ` Hyman Rosen
0 siblings, 1 reply; 20+ messages in thread
From: Dmitry A.Kazakov @ 2002-08-02 23:42 UTC (permalink / raw)
Hyman Rosen wrote:
> Dmitry A.Kazakov wrote:
>> I think that (1) would cause many problems with MI. When tag is a part of
>> the value and then view conversions are problematic.
>> There is also redispatch which is IMO incompatible with (1). I think
>> redispatch must be removed, because when object identification is really
>> necessary (to redispatch) one could use a solution similar to J.-P. Rosen
>> trick.
>
> C++ uses (1), storing multiple "tags" (actually, virtual
> table pointers) in the object if necessary, and supports
> redispatch just fine. The scheme used by the new version
> of g++ is described at
> <http://www.codesourcery.com/cxx-abi/abi.html>.
So in C++ int will never be a class, worse to C++.
> I doubt that the language designers, assuming they do add
> MI to Ada, would be so silly as to deliberately choose to
> implement it in a way that could never be compatible with
> C++ object layout.
I do not think that to repeat others faults is a good idea. As long as we
have no C++ operating system APIs (shudder), I doubt that it is worth
effors to keep objects Ada and C++ compatible. To communicate with C++ one
could declare a simple record type with fields reserved for places where
vtptrs are.
--
Regards,
Dmitry Kazakov
www.dmitry-kazakov.de
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: Dispatching and generics - language lawyer question
2002-08-02 15:49 ` Hyman Rosen
2002-08-02 17:48 ` Stephen Leake
@ 2002-08-05 11:15 ` Dmitry A. Kazakov
2002-08-12 12:44 ` Robert Dewar
2 siblings, 0 replies; 20+ messages in thread
From: Dmitry A. Kazakov @ 2002-08-05 11:15 UTC (permalink / raw)
On Fri, 02 Aug 2002 11:49:34 -0400, Hyman Rosen <hyrosen@mail.com>
wrote:
>Dmitry A.Kazakov wrote:
>> So in C++ int will never be a class, worse to C++.
>
>It's never going be a class in Ada either.
It was rather about an imaginary successor of Ada, which should not be
Ada++ in the sense of C++ = "C with classes". At some point one will
have to drive the line.
> > I doubt that it is worth effors to keep objects Ada
> > and C++ compatible.
>
>I think that the GNAT creators would disagree with you.
>As far as I know, they make corresponding Ada and C++
>objects layout compatible. Perhaps one of them will
>explain why.
Maybe because they get it more or less free. (:-))
---
Regards,
Dmitry Kazakov
www.dmitry-kazakov.de
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: Dispatching and generics - language lawyer question
2002-08-02 17:48 ` Stephen Leake
@ 2002-08-10 3:03 ` Warren W. Gay VE3WWG
0 siblings, 0 replies; 20+ messages in thread
From: Warren W. Gay VE3WWG @ 2002-08-10 3:03 UTC (permalink / raw)
Stephen Leake wrote:
> Hyman Rosen <hyrosen@mail.com> writes:
>>Dmitry A.Kazakov wrote:
>>>So in C++ int will never be a class, worse to C++.
>>
>>It's never going be a class in Ada either.
>>
>> > I doubt that it is worth effors to keep objects Ada
>> > and C++ compatible.
>>
>>I think that the GNAT creators would disagree with you.
>>As far as I know, they make corresponding Ada and C++
>>objects layout compatible. Perhaps one of them will
>>explain why.
>
> It allows you to derive a new Ada type from a C++ class, and vice
> versa. Then a dispatching call from a C++ routine can call an Ada
> body, and vice versa.
>
> It allows _full_ mixed language programming with Ada and C++. This is
> a _very_ good feature. The alternative requires a C interface between
> C++ and Ada, which is simply not as good.
At least at the level of GNAT 3.13p, the C++ support was not quite
"full". To the best of my knowledge, the C++ destructors did
not work when the object went out of scope within Ada code.
You could call the destructor manually, but this of course is
not as convenient as the Ada.Finalization.Controlled type of
object that GNAT does support finalization for.
So in this respect, unless they have fixed this in later
versions (I did not check), the C++ level of support is not yet
"full" in GNAT.
Warren.
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: Dispatching and generics - language lawyer question
2002-08-02 15:49 ` Hyman Rosen
2002-08-02 17:48 ` Stephen Leake
2002-08-05 11:15 ` Dmitry A. Kazakov
@ 2002-08-12 12:44 ` Robert Dewar
2 siblings, 0 replies; 20+ messages in thread
From: Robert Dewar @ 2002-08-12 12:44 UTC (permalink / raw)
Hyman Rosen <hyrosen@mail.com> wrote in message news:<1028303374.179416@master.nyc.kbcfp.com>...
> I think that the GNAT creators would disagree with you.
> As far as I know, they make corresponding Ada and C++
> objects layout compatible. Perhaps one of them will
> explain why.
That's a bit confused. The layout of tagged types in
GNAT is defined by the body of the run-time unit Ada.Tags.
Whether this corresponds or not to some particular C++
compiler depends on whether you tailor Ada.Tags
appropriately.
In practice, the only significant use of the tight C++
binding of objects and tagged types in GNAT was by SGI
for some of their graphics packages, otherwise I don't
think the feature has been used.
(there are lots of things in GNAT that are very interesting
but don't get used, e.g. the entire Information Systems
Annex :-)
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: Dispatching and generics - language lawyer question
2002-07-31 20:18 ` Robert A Duff
2002-08-02 1:15 ` Dmitry A.Kazakov
@ 2002-08-13 22:50 ` Randy Brukardt
2002-08-14 0:02 ` Robert A Duff
1 sibling, 1 reply; 20+ messages in thread
From: Randy Brukardt @ 2002-08-13 22:50 UTC (permalink / raw)
Robert A Duff wrote in message ...
Bon Duff wrote:
>Note that the distinction between (1) and (2) is analogous to the
>compiler storing array bounds with the array, or with pointers to the
>array. Neither one is uniformly better. I believe GNAT allows a user
>choice, and I think all other compilers store bounds with the array
>(analogous to (1)).
Janus/Ada actually does neither -- the bounds are stored separately, and
belong to neither. (The memory used is allocated separately if needed.)
Indeed, I don't think you can store the bounds with the array for
slices, so I think your analogy is incorrect (there is nothing like
slices for tagged types, of course).
Randy.
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: Dispatching and generics - language lawyer question
2002-08-13 22:50 ` Randy Brukardt
@ 2002-08-14 0:02 ` Robert A Duff
0 siblings, 0 replies; 20+ messages in thread
From: Robert A Duff @ 2002-08-14 0:02 UTC (permalink / raw)
"Randy Brukardt" <randy@rrsoftware.com> writes:
> Janus/Ada actually does neither -- the bounds are stored separately, and
> belong to neither. (The memory used is allocated separately if needed.)
> Indeed, I don't think you can store the bounds with the array for
> slices, so I think your analogy is incorrect (there is nothing like
> slices for tagged types, of course).
Good point. If a slice is passed as a parameter, the bounds ought to be
passed separately from the pointer-to-data. In Ada 83, I suppose one
could always copy slices, but in Ada 95, some types require pass by
reference. Including a slice of one of those.
- Bob
^ permalink raw reply [flat|nested] 20+ messages in thread
end of thread, other threads:[~2002-08-14 0:02 UTC | newest]
Thread overview: 20+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2002-07-22 23:13 Dispatching and generics - language lawyer question Adam Beneschan
2002-07-23 15:42 ` Stephen Leake
2002-07-24 15:37 ` Stephen Leake
-- strict thread matches above, loose matches on Subject: below --
2002-07-24 5:33 Grein, Christoph
2002-07-24 22:55 ` Robert A Duff
2002-07-25 15:46 ` Ben Brosgol
2002-07-29 20:38 ` Robert A Duff
2002-07-31 22:52 ` Dmitry A.Kazakov
2002-07-31 20:18 ` Robert A Duff
2002-08-02 1:15 ` Dmitry A.Kazakov
2002-08-01 16:30 ` Hyman Rosen
2002-08-02 23:42 ` Dmitry A.Kazakov
2002-08-02 15:49 ` Hyman Rosen
2002-08-02 17:48 ` Stephen Leake
2002-08-10 3:03 ` Warren W. Gay VE3WWG
2002-08-05 11:15 ` Dmitry A. Kazakov
2002-08-12 12:44 ` Robert Dewar
2002-08-13 22:50 ` Randy Brukardt
2002-08-14 0:02 ` Robert A Duff
2002-07-25 0:40 ` Robert Dewar
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox