[comp.lang.ada] limited private types

leake@cme-durer.ARPA (Stephe Leake) (11/29/88)

In article <7796@aw.sei.cmu.edu> dd@sei.cmu.edu (Dennis Doubleday) writes:

   In article <739@marvin.cme-durer.ARPA> leake@cme-durer.ARPA (Stephe Leake) writes:
   >In article <7882@nsc.nsc.com> rfg@nsc.nsc.com (Ron Guilmette) writes
   >>   I have some problems with limited private types too.  Specifically, if such
   >>   types are really only accessable through a "limited" functional interface,
   >>   then why shouldn't we be able to declare such types in the public part of
   >>   a package and then defer the full declaration until the package body?
   >
   >What would this gain? Do you have any specific examples where this
   >would have improved the readability or functionality of an
   >application? Let's not just suggest changes because they "sound good".
   >Let's put some thought into it, including concrete examples.

   There is a VERY good reason for it, if you're at all concerned about
   portability.  The declaration of a limited private type is an
   IMPLEMENTATION decision.  It doesn't belong in the specification, it
   belongs in the body.  If I'm trying to export some abstract data type
   from a package, I'd like to have a specification that could be moved,
   intact, from one machine to another.  Only the body should need
   changing.  However, since I must give the full declaration of the type
   in the private part of the spec instead of in the body, it may be that
   I will have to change that part of the spec if I want to port the
   package to a machine which requires a different implementation of the
   type. 

This is true, but it doesn't allow separate compilation; you cannot
compile bodies that use the limited private type untill the size of
that type is known. One way around this is to declare the limited
private type to be a pointer to a type whose full declaration is
defered to the body of the package:

package My_Private_Type

type Hidden is limited private;
...
private

type Real_Hidden;
type Hidden is access Real_Hidden;

end My_Private_Type;

Admittedly, access types are not the best in all situations, but this
does enhance portability.

   >Why does your post sound so hostile?  How do you know that Ron hasn't
   >put any thought into his suggested changes?

I didn't mean to sound hostile, I was simply frustrated with the lack
of information. If Ron was concerned with the issue you raised, he
should have said so explicitly. Believing someone has good reasons,
and knowing what those reasons are, are two different things.

Stephe Leake 	(301) 975-3431 		leake@cme.nbs.gov
National Institute of Standards and Technology
(formerly National Bureau of Standards)
Rm. B-124, Bldg. 220
Gaithersburg, MD  20899

rfg@nsc.nsc.com (Ron Guilmette) (12/02/88)

In article <748@marvin.cme-durer.ARPA> leake@cme-durer.ARPA (Stephe Leake) writes:
>
>In article <7796@aw.sei.cmu.edu> dd@sei.cmu.edu (Dennis Doubleday) writes:
>
>   In article <739@marvin.cme-durer.ARPA> leake@cme-durer.ARPA (Stephe Leake) writes:
>   >In article <7882@nsc.nsc.com> rfg@nsc.nsc.com (Ron Guilmette) writes
>   >>   I have some problems with limited private types too.  Specifically, if such
>   >>   types are really only accessable through a "limited" functional interface,
>   >>   then why shouldn't we be able to declare such types in the public part of
>   >>   a package and then defer the full declaration until the package body?

>This is true, but it doesn't allow separate compilation; you cannot
>compile bodies that use the limited private type untill the size of
>that type is known. One way around this is to declare the limited
>private type to be a pointer to a type whose full declaration is
>defered to the body of the package:

OK. Now there no >'s in from so this is me (Ron Guilmette) talking again.
Sorry for all the >>>>'s above, but I just couldn't pass up the chance to
totally confuse the issue by quoting quotes of comments on quotes of
comments of what I said way back when.  :-)

Anyway, I *do* know about the special tricky Ada rule which allows you
to defer a full type definition until the corresponding package body
(if that type is only used in the package spec to help define pointer
types).

>Admittedly, access types are not the best in all situations, but this
>does enhance portability.
>
>   >Why does your post sound so hostile?  How do you know that Ron hasn't
>   >put any thought into his suggested changes?
>
>I didn't mean to sound hostile, I was simply frustrated with the lack
>of information. If Ron was concerned with the issue you raised, he
>should have said so explicitly. Believing someone has good reasons,
>and knowing what those reasons are, are two different things.

Also, although I didn't explicitly say it, I did mean to make a point
about the "separation of concerns" between a spec and a body, which
Ada generally supports very well.  (I don't think that "portability"
per se is quite the issue that I'm concerned about).  Anyway, don't
worry about apparent hostility.  I'm generally pretty thick skinned
(and some would say thick-headed).

The point I wanted to make was this.  If you look at all the things that
you are allow to do with an object of a limited private type (as given
in LRM 7.4.2 and 7.4.4) in the scope where the given type is *actually*
limited (i.e. within the *whole* scope of the type name but *not* in the
same scope as the full type definition for the type) then you find out
that there isn't very much you can do with (or to) an object of a limited
private type.

Basically, about the only thing you can do with such objects is to pass
them as actual parameters to subprograms.  Obviously, if you do this, then
for each such subprogram, the actual *definition* of the given subprogram
must be compiled only *after* the full type declaration has already been
compiled.

"Why?" you ask.  Well it is because of the fact that Ada insists that
scalars must always be passed by value-result.  Non-scalars may be passed
any old way (e.g. by reference).

Since any given Ada compiler must generate code to copy scalar actual
parameters into their corresponding formals, and since the most efficient
way to do this is to know ahead of time the total size (e.g. 32 bits)
of the given parameter, and since the full type definition of a given
limited private type (object) may sometime be scalar, The Ada designers
made the only wise choice available to them, i.e. to insist that the
full type declaration for any arbitrary limited private type be given
in the same compilation (unit) as the (original) limited type declaration.

This rule insures that the compiler will always know the actual size
of all formal parameters at least before the end of the current compilation.
Thus, the "scalar-by-value-result" rule (see LRM 6.2{6}) may be obeyed
while parameter passing is kept efficient.

What I was proposing was the suspension of the "scalar-by-value-result"
rule, and a substitution (in LRM 6.2{6,7,8}) of language which would
instead insist that *all* parameters, regardless of type, be passed
by reference.

This would yield two benefits:

1)  We could put our (corresponding) full type declarations for our
    limited types into packages bodies where they seem to belong (and
    where they require fewer re-compilations when changed) and

2)  we could make the language simpler by having one single (standard?)
    type of parameter passing *semantics* for *all* types of objects.

Another (dubious?) benefit of my "pass-by-reference-only" rule would be
that it would make all the ex-FORTRAN people feel comfortable (and would
probably simplify mixed-language interfacing where Ada & FORTRAN were 
concerned).  Note that FORTRAN has *always* used the "pass-by-reference-only"
rule.

Note that the programmer himself can easily achieve the semantics of
"pass-by-value-result" if he/she is given the base semantics of "pass-
by-reference" but not vise versa!  To get value-result semantics given
a base of by-reference semantics, all one has to do is make one's own
temporary copies (of the variable(s) involved) within the subprogram in
question.

sommar@enea.se (Erland Sommarskog) (12/04/88)

Ron Guilmette (rfg@nsc.nsc.com.UUCP) wants to be able to defer the 
implementation of a (limited) private type to the body of his package.
He writes:
>Anyway, I *do* know about the special tricky Ada rule which allows you
>to defer a full type definition until the corresponding package body
>...
>Basically, about the only thing you can do with such objects is to pass
>them as actual parameters to subprograms.  Obviously, if you do this, then
>for each such subprogram, the actual *definition* of the given subprogram
>must be compiled only *after* the full type declaration has already been
>compiled.
>...
>Since any given Ada compiler must generate code to copy scalar actual
>parameters into their corresponding formals, and since the most efficient
>...

Since you have thought so much on the not-so-obvious problem of parameter 
passing, you must have found the answer to the more obvious problem of
heap and stack allocation as trivial. I am probably stupid, because I
fail to see any attractive solutions to that. If in my block I declare:
   A : A_limited_type;
   B : Another_limited;
   C : ARRAY(1..20) OF Yet_another_limited;
If the implementation of the types in available in the specification 
of the types originating package, there is no problem. But if you defer
them to the body, and do not allow cheating with pointers? Enlighten
me, how to you allocate them on the stack? Implicit pointers? Some
implicit size-telling function called at run-time? Not very attractive,
since the place on the stack for other local variables is dependent 
of the size of the types.

To be straight I think that the pointer "trick" serves our needs 
perfectly here, and that your proposal is just YAUAC. (Yet Another 
Unnecessary Ada Change.)
-- 
Erland Sommarskog
ENEA Data, Stockholm
sommar@enea.se
"Frequently, unexpected errors are entirely unpredictable" - Digital Equipment

stachour@umn-cs.CS.UMN.EDU (Paul Stachour) (12/05/88)

In article <8197@nsc.nsc.com> rfg@nsc.nsc.com.UUCP (Ron Guilmette) writes:
>
>What I was proposing was the suspension of the "scalar-by-value-result"
>rule, and a substitution (in LRM 6.2{6,7,8}) of language which would
>instead insist that *all* parameters, regardless of type, be passed
>by reference.
>
>This would yield two benefits:
>
>1)  We could put our (corresponding) full type declarations for our
>    limited types into packages bodies where they seem to belong (and
>    where they require fewer re-compilations when changed) and
>
>2)  we could make the language simpler by having one single (standard?)
>    type of parameter passing *semantics* for *all* types of objects.
>
>Another (dubious?) benefit of my "pass-by-reference-only" rule would be
>that it would make all the ex-FORTRAN people feel comfortable (and would
>probably simplify mixed-language interfacing where Ada & FORTRAN were 
>concerned).  Note that FORTRAN has *always* used the "pass-by-reference-only"
>rule.

  I have programmed in a variety of languages over the 25 years
(wow, it is hard to belive, but it is 25 years)
that I have been programming.  In that time, I've learned a LOT
about parameter-passing.
  I learned about Multics and good PL/I implementatations only
after I already had 15 years of work, like 10 years ago.  One of
the things I discovered was how efficent its parameter-passing was,
as well as being flexible.  PL/I likes to pass everything by
reference, and that's good (I'll say why in a moment).  Multics uses
2n+1 (or n+1) words for each parameter-list.  nwords for arg-pointers,
nwords for arg-desicriptors (iff needed), and 1-word for the arg-count,
discriptor-count, and whether args or discriptors exist at all.
Functions get an extra-arg (the result) created in the caller.
This means, in general, that one can often use an arg-list fully
prepared at compilation-time (sometime modified at execurion-time),
and just pass one-pointer to a calling routine. Long and well-thought
Ada-style paramater-lists (including defaults) can be done very
well using this mechanism.
  But the real advantage of reference semantics comes when you
have data-structures being accessed by multiple tasks, as we are
now starting to do in Ada, and Multics has done well for more
than the 10 years I've known it.  You pass an argument to a
subroutine, it changes it, and the change is immediately, instantly,
visable to all routines.  And without the messiness of passing
explicit pointers.  Now, languages that don't allow reference
semantics at all are really ugly.  C's sometimes-you-must (stucts)
and sometimes-you-mussent (arrays) are confusing.  Ada's consistancy
(at least for composites) is refreshing after the last two years
I've been fighting C to get jobs done.
  However, I don't believe that Ada REQUIRES reference semantics
for composites, it merely allows them.  And in these days of
distributed programs, it is questionable what kind of efficiency
one can get with reference semantics over many machines.
[The questions are similar to those of efficiency in virtual-machines
20 years ago; questions which were well-settled by the measurements
in Multics showing that IO actually got cut in half in virtual-memory
+ sharing paradym over thant of physical-memory + explicit IO.
Too bad most hardware/software groupings we call computer systems
still can't seem to do good virtual-memory management, but that's
another discussion.]
  I'm for reference semantics.  They are natural, efficient, and
don't force this explicit-pointer "junk".  But let's make sure
what we can do with them in today's environment before we ask
Ada 9X to mandate them.

ryer@inmet.UUCP (12/07/88)

I agree it would be nice to be able to defer specifying the details
of the private type until the body is compiled.  Also, the reason for
the present restriction is so that the compiler can know the size of
the type.  Implicitly generated heap objects would work ok (forcing all
passage of private types by reference).  HOWEVER:

Ada now provides the pointer-based semantics when the user explicitly codes
an access type.  It ALSO provides the more efficient direct allocation/access
when the user does not code a access type.   The language changes proposed 
would eliminate one option.  It is possible to build an Ada compiler which 
NEVER uses the heap or any sort of pointer to access ordinary objects of 
static sizes, and at least one is very careful of this.  This is important
to those who use Ada for realtime systems.  

Many actual private types in real programs turn out to be integers.  Passing
these by value makes a big difference in the performance of the generated
code.  (Often a procedure parameter is already in a register at the call
site, and some optimizers contrive to make it the appropriate one so the cost
for passing it is zero as compared to a store at the call site followed by
a load in the caller).  Some Ada applications are built for small machines
with restricted memory and modest throughput and have to meet timing constraints
that are very tight (e.g. 100 hertz).  

As we used to say in the Ada language design days, "any good compiler will
resolve this", in this case by only introducing an implicit access type when
the private type's definition is missing from the spec and otherwise generating
the same code as now.  I hope we've learned that compiler writers sometimes
take the easiest path and defer the optimal approach to some future release.
We've gone through years of performance problems with Ada because of compilers
that weren't optimal.  Efficient approaches were proposed for every language
feature when the language was being designed, and features were dropped it
nobody could come up with an efficient implementation.  It turns out that 
compilers aren't as good as the language designers contemplated even now.

There had been no mention of the impact on the generated code in this note
stream and I had to post this flame.  In the 9X cycle, we need to ask
not only whether an efficient implementation is feasible, but whether
it is likely to appear in most compilers within a reasonable time.

I "vote" strongly against the proposal that ALL parameters should be
passed by reference  -- that clearly would make Ada less efficient on
most machines however good the compilers are.

The slow acceptance of Ada has been due to performance problems in the
generated code and speed and reliability problems in the compilers, not
due to a lack of flexibility in dealing with private types or other features.
Lets not add complexity and reduce performance this time around.

Mike Ryer
Intermetrics, Inc.

stt@inmet (12/07/88)

All Ada compilers already deal with "stack" objects
of size unknown at compile time (e.g. x : string(1..f(y));).
Some compilers do this more efficiently than others,
but the big advantage is that the compiler takes care
of freeing the associated storage when the object goes away.
When access types are used, it is always a challenge to
figure out how to ensure that the storage gets efficiently
reclaimed.  (This is one of the big reasons why
I believe user-defined "finalization" should be added into the language.)

Nevertheless, access types with a deferred incomplete designated
type are still used in complex systems, because they break
recompilation dependencies.  It seems like allowing the
deferral of any private type to a package body would be little or no
additional burden on compiler writers, and would provide
the same nice break in recompilation dependency when used.  
For non-discriminated types,
the size would be an elaboration-time constant.  For discriminated types,
the size would be determined by a function call (many
compilers already create size functions to save code space
for complex variant record size calculations).
However, it would be appropriate to require that the full
definition of the private type be a record type, so that
pass-by-reference semantics would be allowed.  

The syntax for such a deferred private record type might be:
     . . .
  private
      type Priv is record;
     . . .

I fully agree with the importance of pass-by-value semantics
for all scalars/access-values.  It is easy enough to wrap a scalar
into a record if pass-by-reference semantics are preferred for some
particular application.  The improved speed possibilities for pass-by-value
scalars far outweigh any abstraction disadvantage.

S. Tucker Taft
Intermetrics, Inc.
Cambridge, MA  02138

leake@cme.nbs.gov (Stephe Leake) (12/07/88)

In article <8197@nsc.nsc.com> rfg@nsc.nsc.com (Ron Guilmette) writes:

   What I was proposing was the suspension of the "scalar-by-value-result"
   rule, and a substitution (in LRM 6.2{6,7,8}) of language which would
   instead insist that *all* parameters, regardless of type, be passed
   by reference.

   This would yield two benefits:

   1)  We could put our (corresponding) full type declarations for our
       limited types into packages bodies where they seem to belong (and
       where they require fewer re-compilations when changed) and

   2)  we could make the language simpler by having one single (standard?)
       type of parameter passing *semantics* for *all* types of objects.

This sounds reasonable, but will obviously have a performance impact
where lots of scalar parameter passing is going on (something I don't
do much - records are much more expressive than scalars). But since
the problem is really only with (limited) private types, how about
insisting only those must be passed by reference?

Stephe Leake 	(301) 975-3431 		leake@cme.nbs.gov
National Institute of Standards and Technology
(formerly National Bureau of Standards)
Rm. B-124, Bldg. 220
Gaithersburg, MD  20899