Version 1.4 of ai05s/ai05-0074-2.txt
!standard 7.1(3) 08-07-20 AI05-0074-2/01
!standard 7.1(6/2)
!standard 7.3(4)
!standard 7.4(3)
!standard 8.2(2)
!standard 8.2(8)
!standard 8.2(9)
!class Amendment 08-01-18
!status work item 08-01-18
!status received 07-09-27
!priority Medium
!difficulty Hard
!subject Allowing an explicit "end private;" in a package spec
!summary
Allow "end private;" in a package spec to end the private part, and
allow additional visible declarations (such as generic instantiations)
to be provided thereafter. These post-private declarations may take
advantage of the fact that the private views and deferred constants of
the package have been completed.
!problem
Ada 95 provides formal package parameters. One way of using these parameters is
to define a "signature" for a class of abstractions, such as all set
abstractions, or all physical unit abstractions, and then build a new generic
abstraction on top of the original class of abstractions using this signature as
the template for a formal package parameter.
Unfortunately, it is difficult to use signatures because of the fact that an
instantiation freezes all of its actual parameters.
For example:
Given the signature for a set abstraction:
generic
type Element is private;
type Set is private;
with function Size(Of_Set : Set) return Natural is <>;
with function Union(Left, Right : Set) return Set is <>;
with function Intersection(Left, Right : Set) return Set is <>;
with function Empty return Set is <>;
with function Unit_Set(With_Item : Element) return Set is <>;
with function Nth_Element(Of_Set : Set) return Element is <>;
package Set_Signature is end;
... we could define a generic that required some set
abstraction, but it didn't care which one so long
as it implemented the above signature:
generic
with package Base_Set is new Set_Signature(<>);
package Layered_Abstraction is
type Cool_Type(Set : access Base_Set.Set) is limited_private;
procedure Massage(CT : in out Cool_Type; New_Set : Base_Set.Set);
...
end Layered_Abstraction;
Now if we want to define a set type and provide the pre-instantiated
signature for it, we run into trouble:
generic
type Elem is private;
with function Hash(El : Elem) return Integer;
package Hashed_Sets is
type Set is private;
function Union(Left, Right : Set) return Set;
...
package Signature is new Set_Signature(Elem, Set);
private
type Set is record
...
end record;
end Hashed_Sets;
The problem is that we can't do the instantiation of Set_Signature where we
would want to do so, because the instantiation freezes the type "Set"
prematurely.
A similar problem occurs when a type wants to include a pointer to a container
based on the type being defined. For example:
package Expressions is
type Expr_Ref is private;
package Expr_Sequences is new Sequences(Expr_Ref);
--
type Seq_Of_Expr is access Expr_Sequences.Sequence;
function Operands(Expr : Expr_Ref) return Seq_Of_Expr;
...
private
type Expression; --
type Expr_Ref is access Expression;
end Expressions;
Here we have a case where we want to instantiate a generic using a private type,
and use the results of the instantiation as the designated type of an access
type, which is in turn used as a parameter or result type of an operation on the
private type.
Unfortunately, we can't instantiate the "Sequences" generic with Expr_Ref, since
it is private.
!proposal
We propose to augment the syntax for package specs so that the end of
the private part may be specified (with "end private;") and thereafter
additional visible declarations may be provided. In the "post-private"
visible part, no new private views nor deferred constants may be
declared, as there would be no place to complete them.
When there is an "end private;" we might recommend that the declarations
in the private part be indented. For example:
package Abc is
type T is private;
Null_T : constant T;
private
type T is new Integer;
Null_T : constant T := 0;
end private;
package Lists_Of_T is new Generic_Lists(T);
type T_List is new Lists_Of_T.List;
end ABC;
Allowing visible declarations after the private part allows generic
instantiations and other constructs that freeze a private type (such as
record extensions or variable declarations), to appear within the same
package spec that defines the private type.
The post-private visible part has no visibility on the private part. In
that way it is analogous to the visible part of a child package.
However, it can rely on the fact that all private views and deferred
constants have been completed. We do not allow multiple "private ...
end private;" parts because then it would not be clear in which one a
given private view or deferred constant is completed, without violating
privacy.
!wording
Replace 7.1(3) with the following:
package_specification ::=
package defining_program_unit_name is
{basic_declarative_item}
[private
{basic_declarative_item}
[end private;
{basic_declarative_item}]]
end [[parent_unit_name.]identifier]
Modify 7.1(6/2) as follows:
The first list of basic_declarative_items {and optional third list of
basic_declarative items} of a package_specification of a package
other than a generic formal package {comprise what} is called the
visible part of the package. [Redundant: The optional list of
basic_declarative_items [after] {following} the reserved word private
(of any package_specification) is called the private part of the
package. If the reserved word private does not appear, the package
has an implicit empty private part.] Each list of basic_declarative_
items of a package_specification forms a declaration list of the
package.
Modify 7.3(4) as follows:
A private_type_declaration or private_extension_declaration declares
a partial view of the type; such a declaration is allowed only as a
declarative_item of {the first declaration list of} the visible part
of a package, and it requires a completion, which shall be a
full_type_declaration that occurs as a declarative_item of the
private part of the package. [Redundant: The view of the type
declared by the full_type_declaration is called the full view.] A
generic formal private type or a generic formal private extension is
also a partial view.
TBD: is it OK to refer to the "first declaration list of the visible
part of a package"? Does that work for generic packages, or
is the first declaration list the formal part? do we need to
say "first list of basic_declarative_items of the visible part
of the package"? Same question applies for deferred constants
in 7.4(3).
Modify 7.4(3) as follows:
A deferred constant declaration that is completed by a full constant
declaration shall occur immediately within {the first declaration
list of} the visible part of a package_specification. For this case,
the following additional rules apply to the corresponding full
declaration:
Add after 8.2(2):
* The immediate scope of a declaration in the private part of a package
or generic package does not include the optional third declaration
list of the package_specification, [Redundant: following the
reserved words END PRIVATE.]
Modify 8.2(8) as follows:
* The visible part of a generic unit includes the generic_formal_part.
For a generic package, it also includes the first list {and optional
third list} of basic_declarative_items of the package_specification.
For a generic subprogram, it also includes the profile.
Modify 8.2(9) as follows:
* [Redundant: The visible part of a package, task unit, or protected unit
consists of declarations in the program unit's declaration other
than those following the reserved word PRIVATE, if any{, and
preceding the reserved words END PRIVATE, if any}; see 7.1 and 12.7
for packages, 9.1 for task units, and 9.4 for protected units.]
!discussion
This proposal solves several problems that come up frequently in
practice with private types. Certainly the most common is a desire
to include an instantiation of a generic (possibly a signature
generic as mentioned in the "problem" section above, or perhaps
a container generic), with a private type as one of the actual
parameters to the instantiation. Another problem is a desire
to export a variable of a private type. This is not permitted
prior to the private type being completed.
A third problem is best illustrated by an example:
package Constructs is
type Root_Construct_Handle is abstract tagged private;
...
generic
type Data_Part is private;
package Handles is
type Construct_Handle is
new Root_Construct_Handle with private;
type RO_Access is access constant Data_Part;
type RW_Access is access all Data_Part;
function Get_RO_Access(Handle : Construct_Handle)
return RO_Access;
function Get_RW_Access(Handle : Construct_Handle)
return RW_Access;
private
type Construct_Handle is
new Root_Construct_Handle with ...
--
--
end Handles;
private
type Root_Construct_Record;
--
type Root_Construct_Access is
access all Root_Construct_Record;
type Root_Construct_Handle is abstract tagged
record
Construct : Root_Construct_Access;
...
end record;
end Constructs;
Here we want to define a nested generic package that exports a private
extension of the private type Root_Construct_Handle. Everything is fine
until we get to the private part of this nested generic, and we realize
we need to give the full definition for the private extension, and we
cannot, because that will freeze the private type.
In all of these cases, moving the offending construct to after "end
private;" would work as desired. A generic instantiation that takes the
private type as a parameter, a variable of the private type, or a nested
generic as illustrated above, would all be fine if declared after the
completion of the private type. Note that none of these need actual
visibility on the full definition of the type, they simply need the
private type to have been completely defined, so that it is OK to freeze
it.
We have considered many alternatives in the past, such as "limited"
instantiations of some sort, and Ada 2005 itself provides some
"workarounds" that address the problems to some extent, involving child
units or "limited with" (see below). But none of these alternatives
address the core of the problem, which is a desire to have declarations
in the same package spec where a private type is declared that currently
are illegal because they freeze the private type.
Allowing "end private;" has relatively straightforward semantics, with
no new freezing rules or new kinds of instantiations. Other than the
syntax change, the main significant change is the proposed 8.2 rule that
the immediate scope of a declaration in the private part does not
include the list of visible declarations, if any, that follow "end
private;". This makes them very similar to the declarations in the
visible part of a child unit.
IMPLEMENTATION ISSUES
Some implementors mentioned a concern that rarely do we "lose"
visibility in a single construct, and that their implementations can't
easily support that. One approach might be to treat the post-private
visible part roughly like the visible part of a child, by starting from
scratch and building up the visibility that corresponds to that which a
(public) child unit sees, rather than trying to "remove" declarations
from the existing visibility at the end of the private part.
Another possible approach might be to treat the private part roughly
like a nested construct, where the visibility is "pushed" before the
private part, and then "popped" afterward. Prior to popping the private
part visibility, it would want to be "saved" so that when it is wanted
again, such as in the package body, or in the private part of some
child, it can be restored. This kind of "save" might be similar to
what is done with subunit "stubs."
Ada 2005 WORKAROUNDS
Here are some examples of how the problem could be addressed using
existing Ada 2005 capabilities.
For the first example, making the instantiation a child unit solves the
problem. However, this is annoying, because accessing the instance
requires an extra with and instantiation (since children of generics
must be generic):
generic
type Elem is private;
with function Hash(El : Elem) return Integer;
package Hashed_Sets is
type Set is private;
function Union(Left, Right : Set) return Set;
...
private
type Set is record
...
end record;
end Hashed_Sets;
generic
package.Hashed_Sets.Signature is
package The_Signature is new Set_Signature(Elem, Set);
end Hashed_Sets.Signature;
A user of Hashed_Sets must with and instantiate Hashed_Sets.Signature in
order to access the instance.
The second problem can also be solved with child units, using the
limited with:
limited with Expressions.Sequences;
package Expressions is
type Expr_Ref is private;
type Seq_Of_Expr is access Expressions.Sequences.Sequence;
function Operands(Expr : Expr_Ref) return Seq_Of_Expr;
...
private
type Expression; --
type Expr_Ref is access Expression;
end Expressions;
package Expressions.Sequences is
package Expr_Sequences is new Sequences(Expr_Ref);
type Sequences is new Expr_Sequences.Sequence;
end Expressions.Sequences;
Here, besides the extra with clause, we need to declare an extra type
simply so that the type is visible to the limited with clause (which
operates purely syntactally). This means that extra type conversions are
necessary.
Here is the generic signature example, using "end private;":
generic
type Elem is private;
with function Hash(El : Elem) return Integer;
package Hashed_Sets is
type Set is private;
function Union(Left, Right : Set) return Set;
...
private
type Set is record
...
end record;
end private;
package Signature is new Set_Signature(Elem, Set);
end Hashed_Sets;
Here is an example where we want to instantiation a container:
package Expressions is
type Expr_Ref is private;
type Expr_Seq; --
type Seq_Of_Expr is access Expr_Seq;
function Operands(Expr : Expr_Ref) return Seq_Of_Expr;
...
private
type Expression; --
type Expr_Ref is access Expression;
end private;
package Expr_Sequences is new Sequences(Expr_Ref);
type Expr_Seq is new Expr_Sequences.Sequence;
--
end Expressions;
Here is the nested generic with a private extension:
package Constructs is
type Root_Construct_Handle is abstract tagged private;
...
private
type Root_Construct_Record;
--
type Root_Construct_Access is
access all Root_Construct_Record;
type Root_Construct_Handle is abstract tagged
record
Construct : Root_Construct_Access;
...
end record;
end private;
generic
type Data_Part is private;
package Handles is
type Construct_Handle is
new Root_Construct_Handle with private;
type RO_Access is access constant Data_Part;
type RW_Access is access all Data_Part;
function Get_RO_Access(Handle : Construct_Handle)
return RO_Access;
function Get_RW_Access(Handle : Construct_Handle)
return RW_Access;
private
type Construct_Handle is
new Root_Construct_Handle with ...
--
--
end Handles;
end Constructs;
!ACATS test
ACATS B and C-Test(s) are necessary.
!appendix
[Editor's note: For earlier ideas on this topic, see AI95-359-1, AI95-359-2,
AI95-359-3, AI95-359-4, and AC-123, as well as the other alternative.]
From: Tucker Taft
Date: Thursday, September 27, 2007 12:33 PM
Robert made the mistake of asking me before his
talk at AdaUK on Tuesday what was on my "wish
list" for Ada 2015. I mentioned three things:
1) Instantiation of generics with private types
2) Region-based storage management
3) Units checking
This of course got me to thinking some more about these
things. As far as instantiation of generics with
private types, in the same package where the private
type is declared, I am currently enamored of two
different solutions, both of which might be interesting
in their own right:
A) Allow the notion of "end private;" where the private
part ends before the end of the package spec, and then
you can return to visible declarations.
B) Allow the ability to have child units of a
package or generic package that are effectively
"pre-withed" by all dependents.
With (A), you simply move the instantiations to a point
after the "end private;" and then you can instantiate
any generic with the now completed private types
declared in the package. Robert immediately asked whether
it made sense to have multiple private parts in a
single package. I didn't have an immediate response.
With (B), you make your instantiations be child units,
and then "pre-with" them on behalf of your clients,
and then you are able to use private types in
the instantiations, and your clients think of them as
sub-packages. I believe GNAT may at one point have
played a trick roughly like this for Text_IO, by making
Integer_IO and friends into children of Text_IO, but
"pre-with"ing them so clients see them as subpackages.
If a generic unit "pre-withs" a child, then there is
no particular reason the child needs to be limited to
being itself a separate generic, since the instantiator of the
parent unit "knows" to instantiate the pre-with'ed
children as well.
Syntactically, I thought that given "with" creating
a dependence starting at the beginning of the
compilation unit, and "private with" creating
a dependence starting at the word "private,"
then "end with <child1>, <child2>;" might be
a natural way to "pre-with" the specified children,
with it taking effect at the end of the
compilation unit.
E.g. for Text_IO, one might have:
private with System.RTS.Files;
end with Ada.Text_IO.Integer_IO,
Ada.Text_IO.Enumeration_IO,
Ada.Text_IO.Float_IO, ...
package Ada.Text_IO is
type File_Type is private;
...
private
type File_Type is new
System.RTS.Files.File_Type with ...
end Ada.Text_IO;
-- conceptually, the "with"s of children
-- happen here, and *are* inherited by all
-- clients of Ada.Text_IO.
Comments, flames, etc. as usual.
****************************************************************
From: Randy Brukardt
Date: Thursday, September 27, 2007 1:41 PM
> Comments, flames, etc. as usual.
How's your homework coming? ;-)
Oh, you wanted comments on your ideas??
The second one looks more appealing (not the syntax, though!), because it
doesn't require changing the visibility model of the language. However, the
"forward" reference in the "end with" as you have it would be problematical,
because it requires referencing something that by definition could not have
yet been created. I know that when I tried to use such an idea in one of the
early proposals for what became limited with, it was not well received by
users. I'm not sure if that is a real problem in this context.
Syntactically, I'd suggest something like "include with", which would of
course require another keyword.
Going back one more step, here's my "wish list" for Ada 2015:
1) In out parameters for functions (with expressions with function calls for
which order could change the result being made illegal);
2) More exposure of concurrency for multi-core systems (combined with more
checking to reduce the possibility of deadlocks/indeterminate results);
3) Instantiation of generics with private types.
****************************************************************
From: Robert A. Duff
Date: Thursday, September 27, 2007 6:00 PM
> 1) Instantiation of generics with private types
It seems to me that the most important case is where there is some mutual
recursion going on, such as:
with Ada.Containers.Vectors; use Ada.Containers;
package Trees is
type Tree is private;
package Tree_Vectors is new Vectors (Tree,...); -- Illegal!
...
private
type Tree is
record
...
Subtrees : access Tree_Vectors.Vector;
end record;
end Trees;
Otherwise, you can just put the instance in a child. That's an annoyance,
because clients have to 'with' it, but it doesn't prevent you from doing
anything. The mutually recursive case, on the other hand, requires massive
restructuring to work around the restriction.
****************************************************************
From: Tucker Taft
Date: Thursday, September 27, 2007 11:16 PM
> It seems to me that the most important case is where there is some mutual
> recursion going on, such as:
>
> with Ada.Containers.Vectors; use Ada.Containers;
> package Trees is
> type Tree is private;
> package Tree_Vectors is new Vectors (Tree,...); -- Illegal!
> ...
> private
> type Tree is
> record
> ...
> Subtrees : access Tree_Vectors.Vector;
> end record;
> end Trees;
Couldn't you just use an incomplete type here:
with Ada.Containers.Vectors; use Ada.Containers;
package Trees is
type Tree is private;
...
private
type Tree_Vector;
type Tree is
record
...
Subtrees : access Tree_Vector;
end record;
package Tree_Vectors is new Vectors (Tree,...);
type Tree_Vector is new Tree_Vectors.Vector;
end Trees;
Presuming you are going to use access values, I don't
see a problem. You could still use the "end private"
to make the instantiation to be *visible*, if that
were desirable.
> Otherwise, you can just put the instance in a child. That's an annoyance,
> because clients have to 'with' it, but it doesn't prevent you from doing
> anything. The mutually recursive case, on the other hand, requires massive
> restructuring to work around the restriction.
The one place where putting the instantiation in the child
doesn't really work is when you are trying to include
the instantiation of, say, a signature package within a generic.
A generic can't have a child that is an instantiation.
Either the "end private" or the "end with" would
solve this.
****************************************************************
From: Robert A. Duff
Date: Friday, September 28, 2007 7:00 AM
> Presuming you are going to use access values, I don't
> see a problem. You could still use the "end private"
> to make the instantiation to be *visible*, if that
> were desirable.
OK, that's reasonable, so long as there's a way to export Tree_Vector (and its
ops).
To have mutual recursion, Ada requires an access type to break the chain.
I would prefer any solution allow the programmer maximum flexibility in
where to put that access type (e.g. as above, versus in the generic, etc).
Otherwise, there will be cases where you are annoyingly forced to use
TWO access types.
> > Otherwise, you can just put the instance in a child. That's an annoyance,
> > because clients have to 'with' it, but it doesn't prevent you from doing
> > anything. The mutually recursive case, on the other hand, requires massive
> > restructuring to work around the restriction.
>
> The one place where putting the instantiation in the child
> doesn't really work is when you are trying to include
> the instantiation of, say, a signature package within a generic.
> A generic can't have a child that is an instantiation.
> Either the "end private" or the "end with" would
> solve this.
OK. But the "end private" idea has the advantage that you can refer to the
instance from within the package itself, right?
I'm pretty sure that if you want two visible parts, you're going to want
several visible parts and several private parts. Somebody famously said,
a programming language should have zero, one, or infinite of any thing.
By the way, the "end with" syntax is, well..., it will take some "getting used"
to. If I see "end" (especially at the beginning of a line) I think "this is
the end of something".
****************************************************************
From: Jean-Pierre Rosen
Date: Friday, September 28, 2007 9:05 AM
> By the way, the "end with" syntax is, well..., it will take some "getting used"
> to. If I see "end" (especially at the beginning of a line) I think "this is
> the end of something".
>
Hmmm... since the effect is to automatically with the children, what
about "with all" ?
(Designing new syntax is always great fun :-)
****************************************************************
From: Randy Brukardt
Date: Friday, September 28, 2007 1:11 PM
> By the way, the "end with" syntax is, well..., it will take some "getting used"
> to. If I see "end" (especially at the beginning of a line) I think "this is
> the end of something".
As I said yesterday, the idea doesn't bother me, but the syntax is horrible.
It doesn't convey the meaning of the construct at all (which is that the
with is "included" in any uses of this package). I suggested "include with",
because that conveys the meaning, and I would much rather that we referred
to Tucker's idea that way, so that it is intuitively obvious what it is
we're talking about. ("end private" has that effect, too, as well as
complete revulsion. :-). Maybe someone else has a syntax suggestion that
actually conveys the meaning (sorry, J-P, but "with all" is just as bad),
but in the absence of that, I strongly suggest that we use "include with"
since it does.
After all, my generic default type proposal was defeated primarily because
of syntax concerns. I would think Tucker would not want that to happen to
this idea.
****************************************************************
From: Tucker Taft
Date: Friday, September 28, 2007 2:27 PM
Or "all with Ada.Text_IO.Integer_IO;"
-- That is "all [clients implicitly] with Ada.Text_IO.Integer_IO"
Another approach is to use "is separate" which has
been bandied about in the past, and treat them
as package "spec subunits." E.g.:
package Ada.Text_IO is
...
generic package Integer_IO is separate;
generic package Float_IO is separate;
private
...
end Ada.Text_IO;
****************************************************************
From: Jean-Pierre Rosen
Date: Monday, October 1, 2007 3:23 AM
And how would it be different from a child package, apart from not
seeing the private part of the parent?
****************************************************************
From: Randy Brukardt
Date: Monday, October 1, 2007 2:30 PM
Because it would be automatically included whenever the parent is withed,
whereas a child unit has to be withed separately. Tucker is trying to make
it easier to separate specifications into separate units when the logical
view is a single unified specification.
****************************************************************
From: Tucker Taft
Date: Monday, October 1, 2007 2:53 PM
Bob makes the point that the "end private" idea
is more powerful, in that you can use items from
the instantiation to visibly complete incomplete types
declared before the instantiation. When trying
to create mutually recursive types, that seems
pretty important.
From an implementation point of view, multiple
visible parts also seems simpler than "spec subunits"
or "inclusive with" of children. Any comments
on the "end private" syntax, and/or whether a semicolon
is desirable after "end private"? I suspect a different
indenting would be used for "private/end private,"
such as:
package Trees is
type Tree is private
...
private
...
end private
...
end Trees;
****************************************************************
From: Edmond Schonberg
Date: Monday, October 1, 2007 3:22 PM
A smaller syntactic change would be to have both views of the type in
a single declaration:
private type Tree is record ...
You can have as many of those as you want, which answers the "zero,
one, infinity" rule. Full "private sections" would sit heavily on
my stomach (and Randy's too, I recall).
****************************************************************
From: Randy Brukardt
Date: Monday, October 1, 2007 3:45 PM
...
> From an implementation point of view, multiple
> visible parts also seems simpler than "spec subunits"
> or "inclusive with" of children.
Gee, Tucker, what are you smoking these days? I want some! :-)
The implementation of "inclusive with" is trivial: just jam an extra with
into the list of withed units when the appropriate thing is encountered in
the loaded unit. (But I do agree that it is not an ideal solution to the
mutual recursion problem.)
OTOH, the implementation of a new kind of visibility would have a lot of
effects throughout the compiler, because visibility is such a pervasive
idea. (And in Janus/Ada it is somewhat distributed, which makes things even
worse.) I also worry about information "leakage" out of the private part; we
currently have a number of rules intended to ensure that you cannot depend
on the contents of a private part, and it seems likely that there would be
effects visible in an "end private" part that would effectively render items
in the private part visible.
I still think this is a bad idea; I still think that two part instantiations
can work if sufficiently limited (we never really finished working on that
idea - it was only the broadly defined version that didn't work). Ed's idea
also would work (although it has the issue of exposing the implementation in
practice even if not semantically).
****************************************************************
From: Robert A. Duff
Date: Monday, October 1, 2007 5:15 PM
>...Any comments
> on the "end private" syntax, and/or whether a semicolon
> is desirable after "end private"?
From a purely syntactic point of view, I don't mind "end private".
It was "end with" that rubbed me the wrong way.
I suppose you can have multiple private parts, as in:
package P is
... -- public stuff
private
... -- private stuff
end private;
... -- public stuff
private
... -- private stuff
end private;
... -- public stuff
end P;
?
A semicolon? Yeah, I guess so. Shrug.
Syntax is important, but the semantics is where trouble might arise.
****************************************************************
From: Tucker Taft
Date: Monday, October 1, 2007 5:35 PM
I should know better than to guess at relative
implementation difficulties. My presumption
was that multiple visible parts were not
significantly different from single visible
parts. There isn't really a "new" kind of
visibility, is there? Some declarations
in the spec are private, and some are visible,
which is how it is now.
But I guess the universal rule in this area is:
Your Mileage May Vary.
I would be curious whether how other vendors
see the relative implementation difficulty.
One major problem I have with the "private type ..."
suggestion is that you don't have any way to
declare types that are not visible at all
using that notation. It is quite common that
some of the types of components of a private
type are not visible at all. I also worry
that the "private type ..." proposal is
a completely different approach to visibility
from a conceptual point of view, creating
a dramatic shift in perception of how things
are done.
****************************************************************
From: Randy Brukardt
Date: Monday, October 1, 2007 6:07 PM
> I should know better than to guess at relative
> implementation difficulties. My presumption
> was that multiple visible parts were not
> significantly different from single visible
> parts. There isn't really a "new" kind of
> visibility, is there? Some declarations
> in the spec are private, and some are visible,
> which is how it is now.
Well, each declaration is either visible or it is not at a particular point
of the program. But that changes for each different unit/scope that you go
through. There are already multiple kinds of private parts (package,
protected), and the rules for what is visible are complex.
Our implementation changes a visibility flag on each declaration as we enter
or leave different program units; how that works exactly is different for
packages, subprograms, protected types, task types, child units (and public
units are different than private units). [At least I *think* this is how it
works; I didn't design or write this code so I'm somewhat hazy on the
details.) For instance, for packages, there is a pointer that separates the
private declarations from the public ones. Obviously, if there are
additional parts, there are going to be more "declaration ranges" as well,
and that will make additional complications.
There are currently about 96 references to the "private_list" pointer in
Janus/Ada; all of those would need to be looked at. Obviously, some are just
initializations, but many seem to have important effects. 96 things to look
at seems like a massive job.
> But I guess the universal rule in this area is: Your Mileage May Vary.
Right; it's usually not possible to guess the implementation effort for some
other compiler. (And it's not that easy even for your own!)
I'd be especially be wary of comparing implementation effort between two
ideas unless one is absurdly simple (like just a syntax change).
> One major problem I have with the "private type ..."
> suggestion is that you don't have any way to
> declare types that are not visible at all
> using that notation. It is quite common that
> some of the types of components of a private
> type are not visible at all. I also worry
> that the "private type ..." proposal is
> a completely different approach to visibility
> from a conceptual point of view, creating
> a dramatic shift in perception of how things
> are done.
I agree with this, which is why I still think that the two part
instantiation (which matches up well with a two part type declaration) is
the way to go. The main problem I see is that the syntax choices we looked
at did not really properly convey what is going on (and that is important in
order to make the use intuitive). [I realize that there were semantic
problems, but I still think those could be worked out by limiting the
functionality of the "private" instantiation -- we never really tried that
seriously to see, as Pascal removed it from the Amendment agenda at that
point.]
****************************************************************
From: Robert I. Eachus
Date: Monday, October 1, 2007 6:59 PM
>I still think this is a bad idea; I still think that two part instantiations
>can work if sufficiently limited (we never really finished working on that
>idea - it was only the broadly defined version that didn't work). Ed's idea
>also would work (although it has the issue of exposing the implementation in
>practice even if not semantically).
I think that the end private (with or without semicolon) is conceptually
the easiest for users to master. I do think though that a restricted
version of that proposal would be a lot easier to both implement and
understand. First yes, it would be possible to have multiple private
parts, but is that game really worth the candle? If you have ONE
private part, then private types have to be declared before the private
part, along with deferred constants and so on. If we hang the
elaboration of child units then the package body right at the end of the
private part--or the package declaration if there isn't one--then
instantiations of children there are fine. As for making private
details visible, no reason it should. What you care about is that
private types, and for that matter subprograms in the (first) public
part are now elaborated.
Would there be some confusion from the fact that declarations in the
second public part would not be visible in the body? Not that much.
Would it be a problem? I'd have to do some experimentation, but I don't
see it as a problem. You might have to put some declarations in a child
package then use renaming in the second public part to make them visible
as part of the main package API. I have to think that in most cases
that will be what you want to do anyway.
Now to explain some magic handwaving above. How do you tell that a
reference in the second private part is to a child package? Either a
with clause, a use clause, or both in the second public part. Allowing
with clauses there is much more like the two part package declarations,
but a bit easier to maintain. I'd like to limit it to use clauses for
child units for methodological reasons, but such units could have their
own with clauses for anything, so that would not limit much in reality.
****************************************************************
From: John Barnes
Date: Monday, October 1, 2007 1:44 AM
Of all the suggestions, I like the end private idea. In fact I sent the
following a few days ago but it seemed to go down a black hole. I said in a
reply to Tuck:
> A) Allow the notion of "end private;" where the private
> part ends before the end of the package spec, and then
> you can return to visible declarations.
I suggested that to Jean in a letter in 1983 or thereabouts but he didn't
reply! I always thought that the Ada all-in-one-lump approach to visibility,
although elegant, was not flexible enough in practice. Another case of
needing truth before beauty.
PS And of course dammit - you need a semicolon!
****************************************************************
From: Jean-Pierre Rosen
Date: Monday, October 1, 2007 3:16 AM
> A smaller syntactic change would be to have both views of the type in a
> single declaration:
>
> private type Tree is record ...
Except of course that it would be ambiguous:
private
type Tree is ...
The more I think about the "end private" idea, the more uncomfortable I
feel. I'm afraid there is a huge can of worms sleeping under it...
For example:
Child units currently see *the* private part. Would they see all of them?
If I put a "use" clause in a private part, does it extend into the
following non-private parts?
How would the "private with" work? It should apply to all private parts,
therefore we'll have visibility coming in and out...
****************************************************************
From: Pascal Leroy
Date: Tuesday, October 2, 2007 8:00 PM
> Bob makes the point that the "end private" idea is more
> powerful, in that you can use items from the instantiation to
> visibly complete incomplete types declared before the
> instantiation. When trying to create mutually recursive
> types, that seems pretty important.
>
> package Trees is
> type Tree is private
> ...
> private
> ...
> end private
> ...
> end Trees;
My first reaction is that this reeks of C++.
This is one of the (many) things that I hate in C++: the declaration of a
class can include any number of public/protected/private sections in any
order. True, this is more flexible than the Ada model, but then it sooo
easy to misuse. In Ada, as soon as you see the word private, you know that
you are done with the exported declarations. In C++ you have to read the
entire class declaration, keeping track of all the visibility changes, just
to find out which declarations are exported. It is extremely easy to miss
one of the little words public/protected/private, and be just totally
confused about what a class does.
At any rate, it would be good to explain what exactly is visible after "end
private" and how you ensure that no private information is leaked out of the
package.
****************************************************************
From: Pascal Leroy
Date: Tuesday, October 2, 2007 8:01 AM
> I still think this is a bad idea; I still think that two part
> instantiations can work if sufficiently limited (we never
> really finished working on that idea - it was only the
> broadly defined version that didn't work).
I agree with this. I always thought that we were on the right track, but we
just didn't have enough time to find a model that would avoid all these
Baird pathologies.
****************************************************************
From: Robert A. Duff
Date: Tuesday, October 2, 2007 8:06 PM
> My first reaction is that this reeks of C++.
...says the guy who is leaving the Ada world for greener pastures. ;-) ;-)
And if the C++ standard says "the sky is blue", should we automatically dispute
that? ;-)
Note smileys -- I'm just teasing you, Pascal.
> This is one of the (many) things that I hate in C++: the declaration of a
> class can include any number of public/protected/private sections in any
> order. True, this is more flexible than the Ada model, but then it sooo
> easy to misuse. In Ada, as soon as you see the word private, you know that
> you are done with the exported declarations. In C++ you have to read the
> entire class declaration, keeping track of all the visibility changes, just
> to find out which declarations are exported. It is extremely easy to miss
> one of the little words public/protected/private, and be just totally
> confused about what a class does.
If it's properly indented, I don't find it hard to see the private/public
structure.
> At any rate, it would be good to explain what exactly is visible after "end
> private" and how you ensure that no private information is leaked out of the
> package.
Indeed. Demons lurk, here, in the details.
****************************************************************
From: Randy Brukardt
Date: Wednesday, October 24, 2007 11:32 PM
...
> At any rate, it would be good to explain what exactly is visible after "end
> private" and how you ensure that no private information is leaked out of the
> package.
Let's just look at a few cases that would need to be defined:
* What if is there is a use clause in the private part? Does it extend into the
following non-private part(s)? [This was from Jean-Pierre.] I think the answer
has to be no, but that surely complicates things (and implementations).
* How about "additional operations" of a type? That is, in something like:
package P is
type Q is limited private;
private
type Q is record
A : Integer := 10;
end record;
end private;
Obj : Q;
B : Boolean := (Obj = Obj);
end P;
Again, probably not. More generally, declarations in a private part should not be
visible in a following visible part, so we'd have visibility going out at "end private;"
and then returning when we reached the body.
with Func;
package P is
type Q is limited private;
private
type Q is access Integer;
Obj : Q;
end private;
I : Integer := Obj.all;
end P;
This reduced visibility worries me, as we generally don't lose visibility in a single
package: everything is additive (visible part, private part, body). It seems to offer
the opportunity for more anomalies.
* Do public children see all of the visible declarations but none of the private ones
of their parent? (Seems necessary.)
I'm sure there are more issues to consider. I haven't seen any posting by Steve Baird
on this yet. ;-) The whole thing sounds like fun. :-(
****************************************************************
From: Tucker Taft
Date: Thursday, October 25, 2007 7:25 AM
Randy Brukardt wrote:
> Pascal notes:
>
> ...
>> At any rate, it would be good to explain what exactly is visible after
> "end
>> private" and how you ensure that no private information is leaked out of
> the
>> package.
>
> Let's just look at a few cases that would need to be defined:
>
> * What if is there is a use clause in the private part? Does it extend into
> the following non-private part(s)? [This was from Jean-Pierre.] I think the
> answer has to be no, ...
Agreed, clearly no.
>
> * How about "additional operations" of a type? That is, in something like:
>
> package P is
> type Q is limited private;
> private
> type Q is record
> A : Integer := 10;
> end record;
> end private;
> Obj : Q;
> B : Boolean := (Obj = Obj);
> end P;
>
> Again, probably not. More generally, declarations in a private part
> should not be visible in a following visible part, so we'd have visibility
> going out at "end private;" and then returning when we reached the body.
Agreed.
>
> with Func;
> package P is
> type Q is limited private;
> private
> type Q is access Integer;
> Obj : Q;
> end private;
> I : Integer := Obj.all;
> end P;
>
> This reduced visibility worries me, as we generally don't lose visibility
> in a single package: everything is additive (visible part, private part,
> body). It seems to offer the opportunity for more anomalies.
Perhaps, though I think nested packages with private
parts are pretty similar. With nested packages,
visibility comes and goes.
Child packages are perhaps even closer to this. When
you start a child package, although they logically "follow"
the private part of their ancestor packages,
they don't "see" the private part. When you hit
the private part of the child package, suddenly
the private declarations of all the ancestors become
visible.
So from an implementation and anomaly point of view,
at the "end private;" you might imagine you are starting a
child package, which cannot take advantage of visibility
on the parent's private part until it hits its own private
part or body.
> * Do public children see all of the visible declarations but none of the
> private ones of their parent? (Seems necessary.)
They don't see the private declarations until they hit their
own private part or body. No real surprise here.
>
> I'm sure there are more issues to consider. I haven't seen any posting by
> Steve Baird on this yet. ;-)The whole thing sounds like fun. :-(
Clearly there will be some implementation effects, but I
don't see many semantic anomalies on the visibility side,
since I believe a child package experiences much the same
"coming and going" of visibility.
The anomalies I could imagine would have to do with "completion"
and overriding, since these kinds of things can't be
simulated by a child package (though a child subprogram
can "simulate" overriding in certain non-tagged cases).
And I agree we will need to work them through if we decide
to pursue this proposal. I am happy to do so, after
I have finished my other homework ... ;-).
****************************************************************
From: Robert A. Duff
Date: Thursday, October 25, 2007 9:15 AM
> Let's just look at a few cases that would need to be defined:
>...
I'm surprised by Tucker's answer. My answer is that "obviously", the
visibility in the second visible part includes everything that came before,
just as if "end private;" were erased. (Now Tuck can tell me why that's
stupid. ;-))
The purpose of privacy is to hide things from OTHER packages, not from the
package itself. This should be legal:
package P is
private
type Base_Type is range 1..2**31-1;
end private
subtype Exported_Type is Base_Type'Base;
end P;
to get a type that has at least 32 bits, but matches the hardware bounds. I
don't want to pester the client with a useless name -- Base_Type is visible in
the visible part, but not in clients.
By the way, C++ has a similar feature. It might help to look at the details,
which I don't remember. Of course C++ visibility is quite different from Ada,
so it might be irrelevant. And C++ visibility is broken in some ways, so we
don't necessarily want to mimic it -- but it couldn't hurt to look.
> * Do public children see all of the visible declarations but none of the
> private ones of their parent? (Seems necessary.)
Yes.
> I'm sure there are more issues to consider. I haven't seen any posting by
> Steve Baird on this yet. ;-)The whole thing sounds like fun. :-(
Indeed. I'm not at all sure the whole idea will work.
****************************************************************
From: Jean-Pierre Rosen
Date: Thursday, October 25, 2007 7:34 AM
> Let's just look at a few cases that would need to be defined:
>
And:
I assumed when I read the proposal that there could be several private
parts, but I didn't see any mention about that. Is it the intent? That
would certainly seem logical from a user point of view:
package Pack is
-- visible
private
-- hidden
end private;
-- visible again
private
-- hidden again
end Pack;
Have great fun with visibility going in and out!
****************************************************************
From: Randy Brukardt
Date: Thursday, October 25, 2007 7:56 PM
...
> > with Func;
> > package P is
> > type Q is limited private;
> > private
> > type Q is access Integer;
> > Obj : Q;
> > end private;
> > I : Integer := Obj.all;
> > end P;
> >
> > This reduced visibility worries me, as we generally don't
> lose visibility
> > in a single package: everything is additive (visible part, private part,
> > body). It seems to offer the opportunity for more anomalies.
>
> Perhaps, though I think nested packages with private
> parts are pretty similar. With nested packages,
> visibility comes and goes.
>
> Child packages are perhaps even closer to this. When
> you start a child package, although they logically "follow"
> the private part of their ancestor packages,
> they don't "see" the private part. When you hit
> the private part of the child package, suddenly
> the private declarations of all the ancestors become
> visible.
Not really: within a single unit, all that can happen is the *addition* of
new things. Once you can see the additional operations, they stay visible
until the end of the unit. (The situation in outer units may be different,
but I don't see that as relevant -- we know that visibility can change in
external units, but it doesn't reduce *directly within* a unit.)
> So from an implementation and anomaly point of view,
> at the "end private;" you might imagine you are starting a
> child package, which cannot take advantage of visibility
> on the parent's private part until it hits its own private
> part or body.
A child package starts with a "clean" visibility slate, into which the
parent and other "withs" are loaded. Handling the private part at that point
is (relatively) easy. A "end private" has a complex visibility state,
already existing, where things would have to be subtracted piece-meal.
(Reloading is not an option: you haven't stored it out yet!) This seems
sustantially more complex. (Of course, I haven't tried implementing this, so
it might prove easier than I think now.)
> > * Do public children see all of the visible declarations but none of the
> > private ones of their parent? (Seems necessary.)
>
> They don't see the private declarations until they hit their
> own private part or body. No real surprise here.
>
> > I'm sure there are more issues to consider. I haven't seen any posting
by
> > Steve Baird on this yet. ;-)The whole thing sounds like fun. :-(
>
> Clearly there will be some implementation effects, but I
> don't see many semantic anomalies on the visibility side,
> since I believe a child package experiences much the same
> "coming and going" of visibility.
No, only "coming". There's no "going": once the private part of a parent is
visible, it stays that way. It's the "going", for declarations in the same
unit, which I think doesn't exist currently and has the potential to add
complications.
Now, I realize that other models of child compilations are possible, but for
us, each one starts with a clean slate which is built up with whatever is
needed (a parent is just a hidden "with" of a unit). The only magic is
making the private part visible when the child "private" is reached, and
that is a pure addition to visibility.
> The anomalies I could imagine would have to do with "completion"
> and overriding, since these kinds of things can't be
> simulated by a child package (though a child subprogram
> can "simulate" overriding in certain non-tagged cases).
> And I agree we will need to work them through if we decide
> to pursue this proposal. I am happy to do so, after
> I have finished my other homework ... ;-).
Sounds good, especially the part about finishing your other homework. :-)
This issue and the possible solutions are on the agenda for Washington,
although I'd expect any brainstorming on it to come after we get our other
work done. (You'll note it is right on the bottom of the list... ;-)
BTW, you are welcome to give the same treatment to the write-up of my
proposal for this problem that you'll find in AI05-0074-1 (another AI number
that I fear will live in infamy, like AI-217 [limited with] and AI-359 [the
4(!) previous versions of this problem]). I hope I can take it as well as I
dish it out. ;-)
****************************************************************
From: Tucker Taft
Date: Thursday, October 25, 2007 11:13 PM
> I'm surprised by Tucker's answer. My answer is that "obviously", the
> visibility in the second visible part includes everything that came before,
> just as if "end private;" were erased. (Now Tuck can tell me why that's
> stupid. ;-))
I would think that would be a recipe for privacy "leakage."
If you, for example, declare a subtype of a private
type in a visible part that follows the private part
that defined its full type, is the subtype a partial
view or a full view of the type? E.g.:
type P is private;
private
type P is new Integer;
end private;
subtype S is P;
What are the properties of subtype S? I
really think it needs to still be private.
I think from a *visibility* point of view,
it should be as though the private declarations
were not there at all. The only effect of
the private declarations should be to complete
certain earlier declarations, so they can be used
in contexts where a completely defined
entity is required.
> The purpose of privacy is to hide things from OTHER packages, not from the
> package itself.
If that were true, then why wouldn't the visible part of
a child package have visibility on the private part of
its parent? It comes physically after it, it is inside
the declarative region of the parent, but it doesn't
have visibility on the private declarations until you
reach its own private part. One reason is to avoid
privacy leakage. The visible part of a child package
*can* take advantage of the fact that the private types
of the parent have been completely defined, so they
can be used in contexts where a completely defined
private type is required.
Basically, I think the visibility on the parent from
the visible part of a child is the best model for
the visibility after "end private;".
> ... This should be legal:
>
> package P is
> private
> type Base_Type is range 1..2**31-1;
> end private
> subtype Exported_Type is Base_Type'Base;
> end P;
>
> to get a type that has at least 32 bits, but matches the hardware bounds. I
> don't want to pester the client with a useless name -- Base_Type is visible in
> the visible part, but not in clients.
I can see how that might be useful, but it would just
introduce too many anomalies, I suspect.
> By the way, C++ has a similar feature. It might help to look at the details,
> which I don't remember. Of course C++ visibility is quite different from Ada,
> so it might be irrelevant. And C++ visibility is broken in some ways, so we
> don't necessarily want to mimic it -- but it couldn't hurt to look.
The basic C++ "protection" model is quite different from
Ada's. "Private" declarations are just as "visible" as
"public" declarations, but you get an error if you try
to use them in a place that isn't supposed to have access
to private declarations. So this doesn't really address
the issue, because the question simply becomes whether
you are allowed to "use" (as opposed to "see") the
declarations from a private part in a following visible
part.
The other big difference in C++ is that you don't
have "two part" type declarations in C++, in the
way you do in Ada for private types.
****************************************************************
From: Robert A. Duff
Date: Friday, October 26, 2007 9:01 AM
...
> What are the properties of subtype S? I
> really think it needs to still be private.
You may well be right, but I'd like to understand why. What if S is an integer
type? Would that cause the sky to fall?
Or, what if S is an integer type within this package, but when viewed from
outside, it is private? There's a "+" implicitly declared in the private part;
what if it's visible in the second visible part, but not visible in clients?
...
> > The purpose of privacy is to hide things from OTHER packages, not from the
> > package itself.
>
> If that were true, then why wouldn't the visible part of
> a child package have visibility on the private part of
> its parent?
I must admit, I've never quite understood the high-level rationale for that.
If the so-called "leakage" is explicit in the program text, what's the harm?
I suppose the devil's in the details...
...
> I can see how that might be useful, but it would just
> introduce too many anomalies, I suspect.
Quite likely true. But the opposite approach seems to have some difficulties,
too.
> > By the way, C++ has a similar feature. It might help to look at the details,
> > which I don't remember. Of course C++ visibility is quite different from Ada,
> > so it might be irrelevant. And C++ visibility is broken in some ways, so we
> > don't necessarily want to mimic it -- but it couldn't hurt to look.
>
> The basic C++ "protection" model is quite different from
> Ada's. "Private" declarations are just as "visible" as
> "public" declarations, but you get an error if you try
> to use them in a place that isn't supposed to have access
> to private declarations.
Ah, yes, thanks for the reminder. I (now) agree that the C++ rules are
irrelevant to this discussion.
****************************************************************
From: Tucker Taft
Date: Friday, October 26, 2007 11:56 AM
>> What are the properties of subtype S? I
>> really think it needs to still be private.
>
> You may well be right, but I'd like to understand why. What if S is an integer
> type? Would that cause the sky to fall?
No, but it changes the rules significantly. In many
places in the reference manual, we indicate that a private
part of a language-defined package is "not specified."
It is nice to be able to do so (not just for the language
designers, but for anyone specifying an abstraction
via a package spec). I would hope that this property
of packages remains true in general, namely you can look
just at the visible part(s) of a package and make
sense of it without having to look at the private
part at all. Furthermore, a maintainer can change
the private part of the package and not have to
worry about the semantic effects on clients of
the package.
> Or, what if S is an integer type within this package, but when viewed from
> outside, it is private? There's a "+" implicitly declared in the private part;
> what if it's visible in the second visible part, but not visible in clients?
Again, that means you can't really understand what is going
on in the visible part without studying the private part.
> ...
>>> The purpose of privacy is to hide things from OTHER packages, not from the
>>> package itself.
>> If that were true, then why wouldn't the visible part of
>> a child package have visibility on the private part of
>> its parent?
>
> I must admit, I've never quite understood the high-level rationale for that.
> If the so-called "leakage" is explicit in the program text, what's the harm?
> I suppose the devil's in the details...
I believe the rationale is as above, namely that you
don't need to study the private part of the parent
to understand the visible parts of the child packages.
Interestingly, this point gives a rationale for only
allowing one private part in a package. If you
allow more than one, then to understand what is
going on in the visible parts you need to know
which private types are completed in which private
part. I don't think that is a feature. In these
discussions, I have harborred a personal dislike for the
idea of multiple private parts in a single package,
but I haven't had a good reason other than aesthetic.
But this to me seems like a good reason. If you
write a package spec and leave the private part(s)
unspecified, then you don't want to have to specify
which private part completes what type (or
deferred constant).
So I would go further and say that the
"end private;" proposal should only allow one private
part, that in the "post-private" visible
part you can't introduce any more private types
or deferred constants, and that all declarations
prior to the "end private;" are frozen at the
point of the "end private;", except for incomplete
types.
With this approach, you have no doubt what has
to happen in *the* private part (the completion
of all partial views and deferred constants, plus
any relevant rep-clauses), and you know that
everything is frozen (except incomplete types)
at the "end private;", whether or not there is
a freezing occurrence that happens to appear
in the private part.
Aesthetically, this also feels better. The
private part can be "sandwiched" between a
pre-private and a post-private visible part,
but you don't have to worry about or deal
with private parts "sprinkled" throughout
the package spec at random points.
Finally, the nature of the pre-private and
the post-private visible parts are pretty
different, since the private types
and deferred constants need to precede
the private part, and the instantiations
and incomplete type completions can follow
it.
There can be some advantages to having
a "standard" usage model implied by the
constructs of the language, rather than
having so many different equally good ways
of solving the same problem that no standard style
emerges, and programming styles within
the same language vary dramatically.
One last point. One reason why I like the
notion of a "post-private" visible part
to handle instantiations is that it is
common for instantiations to be followed by
one or more derived type declarations,
bringing some of the types defined by
the instance out into the surrounding
scope. E.g.:
package T_Vectors is
new Containers.Vectors(T, ...);
type T_Vector is new T_Vectors.Container
with null record;
The proposal of an "internally limited" instantiation
that provides internally only a "limited view" where
all exported types are incomplete, wouldn't
support this standard paradigm (since you can't
derive from an incomplete type). That could
be a significant downside of that proposal,
in my view.
The post-private visible part can easily accommodate
this approach. If necessary, an incomplete
type declaration for T_Vector could precede it
in the pre-private visible part (or in the
private part). E.g.:
package P is
type T is private;
type T_Vector is tagged;
type T_Vec_Ptr is access T_Vector;
function Inner_List(X : T) return T_Vec_Ptr;
procedure Set_Inner_List(
X : in out T; List : in T_Vec_Ptr);
...
private
type T is record
Inner_List : T_Vec_Ptr;
...
end record;
... -- rep-clauses for T, deferred constants, etc.
end private;
package T_Vectors is ...
type T_Vector is new T_Vectors.Container
with null record;
end P;
This feels pretty natural to me, with the post-private
part doing a straightforward and useful job.
It is interesting that when you do a "limited with" on
a package you don't see any nested instances, but you
do see derived type declarations. So using this
paradigm of deriving from a type declared in a nested
instance has the useful effect of making the type
available (as an incomplete type) via a "limited with."
The "internally limited" instantiation will always
be completely invisible via a "limited with."
Finally, because there is no "leakage" from the private part,
we could reasonably write:
package Ada.Neat_Standard_Package is
type Container is private;
...
private
-- not specified by the language
end private;
package Useful_Instance is
new Great_Generic(Container, ...)
...
end Ada.Neat_Standard_Package;
in a specification of some future language-defined
package, where we wanted to include some visible
instantiations (such as signatures), and there
would be no danger in saying the private part is
"not specified" since there is no doubt as to what
would have to happen in the private part.
Note also that the post-private part acts somewhat
like the visible part of an "anonymous" child
that you want all clients to "with" and "use."
Semantically I think it can follow many
of the same rules as a child visible part, and
could even be implemented in a somewhat
similar fashion, by starting from an empty
slate, and then "loading up" the visibility
stack with the with and use clauses, the pre-private
visible part, etc. This would avoid having
to remove declarations from the visibility
stack, which I could imagine would be a burden
for some implementations.
****************************************************************
From: Robert A. Duff
Date: Friday, October 26, 2007 1:48 PM
> Interestingly, this point gives a rationale for only
> allowing one private part in a package. If you
> allow more than one, then to understand what is
> going on in the visible parts you need to know
> which private types are completed in which private
> part. I don't think that is a feature.
OK, that makes sense.
>...In these
> discussions, I have harborred a personal dislike for the
> idea of multiple private parts in a single package,
> but I haven't had a good reason other than aesthetic.
> But this to me seems like a good reason.
Agreed. I find vague aesthetics much more convincing when backed up by good
reasons!
Of course, allowing just one _visible_ part would be a further
simplification. ;-)
****************************************************************
From: Tucker Taft
Date: Friday, October 26, 2007 2:21 PM
> Of course, allowing just one _visible_ part would be a further
> simplification. ;-)
Uhhhh, true, but how does that solve our original problem
with instantiations using private types?
****************************************************************
From: Robert A. Duff
Date: Friday, October 26, 2007 2:47 PM
It doesn't. Hence the smiley. I was just attempting to wryly point out that
this idea might not fly at all (and maybe in fact the whole problem can't be
solved reasonably). Sorry for being unclear. Anyway, I guess we have several
years to think about it...
****************************************************************
*** End of discussion of "end private" idea ***
****************************************************************
From: Tucker Taft
Date: Friday, October 19, 2007 11:16 PM
Randy and others indicated that they thought we came
close on allowing for instantiation with private types
by having a special kind of "partial" instantiation
preceding the "full" instantiation.
Here are some recent thoughts I had on this. They may be
redundant or overlapping with our earlier discussions,
but I'm not sure. The basic idea is that we have
a "partial" instantiation, where we instantiate only
the spec of the generic, and have no special pre-freezing
of the actuals. Then we have the "full" instantiation
where the actuals are frozen, and we instantiate
the body. For example:
type Priv is private;
package Inst is new Gen(Priv) with private;
-- "with private" indicates that
-- private types are permitted, and no
-- automatic freezing of actuals occurs.
-- Only the *spec* of the generic Gen
-- is instantiated at this point.
-- (Of course some other syntax is possible.)
private
type Priv is record
X : Inst.Something;
end record;
package Inst is new Gen(Priv);
-- at this point, we instantiate the
-- body of Gen, and do the usual pre-freezing
-- of the actual parameters.
This two-phase instantiation of a generic package is
almost identical to a hand expansion as a nested package
spec, followed by a later hand expansion for the
corresponding body. This means that pretty much anything
you can do with a nested package you can do with a
generic.
An important caveat: this means that you have instantiation-
time checks that may reveal certain aspects of the private
part of the generic. For example, if in the private
part of the generic there is an object declaration
of the formal type, then that would freeze the actual type
at that point. In addition, type circularity checks
could reveal whether the formal type is used directly or
only with a level of indirection in a type declared in
the generic (in the example above, we are assuming that
type "Something" in the generic does not include a
subcomponent of the formal type).
These instantiation-time checks don't seem significantly
worse than the ones we already have (every place where
we say a given rule "applies also in the private part
of an instance of a generic unit."). We have somewhat
gotten used to the fact that the private part of a generic
forms part of its contract.
The main thing I like about this approach is that it
really doesn't involve a bunch of special rules, such
as making types automatically incomplete, etc. It
just separates the instantiation of the spec from the
body, and associates the "pre-freezing" of actuals with
the instantiation of the body.
I am almost certainly missing some subtlety, but at
the moment it feels pretty straightforward.
****************************************************************
From: Tucker Taft
Date: Friday, October 19, 2007 11:41 PM
I should add, that if there are nested instantiations
inside the generic, then during the "partial"
instantiation, *probably* only spec instantiations should
take place, with the body instantiations being
delayed until the enclosing generic's body is
instantiated. I knew I'd find at least one
subtlety...
****************************************************************
From: Randy Brukardt
Date: Friday, October 19, 2007 11:58 PM
> Randy and others indicated that they thought we came
> close on allowing for instantiation with private types
> by having a special kind of "partial" instantiation
> preceding the "full" instantiation.
>
> Here are some recent thoughts I had on this. They may be
> redundant or overlapping with our earlier discussions,
> but I'm not sure.
I recommend going back and reading the minutes on the various versions of
AI-359 in order to see the problems.
> The basic idea is that we have
> a "partial" instantiation, where we instantiate only
> the spec of the generic, and have no special pre-freezing
> of the actuals. Then we have the "full" instantiation
> where the actuals are frozen, and we instantiate
> the body.
...
> The main thing I like about this approach is that it
> really doesn't involve a bunch of special rules, such
> as making types automatically incomplete, etc. It
> just separates the instantiation of the spec from the
> body, and associates the "pre-freezing" of actuals with
> the instantiation of the body.
>
> I am almost certainly missing some subtlety, but at
> the moment it feels pretty straightforward.
I think that there were problems with elaboration of the package
specification. You can't actually do anything that would depend on the
private type (because you don't yet know its declaration), so what you could
put in the specification would be quite limited.
For Janus/Ada, we generate thunks at the point of the instantiation which
are called by the elaboration. Those obviously assume that the full type
definition is available. (We share the elaboration part of the specification
in the same way that we share the body; if we were able to do real template
expansion, we probably would have used that everywhere...)
The privacy breaking is uncomfortable, mainly because it could easily mean
that generics like the containers may not be usable in specifications
depending on their implementation. That suggests that we'd have to add a
boatload of restrictions on how the private part is implemented (not
allowing instantiation of the containers with a private type would be a
non-starter, given that it is one of the problems that we need to fix). And
I have to wonder what it would do to the IBM implementation.
The solution still seems to me to be a limited instantiation that exports a
limited view of the generic within the specification (but exports the full
view of the generic to clients of the package). The problem we had in
Atlanta was trying to export too much to the package; that led to all kinds
of anomolies. Exporting nothing would work great, but wouldn't solve Bob's
recursion problem. My main concern is that I never came up with a syntax
that really explained what is happening. Maybe something like:
package Inst is new Gen(Priv) for export;
which makes it clear that Inst is only for export and for only limited local
use.
****************************************************************
From: Randy Brukardt
Date: Saturday, October 20, 2007 12:00 AM
> I should add, that if there are nested instantiations
> inside the generic, then during the "partial"
> instantiation, *probably* only spec instantiations should
> take place, with the body instantiations being
> delayed until the enclosing generic's body is
> instantiated. I knew I'd find at least one
> subtlety...
Mr. Code sharing is about to throw up. :-)
****************************************************************
From: Randy Brukardt
Date: Saturday, October 20, 2007 12:07 AM
...
> My main concern is that I never came up with a
> syntax that really explained what is happening. Maybe something like:
>
> package Inst is new Gen(Priv) for export;
>
> which makes it clear that Inst is only for export and for only
> limited local use.
Indeed, this suggests that the partial instantiation is not visible at all
inside the package specification (until it is completed, of course).
If we want a limited view, too, we could add syntax for that:
package Inst is new Gen(Priv) for export with limited;
There can be no anomolies with the first form, because it has no semantic
effect on the package: it simply makes a declaration in the private part
visible from the outside of the package.
The second form adds a limited view so that recursive data types can be
created. I'd expect that to work roughly the same as regular limited view,
so I think we've already covered the anomolies. (Famous last words... ;-) I
realize this construct is a bit more limiting for Bob than some of the other
proposals, but at least with it you can write the sort of things he wants to
(as opposed to the current situation where it is nearly impossible).
****************************************************************
From: Tucker Taft
Date: Saturday, October 20, 2007 10:58 AM
> I think that there were problems with elaboration of the package
> specification. You can't actually do anything that would depend on the
> private type (because you don't yet know its declaration), so what you could
> put in the specification would be quite limited.
Can you elaborate on this? When you say "you can't actually do
anything" are you talking about the compiler or the
programmer? How is this different from a nested package at
the point of the partial instantiation, that refers to a
private type from the enclosing package? It is true that in
both cases, you can't lay out any types declared in the nested
package that depend on the outer private types, but there is
no requirement to do so until you hit a freezing point. By
postponing the instantiation of the body, we hopefully postpone
the freezing points.
> For Janus/Ada, we generate thunks at the point of the instantiation which
> are called by the elaboration. Those obviously assume that the full type
> definition is available. (We share the elaboration part of the specification
> in the same way that we share the body; if we were able to do real template
> expansion, we probably would have used that everywhere...)
I think you clearly would need to postpone generating some of the thunks,
and separate the elaboration into two routines, one for the spec
which would use only the thunks it really needed for the elaboration
of the spec, and one for the body, which would use the remaining thunks.
>
> The privacy breaking is uncomfortable, mainly because it could easily mean
> that generics like the containers may not be usable in specifications
> depending on their implementation. That suggests that we'd have to add a
> boatload of restrictions on how the private part is implemented (not
> allowing instantiation of the containers with a private type would be a
> non-starter, given that it is one of the problems that we need to fix). And
> I have to wonder what it would do to the IBM implementation.
I agree we might have to specify additional implementation requirements.
The most important would be that the generic must support partial
instantiation.
Secondarily, we would have to decide whether we want
to require that certain exported types not include the formal
types as subcomponents. My sense would be that we probably *don't*
want to impose this latter requirement on the "definite" versions,
so that "bounded" versions can be created that use no levels of
indirection. On the other hand, imposing this requirement on
the "indefinite" versions would seem reasonable, and actually
makes the "indefinite" versions that much more flexible.
I suppose this brings up another possibility, where we only
defer freezing an actual type if the corresponding formal type
has unknown discriminants. Since we know that a formal type
with unknown discriminants could *never* be a subcomponent of
an exported "definite" type, we eliminate the privacy breaking.
That has a nice ring to it...
I can even imagine we consider eliminating the need for two-part
instantiations, and make them "implicit" when there is a formal
type with unknown discriminants. That is, if a formal type
has unknown discriminants, then the freezing of the corresponding
actual type, and the body instantiation, is postponed
until the next "general" freezing point. That has some
upward compatibility issues, of course, but they might be
acceptable in trade for avoiding explicit two-part
instantiations.
****************************************************************
From: Tucker Taft
Date: Saturday, October 20, 2007 11:01 AM
Once you accept having two separate elaboration routines,
one for the spec, and one for the body, I don't see this
makes sharing more difficult. Furthermore, if we
limit this capability to generics that have at least
one formal type having unknown discriminants (which is
related to what I suggested in my most recent response),
this reduces the distributed overhead.
****************************************************************
From: Robert I. Eachus
Date: Saturday, October 20, 2007 10:49 PM
>If we want a limited view, too, we could add syntax for that:
>
> package Inst is new Gen(Priv) for export with limited;
>
>There can be no anomolies with the first form, because it has no semantic
>effect on the package: it simply makes a declaration in the private part
>visible from the outside of the package.
>
>The second form adds a limited view so that recursive data types can be
>created. I'd expect that to work roughly the same as regular limited view,
>so I think we've already covered the anomolies. (Famous last words... ;-) I
>realize this construct is a bit more limiting for Bob than some of the other
>proposals, but at least with it you can write the sort of things he wants to
>(as opposed to the current situation where it is nearly impossible).
With my Norm Cohen Halloween costume on, and tongue very firmly in
cheek, might I suggest:
package Inst is not private new Gen(Priv) with limited;
or if you prefer:
not private package Inst is new Gen(Priv) with limited;
What about making the limited view the default? Then you could say:
not private package Inst is not limited new Gen(Priv);
Am I being silly here? A bit. But the first case seems to map almost
directly to the multiple private part model. As I see it, the problems
there come from exporting types and declarations in the private part, by
a renaming, derived type, or subtype in the generic package
specification. In one sense we are trying to poke holes in the privacy
screen, so it seems unfair to talk about unintended holes that a
programmer can avoid. On the other hand, the Ada philosophy is to make
such things explicit.
So maybe we are looking in the wrong place. We know what we want: to be
able to export generic instamces which are not child packages but can
see into the private part. If we flag the generic for export in some
way, it may be that we want to flag any otherwise private types,
subprograms, or whatever that such a generic can make visible outside
the private part.
Taking off the Norm Cohen costume, something like:
package Foo is
type Bar is private;
...
private
export type Bar is....;
export type FooBar is Bar with...;
type Foob is ...;
export package FuBar is new ...;
-- can have Bar or FooBar as generic formals, but not Foob;
end Foo;
Seems to me we avoid most of the potential issues this way. Yes, you
can use this mechanism to break privateness. But then again, unless
someone is holding a gun, or the software equivalent, to the
programmer's head, there is no requirement to make any particular type
private, or to even have any private types at all.
For the package FuBar, the export keyword (or whatever syntax is chosen)
has semantic consequences: The visibility of the unit is increased.
For non-generic objects, types, or subprograms, all the notation does is
flag that they can be used in such quasi-public instantiations. Or you
could take the view that it also expands the visibility: into the
generic_actual_part of an exported generic instantiation.
****************************************************************
From: Randy Brukardt
Date: Sunday, October 21, 2007 10:12 PM
Tuck, we already have two elaboration routines. That's not the point; the
point is that you can't elaborate the body at the point of the instance;
you'd have to define some later point that occurred implicitly. But that
idea was roundly disliked last time, and I can't imagine what's changed
about that.
Essentially, you are rehashing all of the ideas that we previously
discarded. They didn't work two years ago and I don't know of anything
that's changed since. For instance, there was a strong dislike for "implicit
elaboration" at some later point, because a small change in a generic spec
or in an instantiation can cause the semantics to change (and possibly
break).
Can we possibly go back to where we left off (which is what I was trying do)
and not go through all of these discarded ideas??
****************************************************************
From: Randy Brukardt
Date: Sunday, October 21, 2007 10:22 PM
> > I think that there were problems with elaboration of the package
> > specification. You can't actually do anything that would depend on the
> > private type (because you don't yet know its declaration), so what you
could
> > put in the specification would be quite limited.
>
> Can you elaborate on this? When you say "you can't actually do
> anything" are you talking about the compiler or the
> programmer?
The programmer, mainly. There are a lot of restrictions on the usage of
private types before the full definition, and none of those can be allowed
in a generic that will be used in your scheme.
> How is this different from a nested package at
> the point of the partial instantiation, that refers to a
> private type from the enclosing package?
When you write a nested package, you do that while you design the rest of
the package, and you can avoid (well, you have to avoid) doing anything that
would be illegal with the private type.
But with a generic, you are talking about a package that was designed
separately, and may very well already exist. I don't think people are going
to want to redesign all of their generics to work in this context.
> It is true that in
> both cases, you can't lay out any types declared in the nested
> package that depend on the outer private types, but there is
> no requirement to do so until you hit a freezing point. By
> postponing the instantiation of the body, we hopefully postpone
> the freezing points.
Piece-meal freezing of generic units was rejected last time -- too much of
an implementation earthquake.
> > For Janus/Ada, we generate thunks at the point of the instantiation which
> > are called by the elaboration. Those obviously assume that the full type
> > definition is available. (We share the elaboration part of the specification
> > in the same way that we share the body; if we were able to do real template
> > expansion, we probably would have used that everywhere...)
>
> I think you clearly would need to postpone generating some of the thunks,
> and separate the elaboration into two routines, one for the spec
> which would use only the thunks it really needed for the elaboration
> of the spec, and one for the body, which would use the remaining thunks.
There is only one generic descriptor, and I don't see any reason for more.
The problem really is that there would be no way to prevent "early" calls to
thunks that don't exist, so there would be many impossible-to-find bugs to
deal with. (Our generics already have that problem, and I surely don't want
to make it worse.)
Remember that you can't make any calls (including those for thunks) until
you've elaborated the generic. It's hard to imagine what you could do
between the partial instantiation and the full one. (Yes, nested packages
have this problem, too, Program_Error is raised by doing almost anything.
That makes them not very useful.)
> > The privacy breaking is uncomfortable, mainly because it could easily mean
> > that generics like the containers may not be usable in specifications
> > depending on their implementation. That suggests that we'd have to add a
> > boatload of restrictions on how the private part is implemented (not
> > allowing instantiation of the containers with a private type would be a
> > non-starter, given that it is one of the problems that we need to fix). And
> > I have to wonder what it would do to the IBM implementation.
>
> I agree we might have to specify additional implementation requirements.
> The most important would be that the generic must support partial
> instantiation.
>
> Secondarily, we would have to decide whether we want
> to require that certain exported types not include the formal
> types as subcomponents. My sense would be that we probably *don't*
> want to impose this latter requirement on the "definite" versions,
> so that "bounded" versions can be created that use no levels of
> indirection. On the other hand, imposing this requirement on
> the "indefinite" versions would seem reasonable, and actually
> makes the "indefinite" versions that much more flexible.
The scheme I was proposing needs none of this. The only thing that changes
is that we have a new kind of partial instantiation.
> I suppose this brings up another possibility, where we only
> defer freezing an actual type if the corresponding formal type
> has unknown discriminants. Since we know that a formal type
> with unknown discriminants could *never* be a subcomponent of
> an exported "definite" type, we eliminate the privacy breaking.
> That has a nice ring to it...
>
> I can even imagine we consider eliminating the need for two-part
> instantiations, and make them "implicit" when there is a formal
> type with unknown discriminants. That is, if a formal type
> has unknown discriminants, then the freezing of the corresponding
> actual type, and the body instantiation, is postponed
> until the next "general" freezing point. That has some
> upward compatibility issues, of course, but they might be
> acceptable in trade for avoiding explicit two-part
> instantiations.
The implicit idea was roundly rejected in the past. Let's not keep rehashing
the old stuff.
****************************************************************
From: Tucker Taft
Date: Sunday, October 21, 2007 10:26 PM
The proposal I have been talking about
presumes there is a partial instantiation
(I suggested the "with private" syntax),
followed by a "full" instantiation.
The spec elaboration(s) happen at the
point of the partial instantiation,
and the body elaboration(s) happen at the
point of the full instantiation.
Sorry if that wasn't clear.
****************************************************************
From: Randy Brukardt
Date: Sunday, October 21, 2007 10:41 PM
It was clear, but then you started talking about implicit instantiations and
having different behavior for different kinds of formals. That certainly was
discredited before.
My recollection is that we talked about a model similar to the one you are
proposing now and rejected it for some reason. But I don't recall the
reason, and I'm too far behind on my work after the disk failure to spend
time looking it up. (So I may just be spreading FUD, I hope not.)
****************************************************************
From: Tucker Taft
Date: Sunday, October 21, 2007 11:03 PM
Sorry if it feels like I am dragging you through
the past. Sometimes new ideas turn up when you
visit old ground again. Of the various
musings I have uttered, the one that I think that
*is* new and might be worth further investigation
is as follows:
1) Allow "partial" instantiations *only* if
at least one of the formal types is
a formal type with unknown discriminants.
2) At the partial instantiation, freeze all
actuals *except* the actuals corresponding
to formals with unknown discriminants.
Instantiate the spec, and only partially
instantiate nested instantiations of
generics with formals with unknown discrims.
3) At the full instantiation, freeze the actuals
corresponding to formals with unknown discrims,
then instantiate the bodies of any nested
instantiations only partially instantiated
thus far, and then instantiate the body of
the "main" generic.
This has the advantage that implementations need
support partial instantiations only for a subset
of generics, presuming there might be a higher distributed
overhead to support partial instantiations on all
generics. It also has the advantage that all actual
types in a generic instance may still be presumed
to be "pre-frozen," *unless* the formal has unknown
discriminants. Furthermore, because formals with
unknown discriminants cannot be used as components
of other types, it is safe to use any type exported
by the generic instance in the full definition of
a private type passed as the actual, since it can't
create a circularity.
****************************************************************
From: Robert A. Duff
Date: Monday, October 22, 2007 8:25 AM
>... Furthermore, because formals with
> unknown discriminants cannot be used as components
> of other types, it is safe to use any type exported
> by the generic instance in the full definition of
> a private type passed as the actual, since it can't
> create a circularity.
Well, that seems to solve the problem, but it has some flaws.
I'm not sure they're fatal flaws.
It means I can create a Thing record containing a Vector of pointers to Things,
either by passing a pointer to Vectors or by passing Thing to
Indefinite_Vectors. But I can't create a Thing containing a pointer to a
Vector of Things. It's bad enough that we force the programmer to introduce a
level of indirection in order to have recursive types -- now we're telling them
which place has to have the indirection.
It's overkill -- you're forced to use a level of indirection even when there is
no cycle, or else do the old-fashioned thing (make the instantiation a child).
The usual: you can't do X because if you also did Y, then there would be
trouble -- it's pretty annoying if you don't want to do Y.
Maybe it's good enough. OTOH, I suppose we have several years to search for
better solutions. ;-)
****************************************************************
From: Tucker Taft
Date: Monday, October 22, 2007 9:00 AM
If you are willing to insert the level of indirection
explicitly using an access type, then you can create
a thing that contains a pointer to a vector
of things using an incomplete type:
type Vector_Of_Things;
type Thing is record
Vec : access Vector_Of_Things;
end record;
package Thing_Vectors is new Vectors(Thing, ...);
type Vector_Of_Things is new Thing_Vectors.Vector
with null record;
Is that adequate?
****************************************************************
From: Robert A. Duff
Date: Monday, October 22, 2007 9:40 AM
I'm confused. I thought we were talking about the case where Thing is private,
and Thing_Vectors is exported from the package. So "new Vectors(Thing)" is
illegal, since the formal does not have unknown discrims.
****************************************************************
From: Tucker Taft
Date: Monday, October 22, 2007 10:19 AM
Clearly the above can be in the private part, with a visible
partial view of "Thing" declared in the visible part.
But you are right that you can't also export Thing_Vectors
in the above, except via the hypothetical "end private;"
construct, which would immediately precede the instantiation.
So you are right that the partial instantiation approach
allows for indirection buried in the generic, whereas
the "end private;" allows for indirection in the user's private
type. You need both to allow for both.
One could argue that it wouldn't be all bad for the language
to make a choice, favoring one over the other. Or one
could argue that the language should support both equally.
Right now it supports neither, and if you want to create
a mutually recursive combination approximating type and
vector-of-type, then you are limited to type and vector
of ptr-to-type.
Randy is a big fan for burying access types inside abstractions,
and the partial instantiation idea seems to be the only
one so far that allows all uses of access types to be buried
inside the container abstractions.
The "end private;" seems more general, but it is roughly
equivalent to using a child. The partial instantiation
provides a capability that cannot be reproduced using a child.
Seems like some tough choices.
****************************************************************
From: Randy Brukardt
Date: Monday, October 22, 2007 6:56 PM
> Sorry if it feels like I am dragging you through
> the past. Sometimes new ideas turn up when you
> visit old ground again. Of the various
> musings I have uttered, the one that I think that
> *is* new and might be worth further investigation
> is as follows:
>
> 1) Allow "partial" instantiations *only* if
> at least one of the formal types is
> a formal type with unknown discriminants.
Yes, this is new.
> 2) At the partial instantiation, freeze all
> actuals *except* the actuals corresponding
> to formals with unknown discriminants.
> Instantiate the spec, and only partially
> instantiate nested instantiations of
> generics with formals with unknown discrims.
But this is a variation of ideas that we could not get to work in the past.
The problem was two-fold: implementation earthquakes for various models, and
privacy violations. It's actually three-fold for this one, because partially
instantiating nested instantiations means that where the body of those
nested instantiations are elaborated would differ depending on how the
instantiation is written. That seems bad: it's not uncommon to use an
instantiation to get a body elaborated in a spec (Ada provides no other way
to do that). So, given a generic like:
generic
type Foo (<>) is ...
package Bar is
package Nested is new <some generic> (Foo);
subtype Bounds is Integer range 1 .. Nested.Func;
end Bar;
A partial instantiation of Bar would raise Program_Error on the call of
Nested.Func (because the body is not elaborated) but would work correctly
for a normal instantiation. That's unpleasant.
> 3) At the full instantiation, freeze the actuals
> corresponding to formals with unknown discrims,
> then instantiate the bodies of any nested
> instantiations only partially instantiated
> thus far, and then instantiate the body of
> the "main" generic.
>
> This has the advantage that implementations need
> support partial instantiations only for a subset
> of generics, presuming there might be a higher distributed
> overhead to support partial instantiations on all
> generics.
I don't think that this would be very useful in simplifying the
implementation, but of course I can't say how it would work for other
implementers.
> It also has the advantage that all actual
> types in a generic instance may still be presumed
> to be "pre-frozen," *unless* the formal has unknown
> discriminants. Furthermore, because formals with
> unknown discriminants cannot be used as components
> of other types, it is safe to use any type exported
> by the generic instance in the full definition of
> a private type passed as the actual, since it can't
> create a circularity.
But the real problem is that you haven't addressed the anomalies that Steve
and Erhard pointed out the last time we discussed this (and that effectively
killed it). The problem was that a lot of partially defined stuff
potentially gets exported from a partial view, and this goes beyond the
sorts of things that we currently have to deal with.
My personal opinion is that the "partial instantiation" (versus a "limited
instantiation") is never going to work. I've been trying to make the
"limited instantiation" more acceptable, but I haven't gotten any real
feedback from you.
I realize that some people don't like the idea for some reason; what I'd
like to find out is what about the proposal is disliked so that it can be
enhanced. (I'm convinced that it would be best to start with a mechanism
that works and would not clobber implementers, and then see if we can safely
extend it to be more powerful.)
---
To summarize the latest idea again:
exported_generic_instantiation ::=
generic_instantiation [for export [with limited view]];
An exported_generic_instantiation shall be given in the visible part of a
package specification. An exported_generic_instantiation has to have a
completion in the private part of the same package specification. The
completion has to conform [I believe we have worked out rules for that in
the past].
The elaboration of an exported_generic_instantiation has no effect.
For a reference to the reference to the defining_program_unit_name of an
exported_generic_instantiation:
* If the reference is within the package specification that contains the
exported_generic_instantiation (and before the completing
generic_instantiation):
If the reserved words "with limited" do not appear, then the reference
is illegal;
If the reserved words "with limited" do appear, then the reference
denotes the limited view of the package created by expanding the generic
unit [Unfortunately, we can't just say the "limited view of the
instantiation", because that is empty. Better wording will be needed].
* Otherwise, the reference denotes the completing generic_instantiation.
(The fact that that instantiation is in the private part is ignored.)
---
This formulation has no elaboration or freezing issues, which were the major
stumbling blocks in the past attempts. (Uses outside the package are
allowed, because all of the elaboration and freezing has to already have
happened.) The only flaw that I can see is that it isn't as intuitive as it
could be, and I blame that mostly on the syntax and terminology. Perhaps
there is a way to make it more intuitive without bringing in all of the
problem areas.
Here's an example:
package Something is
type Priv is tagged private;
package Priv_Lists is new Ada.Containers.Doubly_Linked_Lists (Priv)
for export;
... -- Use of Priv_Lists here is illegal.
private
type Priv is ...
package Priv_Lists is new Ada.Containers.Doubly_Linked_Lists (Priv);
end Something;
with Something;
procedure Do_It is
PList : Something.Priv_Lists.Container; -- This is allowed.
begin
...
end Do_It;
And an example for Bob:
package Something_Else is
type Node is tagged private;
package Node_Lists is new
Ada.Containers.Doubly_Linked_Lists (Node) for export with
limited view;
...
private
type Node is tagged record
Parent : access Node;
Children : access Node_Lists.Container; -- "Recursive" definition.
Siblings : access Node_Lists.Container;
...
end record;
package Node_Lists is new Ada.Containers.Doubly_Linked_Lists (Node);
end Something_Else;
****************************************************************
From: Randy Brukardt
Date: Monday, October 22, 2007 7:09 PM
...
> It means I can create a Thing record containing a Vector of pointers to Things,
> either by passing a pointer to Vectors or by passing Thing to
> Indefinite_Vectors. But I can't create a Thing containing a pointer to a
> Vector of Things. It's bad enough that we force the programmer to introduce a
> level of indirection in order to have recursive types -- now we're telling them
> which place has to have the indirection.
Careful here: by wanting too much the last time, you caused us to look for
other solutions; and the net effect is that you got none. (That may have
been because of a mis-interpretation of your comments, but it surely
happened.)
I think the most important thing is that you can write a private type that
uses the containers recursively. You can't currently do that in any way at
all in Ada, and that is clearly bad. But the most important thing is that
there is a way; obsessing over having the perfect way probably will lead
back to where we are now. (The only way that won't happen is if several
implementers drop out of the ARG, removing their objections to proposals
that have split freezing or elaboration. But then there probably won't be a
need for future Ada standards...)
Of course, we should try to make this as usable as possible, but we have to
be careful to avoid making "best" the enemy of "better" to the point where
we end up with "not-at-all".
****************************************************************
From: Randy Brukardt
Date: Monday, October 22, 2007 7:17 PM
> I'm confused. I thought we were talking about the case where Thing is private,
> and Thing_Vectors is exported from the package. So "new Vectors(Thing)" is
> illegal, since the formal does not have unknown discrims.
Humm, I think my current proposal solves this problem:
package Bob is
type Thing is private;
package Thing_Vectors is new Vectors(Thing, ...) for export with limited view;
private
type Thing is record
Vec : access Thing_Vectors.Vector;
end record;
end Bob;
There are only two problems worth solving here:
(1) The "Duff" problem of using a container of a type within the type itself;
(2) The "Signature" problem of exporting a generic instantiation of private types.
My proposal has solved both of these, and introduces no new dynamic
semantics [which is where the problem was]. (Other than possibly a
conformance check.) The implementation of the visibility changes appear to
be minor (pointing the exported instance at the full one for external usage
seems trivial - of course, you can't reason about other people's compilers).
What's not to like?? ;-)
****************************************************************
From: Robert A. Duff
Date: Monday, October 22, 2007 8:00 PM
> Careful here: by wanting too much the last time, you caused us to look for
> other solutions; and the net effect is that you got none. ...
Understood. There seems to be no perfect solution. So let's be honest about
the drawbacks of each, but not let those drawbacks paralyze us.
****************************************************************
From: Tucker Taft
Date: Monday, October 22, 2007 8:53 PM
> But this is a variation of ideas that we could not get to work in the past.
> The problem was two-fold: implementation earthquakes for various models, and
> privacy violations. It's actually three-fold for this one, because partially
> instantiating nested instantiations means that where the body of those
> nested instantiations are elaborated would differ depending on how the
> instantiation is written. That seems bad: it's not uncommon to use an
> instantiation to get a body elaborated in a spec (Ada provides no other way
> to do that). So, given a generic like:
>
> generic
> type Foo (<>) is ...
> package Bar is
> package Nested is new <some generic> (Foo);
> subtype Bounds is Integer range 1 .. Nested.Func;
> end Bar;
>
> A partial instantiation of Bar would raise Program_Error on the call of
> Nested.Func (because the body is not elaborated) but would work correctly
> for a normal instantiation. That's unpleasant.
If we were to take this approach, then the implementor
of a generic that had a formal type with unknown
discriminants would have to decide whether or not
to support partial instantiations. If they intended
to support a partial instantiation, then they would
clearly have to test it with one. I think formals
with unknown discriminants are rare enough that
that wouldn't be an undue burden.
In general, nested instantiations are pretty
rare, and nested instantiations that pass
a formal type with unknown discriminants are
even rarer, and nested instantiations where
one takes advantage of immediate elaboration
of the body to make a call are even rarer still.
If this very rare generic turns out to be
something where it would be useful to support
instantiations with a private type, then having
to redesign it a bit for that purpose seems reasonable.
>> 3) At the full instantiation, freeze the actuals
>> corresponding to formals with unknown discrims,
>> then instantiate the bodies of any nested
>> instantiations only partially instantiated
>> thus far, and then instantiate the body of
>> the "main" generic.
>>
> ...
>> It also has the advantage that all actual
>> types in a generic instance may still be presumed
>> to be "pre-frozen," *unless* the formal has unknown
>> discriminants. Furthermore, because formals with
>> unknown discriminants cannot be used as components
>> of other types, it is safe to use any type exported
>> by the generic instance in the full definition of
>> a private type passed as the actual, since it can't
>> create a circularity.
>
> But the real problem is that you haven't addressed the anomalies that Steve
> and Erhard pointed out the last time we discussed this (and that effectively
> killed it). The problem was that a lot of partially defined stuff
> potentially gets exported from a partial view, and this goes beyond the
> sorts of things that we currently have to deal with.
Can you elaborate on this? I don't think anything is partially
defined. Some bodies aren't elaborated, but that is no
surprise in a package spec. Most bodies aren't elaborated
in a package spec.
Despite surface similarities, I think the problems Erhard and Steve were
talking about don't apply to this proposal. In the earlier
proposal, we were trying to prevent the partial instantiation
from freezing *anything*. In the proposal we are discussing
here, the only thing that is special is that the actual
types that are associated with formal types with unknown
discriminants are not "pre-frozen." But if they get used
in a spec in a way that would normally require freezing,
then they get frozen. The spec is expanded with completely
"normal" semantics. What gets postponed is the instantiation
(and elaboration) of the body, and the freezing of certain
actuals.
If I am wrong about this, it would be helpful if you could
identify which variant of AI-359 you think this most recent
proposal matches, and which ARG meeting Erhard and Steve
found the flaws. I just looked at a bunch of minutes, and
they all seemed to be concerned with proposals where we
were trying to defer all freezing, or make everything into
a partial view, or defer all elaboration, rather than
just the elaboration of the body. September 2004 in Madison
seemed to be the one you were probably remembering.
Unless I am mistaken, we never discussed
a proposal where the partial instantiation
did all the usual things associated with the instantiation
of a spec, creating full types, etc., with the only change
being that we eliminated the pre-freezing of a subset of
the actuals, and deferred the instantiation of the body.
> My personal opinion is that the "partial instantiation" (versus a "limited
> instantiation") is never going to work. I've been trying to make the
> "limited instantiation" more acceptable, but I haven't gotten any real
> feedback from you.
>
> I realize that some people don't like the idea for some reason; what I'd
> like to find out is what about the proposal is disliked so that it can be
> enhanced. (I'm convinced that it would be best to start with a mechanism
> that works and would not clobber implementers, and then see if we can safely
> extend it to be more powerful.)
I felt that it was useful to try to come up with a proposal
that did *not* require the explicit use of access types.
Your proposal is providing only incomplete types, since it is
providing only a limited view of the instance from within
the package. Hence access types must be used in any reference
to types declared in the package. That is annoying if the
generic already introduces a level of indirection, and
has a bunch of code to ensure leak-free storage management.
The client can't take advantage of that when only given
incomplete types.
If all that we get are incomplete types, then I guess
I prefer the generality of the "end private;" over the
unique and somewhat counter-intuitive semantics of
the "limited internal view" approach.
As far as a specific comment on the limited internal
view idea, syntactically, I would stick with something simple
like "package I is limited new ..." for now, always
provide the limited view internally, and save the
Norm-Cohen-string-of-reserved-words for late nights
at the bar after ARG meetings. ;-)
****************************************************************
From: Randy Brukardt
Date: Monday, October 22, 2007 9:46 PM
...
> > But the real problem is that you haven't addressed the anomalies that Steve
> > and Erhard pointed out the last time we discussed this (and that effectively
> > killed it). The problem was that a lot of partially defined stuff
> > potentially gets exported from a partial view, and this goes beyond the
> > sorts of things that we currently have to deal with.
>
> Can you elaborate on this? I don't think anything is partially
> defined. Some bodies aren't elaborated, but that is no
> surprise in a package spec. Most bodies aren't elaborated
> in a package spec.
Please read the Atlanta minutes.
> Despite surface similarities, I think the problems Erhard and Steve were
> talking about don't apply to this proposal. In the earlier
> proposal, we were trying to prevent the partial instantiation
> from freezing *anything*. In the proposal we are discussing
> here, the only thing that is special is that the actual
> types that are associated with formal types with unknown
> discriminants are not "pre-frozen." But if they get used
> in a spec in a way that would normally require freezing,
> then they get frozen. The spec is expanded with completely
> "normal" semantics. What gets postponed is the instantiation
> (and elaboration) of the body, and the freezing of certain
> actuals.
Quoting from the Atlanta minutes:
"Tucker has changed the freezing rules to require freezing at the end of an
instance and then have piecemeal instantiation. This is necessary so a type
can contain a component of its own type. Pascal objects that this is a
change that we had previously agreed to not make. Randy agrees; he notes
that it is privacy breaking. Pascal says that piecemeal freezing is a dead
boy issue. He continues that the important issue is to solve the export of
containers from specs of private types. Tucker notes also that the signature
package problem is also covered even without piecemeal freezing. Randy notes
that we could still allow this in 2015 if it actually proves to be
important."
And now you are reintroducing this again - some ideas just never die.
> If I am wrong about this, it would be helpful if you could
> identify which variant of AI-359 you think this most recent
> proposal matches, and which ARG meeting Erhard and Steve
> found the flaws. I just looked at a bunch of minutes, and
> they all seemed to be concerned with proposals where we
> were trying to defer all freezing, or make everything into
> a partial view, or defer all elaboration, rather than
> just the elaboration of the body. September 2004 in Madison
> seemed to be the one you were probably remembering.
Then we started having problems with things exported from the instantiation.
The solution (as I see it as least) is to not export so much. You seem to
see the solution as to defer freezing and break privacy.
> Unless I am mistaken, we never discussed
> a proposal where the partial instantiation
> did all the usual things associated with the instantiation
> of a spec, creating full types, etc., with the only change
> being that we eliminated the pre-freezing of a subset of
> the actuals, and deferred the instantiation of the body.
No we didn't. But you again are talking about partial freezing and privacy
breaking, and that was a non-starter in the past. We need proposals that
don't have those properties, not more of the same.
Moreover, I'm not very interested in a proposal that will only work with a
few "special" generics. The standard definite (and presumably bounded)
containers will not work with this proposal. So some users (safety-critical,
for instance) seem to be out of luck. Similarly, the majority of existing
generics don't have formals with unknown discriminants; you want everyone to
rewrite their generics. Yuck.
> > My personal opinion is that the "partial instantiation" (versus a "limited
> > instantiation") is never going to work. I've been trying to make the
> > "limited instantiation" more acceptable, but I haven't gotten any real
> > feedback from you.
> >
> > I realize that some people don't like the idea for some reason; what I'd
> > like to find out is what about the proposal is disliked so that it can be
> > enhanced. (I'm convinced that it would be best to start with a mechanism
> > that works and would not clobber implementers, and then see if we can safely
> > extend it to be more powerful.)
>
> I felt that it was useful to try to come up with a proposal
> that did *not* require the explicit use of access types.
>
> Your proposal is providing only incomplete types, since it is
> providing only a limited view of the instance from within
> the package. Hence access types must be used in any reference
> to types declared in the package. That is annoying if the
> generic already introduces a level of indirection, and
> has a bunch of code to ensure leak-free storage management.
> The client can't take advantage of that when only given
> incomplete types.
The client *shouldn't* be able to take advantage of the private
implementation of a generic unit: that's privacy breaking. One could argue
that it would be nice to take advantage of a visible use of indirection --
but that's pretty unlikely in a well-designed generic. It doesn't seem worth
a lot of implementation pain to provide that capability.
To really allow this, I think it would have to be declared in the contract
of the generic somehow. (No, unknown discriminants does not do that, as
there would usually not be any objects at all. And they're too limiting in
any case.) And clearly it would have to be enforced end-to-end.
> If all that we get are incomplete types, then I guess
> I prefer the generality of the "end private;" over the
> unique and somewhat counter-intuitive semantics of
> the "limited internal view" approach.
The problem is that is way too general, and certainly will introduce new
anomalies. (I can only imagine what the effect on visibility in public
children would be.) Moreover, the 0 or 1 or infinite rule implies that we
would need to add an unlimited number of these new parts -- and visibility
changes are always an implementation earthquake. It also scatters the
visible declarations all over, and forces them into a particular order,
which may not be logical (forcing the instances very late, for example).
It would make just as much sense to follow Robert Eachus' suggestion and
allow individual declarations to be made visible or hidden from clients
(making the private part an obsolescent feature). Then we could be sure that
the earthquake would be complete but also that we wouldn't be forcing any
ugly constructs on people.
> As far as a specific comment on the limited internal
> view idea, syntactically, I would stick with something simple
> like "package I is limited new ..." for now, always
> provide the limited view internally, and save the
> Norm-Cohen-string-of-reserved-words for late nights
> at the bar after ARG meetings. ;-)
I think you miss the point: the critical use is to allow signatures and
containers to be instantiated visibly. The syntax needs to reflect that
somehow, and "is limited" doesn't do that (in fact, it seems to imply that
client use is restricted, when the exact opposite is true). Think of what a
client of a package would see if they don't peek in the private part.
The Bob Duff use for recursive data types is likely to be rare, and I didn't
want the syntax to emphasize that use (which is irrelevant for clients
anyway). (If that was the only problem, I would have been in favor of "No
Action" from the beginning.)
That said, people will get used to anything, so I don't care that much what
the syntax is.
****************************************************************
From: Tucker Taft
Date: Monday, October 22, 2007 10:13 PM
> Quoting from the Atlanta minutes:
>
> "Tucker has changed the freezing rules to require freezing at the end of an
> instance and then have piecemeal instantiation. This is necessary so a type
> can contain a component of its own type. Pascal objects that this is a
> change that we had previously agreed to not make. Randy agrees; he notes
> that it is privacy breaking. Pascal says that piecemeal freezing is a dead
> boy issue. He continues that the important issue is to solve the export of
> containers from specs of private types. Tucker notes also that the signature
> package problem is also covered even without piecemeal freezing. Randy notes
> that we could still allow this in 2015 if it actually proves to be
> important."
>
> And now you are reintroducing this again - some ideas just never die.
It may *sound* the same, but it really isn't. This is
not the same as the "piecemeal" freezing we discussed.
Also, the privacy breaking is dramatically reduced if you limit
this to formal types with unknown discriminants, since they
can't be used as components anyway. By eliminating this
issue, the breakage doesn't seem any worse than the various other
legality rules that are checked in the private parts of
instances.
In any case, I can see that at least *you* aren't interested
in this proposal, even if I can convince you that it really
*is* new. ;-) It felt like a step forward to me...
****************************************************************
From: Jean-Pierre Rosen
Date: Wednesday, October 24, 2007 11:06 AM
I've been following this thread, and I must say I am worried by the
complexity, from a user's point of view, of all the solutions presented.
In short: only members of the ARG will be able to use that. Casual users
already know very few about the real possibilities of the language (OK,
that's what makes our business as consultants). I doubt that they will
find out the appropriate spell if they want to instantiate a generic on
a private type (the real need).
The only acceptable solution is to allow:
with Gen;
package pack is
type T is private;
package Inst is new Gen (T);
and nothing else. Can it be done? For example, decide that in this case
actual instantiation does not happen logically at the place where it is
declared, but immediately after the full declaration of T. But of course
the visibility of the instantiation would be public. No use of the
instance would be allowed until it is "logically" instantiated.
Am I pipe-dreaming here?
****************************************************************
From: Edmond Schonberg
Date: Wednesday, October 24, 2007 2:06 PM
> I've been following this thread, and I must say I am worried by the
> complexity, from a user's point of view, of all the solutions
> presented.
You can add: "and from the implementor's point of view".
>
> In short: only members of the ARG will be able to use that. Casual
> users already know very few about the real possibilities of the
> language (OK, that's what makes our business as consultants). I
> doubt that they will find out the appropriate spell if they want to
> instantiate a generic on a private type (the real need).
>
> The only acceptable solution is to allow:
>
> with Gen;
> package pack is
> type T is private;
>
> package Inst is new Gen (T);
>
> and nothing else. Can it be done? For example, decide that in this
> case actual instantiation does not happen logically at the place
> where it is declared, but immediately after the full declaration of
> T. But of course the visibility of the instantiation would be
> public. No use of the instance would be allowed until it is
> "logically" instantiated.
I agree that for a new language design the only reasonable solution
would be to allow types with only partial views to appear as actuals.
However, as was mentioned repeatedly, any change to the freezing
rules is extremely disruptive. It is nothing we would be willing to
do without a very VERY strong user demand, and that is at present
totally absent. If Ada2005 programmers start using containers in
more ambitious forms maybe they will find it convenient to have the
premature instantiations that have motivated this AI. In the
meantime, there is no harm in exploring other designs, but we may
have more pressing things to fix.
****************************************************************
From: Tucker Taft
Date: Wednesday, October 24, 2007 2:19 PM
I agree, simplicity is important. But it is
always dangerous to guess at the simplicity of
the final result based on the complexity of
the discussion leading up to it. There
is often no correlation (either way ;-).
I think the "end private;" idea is pretty
simple from the user perspective, but
I also agree we need to have a complex
discussion about what it really means
from a detailed visibility point of view,
so that it has the right "intuitive" semantics
from the user point of view.
I also think Randy's idea could appear as
simple to a user, so long as the syntax is kept
very simple. I like using the word
"limited" or the phrase "with private" since both
of those suggest what it going on, as in:
package Inst is limited new Gen (T);
or
package Inst is new Gen(T) with private;
followed by a full normal instantiation
in the private part, after the full
definition of T.
private
type T is ...
package Inst is new Gen(T);
This seems relatively simple, and the
partial instantiation syntax is relatively
familiar from either the new "limited new ..." syntax
on type derivation (for the first syntax), or
private extensions (for the second syntax).
I can't see adding a bunch of new
keywords for this relatively simple
idea.
I also don't see sufficient benefit to have two
variants, one that makes a limited view of
the instance visible prior to the full
instantiation, and one that doesn't.
I think I understand Randy's reasons for
the new keywords and the two variants, but
the cost/benefit ratio is too high for
both in my view. "Limited" already has
multiple meanings, and so we can choose how
we want to define a "limited" instantiation,
and saying that it provides only incomplete
types prior to the full instantiation
seems reasonable. Essentially, the instance
is "incompletely defined" prior to the full
instantiation, in the same way a private
type is incompletely defined prior to the
full type definition.
Similarly, "with private" currently means
"partial view of an extension"
so it could also mean "partial view of
an instantiation" (and along the way,
"with private" is suggestive of the
fact that the actual parameter part can
include one or more private types).
I suspect that users could grasp either
pretty quickly.
As far as not having a distinct syntax for
the partial instantiation, we have discussed
something very close to that proposal in the
past, where the fact that one of the actuals
is not fully defined automatically causes a
different interpretation. Unfortunately, the complexity
of the discussion was astronomical, and
ultimately we abandoned it, despite its
apparent simplicity from a user point of view.
So I think we actually have two reasonable
suggestions, one involving partial instantiations
and one allowing multiple visible parts.
If the syntax is chosen carefully, I think
either one of these could pass the simplicity
test. Even "normal" users already understand there
are limits on how a private type can be
used prior to its full definition.
So please keep pushing simplicity, but don't
prejudge based on the complexity of the discussion.
Try to block out the whole ARG discussion, and decide
whether the final result could have a reasonable syntax
and relatively intuitive semantics.
****************************************************************
From: Randy Brukardt
Date: Wednesday, October 24, 2007 2:28 PM
> The only acceptable solution is to allow:
>
> with Gen;
> package pack is
> type T is private;
>
> package Inst is new Gen (T);
>
> and nothing else. Can it be done?
In short, no. ;-) It shouldn't be a surprise that complex problems require
complex solutions -- or, for that matter, that two part declarations also
require two-part instances (just like using a private type in a subprogram
in the spec requires the body of the subprogram to be elsewhere).
> For example, decide that in this case
> actual instantiation does not happen logically at the place where it is
> declared, but immediately after the full declaration of T. But of course
> the visibility of the instantiation would be public. No use of the
> instance would be allowed until it is "logically" instantiated.
>
> Am I pipe-dreaming here?
It's hard to tell for sure, but I think you are.
(1) This does not solve the "Duff" problem of using "list of T" (and other
data structures) inside of T. You can do that with the built-in data
structures of Ada; it is unfortunate that you can't do it with containers. I
don't think this is the most important issue by far, but it is surely
important.
(2) There is a maintenance hazard here, as changing the specification of the
generic or of the package would change where the generic instantiation
actually occurs. This isn't as bad as in some of the other proposals (in
this case, you'd get a compile-time error if there was a problem), but it
still is an issue. [There also might be issues if the instantiation was
mainly used for a side-effect of its elaboration, moving that elaboration
could cause problems. But such code is tricky anyway, so I can't get too
worried.]
One of the reasons for the two-part instantiation is so that the location of
the "real" instantiation is obvious in the source code. That eliminates this
maintenance hazard.
(3) You don't specify in which cases this magic happens. Does it happen with
all generics, or just a special subset. Assuming it happens with all
generics, when do the other generic parameters (those not dependent on
private types) get evaluated? Either answer seems bad in some cases.
(4) Someone had suggested an idea on this line (not exactly this idea) back
in 2004. It eventually was dropped because of various problems.
The net effect is that I don't think this works. To solve (1), you have to
complicate the freezing and elaboration rules and allow partial exporting of
the contents of the generic. (Essentially what Tucker was trying to do.)
Which has been roundly rejected in the past, as it increases the maintenance
hazard and implementation complications.
I understand your concern about complexity for the user. I don't think it is
too bad, as compilers and mentors can recommend the "magic incantation" of
adding a limited instance when it is needed -- and there are plenty of other
examples of two-part declarations in Ada (a user who is surprised by that is
a real newbie). Still it is fair to have the opinion that the solutions are
complex enough that it is not worth solving the problem at all. (This is not
an opinion that I share!)
I do think that all of these solutions look more complex when described in
detail than they would in actual use. (Which includes your proposal above,
if it was described to the same level of detail.) We don't get to handwave
the details, because the devil is always in the details. My main concern has
been that the syntax of the limited instance (or partial instance, for that
matter) doesn't really intuitively describe what is going on. Which is why I
was trying more complex syntaxes that hopefully made more sense when reading
(but Tucker didn't seem to agree, and I guess you don't either).
****************************************************************
From: John Barnes
Date: Tuesday, October 30, 2007 2:32 AM
>> ...In these
>> discussions, I have harbored a personal dislike for the
>> idea of multiple private parts in a single package,
>> but I haven't had a good reason other than aesthetic.
>> But this to me seems like a good reason.
>
> Agreed. I find vague aesthetics much more convincing when backed up by good
> reasons!
Is it not the case that a child package essentially opens the visible part
of the parent once more but after the private part? Is there the essence of
a solution here? Maybe it was discussed before. I recall we did once
consider using child packages as one of the options when discussing cyclic
packages.
****************************************************************
From: Tucker Taft
Date: Tuesday, October 30, 2007 8:12 AM
In choosing semantics for a post-private visible
part, I have suggested we use the visible part of
a child as a model.
I also suggested in a separate note of having
a way to specify that a given child is to
be implicitly "with"ed by all units that "with"
the parent. I had suggested "end with Parent.Child;"
but no one liked that syntax. Somewhat better might
be "at end with ...", e.g.:
at end with Parent.Child1, Parent.Child2, ...;
package Parent is ...
...
end Parent;
or conceivably:
package Parent is
...
private
...
end Parent with Parent.Child1, Parent.Child2;
These give you the ability to have a child that provides
an instantiation that will appear to clients of Parent
to be included inside Parent, but they don't give you any ability
to use that instantiation before the end of the parent package.
You would still need to be able to insert some declarations
after establishing visibility on the child packages,
if you want to use the instantiations to help define
one of the types exported from the parent package.
Given that, it seems simpler to just use a post-private
visible part to both specify the instantiations
needed, as well as any additional declarations that
link the instantiations up to types exported from
the package.
****************************************************************
From: Robert I. Eachus
Date: Friday, November 2, 2007 10:26 AM
>of the parent once more but after the private part? Is there the essence of
>a solution here? Maybe it was discussed before. I recall we did once
>consider using child packages as one of the options when discussing cyclic
>packages.
>
>
I think we need a solution which is as simple as possible (but not
simpler). Effectively splitting a package specification into two parts
is not really a solution, or if it is, there is no work to be done:
package Private_Type is
type PT is private;
...
private
...
end Private_Type;
package Private_Type.Child is
subtype SPT is PT;
package Container is new ...
end Private_Type.Child;
package To_Be_Withed renames Private_Type.Child:
Three separate compliation units, plus one or two bodies, but it does
the trick. If this solution is sufficient, fine. If not, the need is
for a simpler solution not one that is more complex even if more
general. I think that limits the possibilities to three:
1. Change the rules so that a generic package declaration with a formal
parameter of a private type does not cause freezing of the private
type--but any use of the generic instance does. Second, allow the
instantiation of the body of the generic package to be deferred until
some point in the private part. (At the end is probably simplest. But
that forces any renamings of operations of the package to be done in the
body of the package.)
This does lead to complexities for implementors and language lawyers,
but ordinary users can just do what seems natural.
2. Have a new syntax which allows for two part instantiation of a
generic package. This would seem to have all the complexities of the
above solution, and be less simple for users. But it would mean no
changes to existing legal programs.
3. Allow end private, but only followed by generic instantiations (and
only one private part). Again, renaming of operations gets pushed to
the package body.
****************************************************************
From: Jean-Pierre Rosen
Date: Friday, November 2, 2007 1:09 PM
> I think we need a solution which is as simple as possible (but not
> simpler). Effectively splitting a package specification into two parts
> is not really a solution, or if it is, there is no work to be done:
Agreed
> package Private_Type is
> type PT is private;
> ...
> private
> ...
> end Private_Type;
>
> package Private_Type.Child is
> subtype SPT is PT;
> package Container is new ...
> end Private_Type.Child;
Why not:
package Private_Type.Container is new...
>
> package To_Be_Withed renames Private_Type.Child:
Why do we need this?
If the only issue is that the user needs to "with" several children, we
may introduce a "with all Pack" clause that would "with" pack and all
children that are part of the environment.
****************************************************************
From: Tucker Taft
Date: Saturday, January 5, 2008 1:17 PM
While writing an Ada package recently I bumped
into another place where "end private" would have
helped:
package Constructs is
type Root_Construct_Handle is abstract tagged private;
...
generic
type Data_Part is private;
package Handles is
type Construct_Handle is
new Root_Construct_Handle with private;
type RO_Access is access constant Data_Part;
type RW_Access is access all Data_Part;
function Get_RO_Access(Handle : Construct_Handle)
return RO_Access;
function Get_RW_Access(Handle : Construct_Handle)
return RW_Access;
private
type Construct_Handle is
new Root_Construct_Handle with ...
-- ERROR: Parent type of record extension
-- must be completely defined.
end Handles;
private
type Root_Construct_Record;
-- completed in pkg body
type Root_Construct_Access is
access all Root_Construct_Record;
type Root_Construct_Handle is abstract tagged
record
Construct : Root_Construct_Access;
...
end record;
end Constructs;
The ERROR was annoying. I ended up making
Root_Construct_Handle a visible record type,
eliminating the use of the deferred incomplete
type, etc. Somewhat later I realized that
"end private" would have allowed me to
follow my original design pretty easily:
package Constructs is
type Root_Construct_Handle is abstract tagged private;
...
private
type Root_Construct_Record;
-- completed in pkg body
type Root_Construct_Access is
access all Root_Construct_Record;
type Root_Construct_Handle is abstract tagged
record
Construct : Root_Construct_Access;
...
end record;
end private;
generic
type Data_Part is private;
package Handles is
type Construct_Handle is
new Root_Construct_Handle with private;
type RO_Access is access constant Data_Part;
type RW_Access is access all Data_Part;
function Get_RO_Access(Handle : Construct_Handle)
return RO_Access;
function Get_RW_Access(Handle : Construct_Handle)
return RW_Access;
private
type Construct_Handle is
new Root_Construct_Handle with ...
-- No Error! Parent type of record extension
-- is now completely defined.
end Handles;
end Constructs;
So "end private" not only allows the use of generic
instantiation within the package defining a private
type, but it also allows defining type extensions
in nested packages or nested generic packages.
I realize I could make the nested generic package
into a child, but then the nested generic's body
would not have visibility on the completion of
"Root_Construct_Record." So I would have to
lift that into the spec. In general it is frustrating
when you end up in one of these either/or situations,
where if you make it nested you get some capabilities,
whereas if you make it a child you get other capabilities,
and neither is a superset of the other.
"End private" in general could give one the ability to
create nested packages, nested instantiations,
and nested generics with the same (or more) flexibility
than one can create child packages, child instantiations,
and child generics. Of course if the enclosing construct
is itself a generic, having the flexibility to create
nested packages and instantiations is even more important,
since generics can only have generic children.
I realize I haven't written up the AI for "end private"
yet, so part of this is simply building the "discussion"
section before launching off on proposing some wording.
The direction I am leaning is that about the only
thing different between the first "half" of the visible
part and the last "half" is that the deferred constants
and the partial views are completely defined by the
time you hit "end private." Otherwise, visibility
is the same at "end private" as it was immediately
preceding "private."
****************************************************************
From: Randy Brukardt
Date: Monday, January 7, 2008 10:41 PM
...
> I realize I haven't written up the AI for "end private"
> yet, so part of this is simply building the "discussion"
> section before launching off on proposing some wording.
I don't think there is a lot of doubt about the "use-case" for this idea. I
think that the concern is in the potential anomalies that could result if
this is done carelessly.
> The direction I am leaning is that about the only
> thing different between the first "half" of the visible
> part and the last "half" is that the deferred constants
> and the partial views are completely defined by the
> time you hit "end private." Otherwise, visibility
> is the same at "end private" as it was immediately
> preceding "private."
I don't think that works, at least the way you have it written here. I know
that we carefully crafted the rules about incomplete types based on the
notion that you can't have a visible routine using a deferred incomplete
type. There probably are other issues.
But without a firm proposal, I can't tell for sure if there are problems or
if I just misunderstand something. In summary, please spend more of your
time on a full proposal, and less of your time on hyping this idea before we
can even figure out if it works. (Besides, you have a boatload of other
homework to do in the next 4 weeks...)
****************************************************************
From: Robert A. Duff
Date: Tuesday, January 8, 2008 7:13 AM
> I don't think that works, at least the way you have it written here. I know
> that we carefully crafted the rules about incomplete types based on the
> notion that you can't have a visible routine using a deferred incomplete
> type. There probably are other issues.
What's a "deferred incomplete type"? Not defined in the RM, as far as I can
see. I'm guessing you mean the incomplete type is declared in the private
part, and the full type in the body. Right? (AdaCore folks call those "Taft
Amendment Types".)
If so, the way I read Tuck's last sentence above, such a type is not visible in
either of the two visible parts. So you can't have a visible routine using
such a type.
The previous sentence, about "completely defined", applies only to partial
views declared in the first visible part, I presume. Things declared (only) in
the private part are irrelevant.
****************************************************************
From: Tucker Taft
Date: Monday, July 21, 2008 3:09 PM
I recently took 4, 4-hour train rides, and here is one of the products of
having a bit too much time on my hands (it doesn't happen often!).
[Ed. - this is version /01 of the AI.]
****************************************************************
From: Randy Brukardt
Date: Thursday, July 31, 2008 11:22 PM
> I recently took 4, 4-hour train rides, and here is one of the products
> of having a bit too much time on my hands (it doesn't happen often!).
Please schedule some more train trips. Perhaps on the Trans-Siberian
railroad. ;-)
I took the time to read this entire proposal, and have some quibbles.
(I haven't thought of any show-stoppers, but I haven't tried too hard to
find them. That's Steve's job. ;-)
The AI asks:
> TBD: Is it OK to refer to the "first declaration list of the visible
> part of a package"? Does that work for generic packages, or is the
> first declaration list the formal part? Do we need to say "first list
> of basic_declarative_items of the visible part of the package"? Same
> question applies for deferred constants in 7.4(3).
"first list" and "third list" make me rather ill. I think that both of
these parts need easy to remember names. Obviously, we have to talk about
"first list" and "third list" when we define these names, but hopefully
nowhere else. Calling them collectively the "visible part" is probably
needed to avoid changing many other rules, but then they also need their
own names. Perhaps "leading visible part" and "trailing visible part"?
"primary visible part" and "seconday visible part"? Such a name also
would get rid of the clunky "end private part" terminology that we've
used for the lack of a decent name.
Under "IMPLEMENTATION ISSUES", there is:
> Another possible approach might be to treat the private part roughly
> like a nested construct, where the visibility is "pushed" before the
> private part, and then "popped" afterward. Prior to popping the
> private part visibility, it would want to be "saved" so that when it
> is wanted again, such as in the package body, or in the private part
> of some child, it can be restored. This kind of "save" might be
> similar to what is done with subunit "stubs."
While I can't argue with the basic idea (it won't work in Janus/Ada,
but that's not relevant), but I think the last sentence is bogus. Handling
of a subunit "stub" means taking a snapshot of the state of the symboltable
at the point of the stub and restoring it when needed. (Janus/Ada writes
the entire snapshot to disk at this point, and loads it like a specification
when processing the subunit.) That sort of approach would not work here,
because it would drop the contents of the secondary visible part (ah, much
better than "end private" part or "third declaration list"), which clearly
wouldn't do.
Later in the discussion:
> Allowing "end private;" has relatively straightforward semantics, with
> no new freezing rules or new kinds of instantiations. Other than the
> syntax change, the main significant change is the proposed 8.2 rule
> that the immediate scope of a declaration in the private part does
> *not* include the list of visible declarations, if any, that follow
> "end private;". This makes them very similar to the declarations in
> the visible part of a child unit.
Humm, if we don't have any new freezing rules, then we have privacy leakage
of freezing. Back in the day, Pascal would have screamed bloody murder
because of the damage to incremental compilation.
For instance:
package Leaker is
type Int is range 0 .. 100;
private
Obj : Int; -- Freezes Int.
end private; -- I really hate this semicolon; it feels to me as natural as
-- "end record" (that is, not at all).
for Int'Size use 8; -- Illegal, Int is frozen.
end Leaker;
Of course, if Obj is removed, then the rep. clause is allowed.
I can't get too upset about this particular problem (which is why I labeled
this "quibbles", not "issues"). It could be fixed by simply disallowing
representation items in secondary visible part for entities declared in the
primary visible part. That of course would be compatible (there being no
secondary visible part in older Ada). I think that allowing representation clauses
far away from their declarations is a major mistake in Ada, and I'd be happy
to rein that "feature" in somewhat.
But I do wonder if there are other effects of this "freezing leakage" that
would be more serious. I can't think of any off-hand. But perhaps we need to
consider freezing everything at the end of the private part (just as we do
today); then there can be no private freezing leakage.
****************************************************************
From: Tucker Taft
Date: Friday, August 1, 2008 10:52 AM
> Later in the discussion:
>
>> Allowing "end private;" has relatively straightforward semantics,
>> with no new freezing rules or new kinds of instantiations. Other than
>> the syntax change, the main significant change is the proposed 8.2
>> rule that the immediate scope of a declaration in the private part
>> does *not* include the list of visible declarations, if any, that
>> follow "end private;". This makes them very similar to the
>> declarations in the visible part of a child unit.
>
> Humm, if we don't have any new freezing rules, then we have privacy
> leakage of freezing. Back in the day, Pascal would have screamed
> bloody murder because of the damage to incremental compilation.
I had thought about freezing all entities other than incomplete types
at the point of the "end private," but then I bumped into some situations
where that was undesirably restrictive. For example
package Abc is
type X is private;
procedure Foo(X : Integer);
private
type X is record ... end record;
end private;
package P is new Gen(X);
procedure Foo(X : Integer) renames P.Fum;
end Abc;
If we force "Foo" to be frozen at the "end private" then the renaming would
not be permitted, and could not be moved into the body if the calling
convention of P.Fum was not "Ada."
The "freezing" leakage doesn't seem so bad, because it is very local.
No code outside the package could be affected by anything it learns one way
or the other, so if the implementor decides to change the private part in
a subsequent release of the package, if they can manage to keep the visible
part compatible, the clients would be unaffected. If they can't keep the
visible part compatible, then that is the implementor's problem. In any
case it doesn't affect clients.
>
> For instance:
>
> package Leaker is
> type Int is range 0 .. 100;
> private
> Obj : Int; -- Freezes Int.
> end private; -- I really hate this semicolon; it feels to me as
> natural as "end record" (that is, not at all).
> for Int'Size use 8; -- Illegal, Int is frozen.
> end Leaker;
>
> Of course, if Obj is removed, then the rep. clause is allowed.
>
> I can't get too upset about this particular problem (which is why I
> labeled this "quibbles", not "issues"). It could be fixed by simply
> disallowing representation items in secondary visible part for
> entities declared in the primary visible part. That of course would be
> compatible (there being no secondary visible part in older Ada). I
> think that allowing representation clauses far away from their
> declarations is a major mistake in Ada, and I'd be happy to rein that "feature" in somewhat.
>
> But I do wonder if there are other effects of this "freezing leakage"
> that would be more serious. I can't think of any off-hand. But perhaps
> we need to consider freezing everything at the end of the private part
> (just as we do today); then there can be no private freezing leakage.
Again, I don't think it is a significant problem because it doesn't interfere
with information hiding. The client can't become dependent on whether the
freezing happens inside or outside, since it definitely happens by the time
the compilation unit containing the package is completed.
****************************************************************
From: Randy Brukardt
Date: Friday, August 1, 2008 3:03 PM
> The "freezing" leakage doesn't seem so bad, because it is very local.
> No code outside the package could be affected by anything it learns
> one way or the other, so if the implementor decides to change the
> private part in a subsequent release of the package, if they can
> manage to keep the visible part compatible, the clients would be
> unaffected.
> If they can't keep the visible part compatible, then that is the
> implementor's problem. In any case it doesn't affect clients.
It's true that freezing itself can't affect clients, but surely the things
that you can do with it could. For instance, consider a modification of your
example:
package Abc is
type X is tagged private;
private
type X is tagged record ... end record;
end private;
package P is new Gen(X);
for X'Write use P.Fum;
end Abc;
The fact that the representation clause is visible affects availability (and
thus legality) of later extensions.
Your comment about maintainer beware is true, I suppose, but I still am nervous
about this leakage. It's too bad that we don't have anyone who could tell us
if this sort of leakage would be a problem for the IBM compiler's incremental
compilation.
In any case, this needs to be in the discussion somewhere, because we need to
know that the potential problems were considered.
BTW, the "end private;" (especially the semicolon) in the above makes me ill.
The semicolon should only occur at the end of a construct, and a private part
is not a construct (it is just a part). I'd rather we used syntax like:
package Abc is
type X is tagged private;
private
type X is tagged record ... end record;
visible
package P is new Gen(X);
for X'Write use P.Fum;
end Abc;
which looks much more consistent.
Another off-the-wall comment: do we need to add this extra part in other places
where we have private parts (tasks and protected)? It seems inconsistent if
we don't, even though it doesn't seem very useful in those cases.
****************************************************************
From: Tucker Taft
Date: Friday, August 1, 2008 7:15 PM
> The fact that the representation clause is visible affects
> availability (and thus legality) of later extensions.
That doesn't look legal to me, since X is frozen by the time you get
to the "for X'Write use ...". But in any case your point is valid,
but again, it seems to be all about what appears in the post-private
visible part, not about what may or may not be in the private part.
If either visible part changes, then clearly the clients are affected.
It is true that changes in the private part might affect the legality
of items in the visible part, but that is already true. Clearly you
should spec-out your visible part first, and then construct and place
the private part to support the visible declarations.
> Your comment about maintainer beware is true, I suppose, but I still
> am nervous about this leakage. It's too bad that we don't have anyone
> who could tell us if this sort of leakage would be a problem for the
> IBM compiler's incremental compilation.
I presume Pascal and Steve have some memory of these issues...
> In any case, this needs to be in the discussion somewhere, because we
> need to know that the potential problems were considered.
Agreed.
> BTW, the "end private;" (especially the semicolon) in the above makes
> me ill. The semicolon should only occur at the end of a construct, and
> a private part is not a construct (it is just a part). I'd rather we
> used syntax like:
>
> package Abc is
> type X is tagged private;
> private
> type X is tagged record ... end record;
> visible
> package P is new Gen(X);
> for X'Write use P.Fum;
> end Abc;
>
> which looks much more consistent.
Perhaps. However, I had originally asked about leaving off the ";" on
"end private;" and that made John ill.
You may remember I also proposed "not private"... ;-).
Note also that semicolons do appear in the middle of constructs, in particular,
for formal parameters, though admittedly those are as separators rather
than terminators (which is arguably not a feature!).
> Another off-the-wall comment: do we need to add this extra part in
> other places where we have private parts (tasks and protected)? It
> seems inconsistent if we don't, even though it doesn't seem very
> useful in those cases.
I considered that, and in fact the wording I added in various places doesn't
preclude them, but I couldn't find an argument for making the syntax change.
Perhaps someone else will, or will feel the uniformity argument is sufficiently
strong to justify them.
****************************************************************
From: Randy Brukardt
Date: Friday, August 1, 2008 7:32 PM
> Perhaps. However, I had originally asked about leaving off the ";" on
> "end private;" and that made John ill.
> You may remember I also proposed "not private"... ;-).
Yes, I recall John's issue. And I think he is right, in that a semicolon
should go with an "end", but for the wrong reason: "end" doesn't belong
in the middle of anything.
"not private" is fairly ambiguous. I think a new keyword is justified
(especially as there will be more assuming some of the other proposals
are adopted).
Perhaps that keyword should be "public" rather than "visible".
package Abc is
type X is tagged private;
private
type X is tagged record ... end record;
public
package P is new Gen(X);
for X'Write use P.Fum;
end Abc;
> Note also that semicolons do appear in the middle of constructs, in
> particular, for formal parameters, though admittedly those are as
> separators rather than terminators (which is arguably not a feature!).
Yes, and I sometimes write those as commas and find out when the compiler
complains. Two wrongs don't make a right. Anyway, this isn't very important,
but I wanted to put my position on the record.
> > Another off-the-wall comment: do we need to add this extra part in
> > other places where we have private parts (tasks and protected)? It
> > seems inconsistent if we don't, even though it doesn't seem very
> > useful in those cases.
>
> I considered that, and in fact the wording I added in various places
> doesn't preclude them, but I couldn't find an argument for making the
> syntax change. Perhaps someone else will, or will feel the uniformity
> argument is sufficiently strong to justify them.
The only argument for the change is consistency (I don't think there is any
semantic need). OTOH, there isn't any semantic need for private parts for tasks,
and yet we still have them. So I lean toward adding these parts (if for no
other reason than not having to remember where they are allowed).
Another thing that is not very important.
****************************************************************
From: Robert A. Duff
Date: Friday, August 1, 2008 9:16 PM
> The only argument for the change is consistency (I don't think there
> is any semantic need). OTOH, there isn't any semantic need for private
> parts for tasks, and yet we still have them.
They are needed if you want to have an entry that the task requeues upon, and keep
that entry private to the task.
****************************************************************
From: Dan Eilers
Date: Friday, August 1, 2008 4:00 PM
Would the following be legal?
package Abc is
private
type X is record ... end record;
end private;
type X is private;
end Abc;
That is, can a private type declaration appear in the trailing visible part?
If someone wrote:
package Abc2 is
private
type hidden_X is record ... end record;
end private;
type X is new hidden_X;
end Abc2;
would X be essentially a private type?
If we allow trailing visible parts, could a user put all the visible declarations there,
in order to keep them together?
****************************************************************
From: Tucker Taft
Date: Saturday, August 2, 2008 3:56 AM
> Would the following be legal?
>
> package Abc is
> private
> type X is record ... end record;
> end private;
> type X is private;
> end Abc;
>
> That is, can a private type declaration appear in the trailing visible
> part?
No. In the proposal as written, private types and deferred constants
are only permitted in the pre-private visible part.
>
> If someone wrote:
>
> package Abc2 is
> private
> type hidden_X is record ... end record;
> end private;
> type X is new hidden_X;
> end Abc2;
>
> would X be essentially a private type?
The post-private visible part has no visibility on the declarations of the
private part (you might want to reread the AI ;-).
> If we allow trailing visible parts, could a user put all the visible
> declarations there, in order to keep them together?
Not if any of them are private types or deferred constants.
****************************************************************
From: John Barnes
Date: Monday, August 4, 2008 1:40 AM
>> Perhaps. However, I had originally asked about leaving off the ";"
>> on "end private;" and that made John ill.
Yes it did.
>> You may remember I also proposed "not private"... ;-).
>
> Yes, I recall John's issue. And I think he is right, in that a
> semicolon should go with an "end", but for the wrong reason: "end"
> doesn't belong in the middle of anything.
>
> "not private" is fairly ambiguous. I think a new keyword is justified
> (especially as there will be more assuming some of the other proposals
> are adopted).
A new keyword does seem a bit of a luxury.
> Perhaps that keyword should be "public" rather than "visible".
But if we are going to have one then public is good. Can we think of
ther uses for public?
****************************************************************
From: Dan Eilers
Date: Monday, August 4, 2008 5:34 PM
> > Would the following be legal?
> >
> > package Abc is
> > private
> > type X is record ... end record;
> > end private;
> > type X is private;
> > end Abc;
> >
> > That is, can a private type declaration appear in the trailing
> > visible part?
>
> No. In the proposal as written, private types and deferred constants
> are only permitted in the pre-private visible part.
OK, but why?
It seems to me to be an unnecessary complication to propose that the
rules for what's allowed in the trailing visible part be different
(no private types or deferred constants) from the rules for the leading
visible part.
There wouldn't seem to me to be any problem with privacy leakage or
elaboration ordering, etc. I understand that the trailing visible part
is proposed to have no visibility on the private part, but I don't think
that presents an obstacle, since the leading visible part doesn't have
such visibility either, and it can declare private types and deferred
constants with confidence that their full definition will appear in
the private part.
The name "deferred" constants would be somewhat odd, for constants that
are made visible in a trailing visible part, but that's more of a
naming issue. Maybe we should call them "hidden" constants instead of
"deferred".
What I am getting at is that maybe we could simplify the proposed
"sandwich" structure, and give the user the option of either a leading
visible part or a trailing visible part, but not both in the same package.
This would have the advantage that the trailing visible part would be less
likely to be overlooked if it included all the visible declarations.
It might also facilitate the proposal to extract the private part into a
separate file.
****************************************************************
From: Randy Brukardt
Date: Monday, August 4, 2008 6:36 PM
> > No. In the proposal as written, private types and deferred
> > constants are only permitted in the pre-private visible part.
>
> OK, but why?
Because it would mean abandoning the linear elaboration/declaration model,
perhaps?
> It seems to me to be an unnecessary complication to propose that the
> rules for what's allowed in the trailing visible part be different (no
> private types or deferred constants) from the rules for the leading
> visible part.
The rules aren't different. A private type must be completed in the
trailing private part. Since there is no such part here, it cannot be
completed and thus is always illegal. But the rules are unchanged.
> There wouldn't seem to me to be any problem with privacy leakage or
> elaboration ordering, etc. I understand that the trailing visible
> part is proposed to have no visibility on the private part, but I
> don't think that presents an obstacle, since the leading visible part
> doesn't have such visibility either, and it can declare private types
> and deferred constants with confidence that their full definition will
> appear in the private part.
You think that "completion" before the thing being completed is not new?
Janus/Ada processes declarations linearly, and having to check legal
"completions" for private types would require duplicating quite a bit of
code (especially as that code would have to ignore visibility rules).
> The name "deferred" constants would be somewhat odd, for constants
> that are made visible in a trailing visible part, but that's more of a
> naming issue. Maybe we should call them "hidden" constants instead of
> "deferred".
As is the term "completion" for something that would happen before the declaration.
> What I am getting at is that maybe we could simplify the proposed
> "sandwich" structure, and give the user the option of either a leading
> visible part or a trailing visible part, but not both in the same
> package. This would have the advantage that the trailing visible part
> would be less likely to be overlooked if it included all the visible
> declarations.
Seems like a dubious advantage to me. If this proposal gets at all complex,
it's not going anywhere. The only reason for considering it is that it appears
to be a simpler fix than any other. I would expect style guides to recommend
to only put instances in the trailing public part (preferably nothing).
> It might also facilitate the proposal to extract the private part into
> a separate file.
I hope this latter proposal never gets off the ground; it would force all
compilers into a source-based model where compiling of specs has no significant
effect. Essentially, it would require all Ada compilers to work like GNAT,
which seems bad from a diversity-of-implementations standpoint.
****************************************************************
From: Dan Eilers
Date: Monday, August 4, 2008 8:14 PM
> > OK, but why?
>
> Because it would mean abandoning the linear elaboration/declaration
> model, perhaps?
Are you saying that certain declarations would have to be elaborated
out-of-order? I don't see that.
> The rules aren't different. A private type must be completed in the
> trailing private part. Since there is no such part here, it cannot be
> completed and thus is always illegal. But the rules are unchanged.
I think we are in agreement that the consequences of what the rules allow
would be different, even if the wording of the rules was exactly the same.
I was hoping to eliminate the seemingly unnecessary difference in the
consequences of the rules.
> You think that "completion" before the thing being completed is not new?
I said I didn't think it presented an obstacle. It is already quite common
to declare a full type in an inner scope, and subsequently export it using
a derived type in an outer scope, which is conceptually very similar.
> Janus/Ada processes declarations linearly, and having to check legal
> "completions" for private types would require duplicating quite a bit
> of code (especially as that code would have to ignore visibility rules).
We process declarations linearly too, but I don't see the problem here.
Instead of code duplication, you can probably achieve substantial code reuse.
> > The name "deferred" constants would be somewhat odd, for constants
> > that are made visible in a trailing visible part, but that's more of
> > a naming issue. Maybe we should call them "hidden" constants
> > instead of "deferred".
>
> As is the term "completion" for something that would happen before the
> declaration.
Agreed. "Completion" would be odd, but that seems to be a minor issue.
> Seems like a dubious advantage to me. If this proposal gets at all
> complex, it's not going anywhere.
I was actually trying to simplify things (two parts in a package instead of
three).
> > It might also facilitate the proposal to extract the private part
> > into a separate file.
>
> I hope this latter proposal never gets off the ground; it would force
> all compilers into a source-based model where compiling of specs has
> no significant effect.
I don't follow this. I was actually thinking that being able to put the
private part first would make it easier to split the private part off
into a separate source file, using existing compilation models.
****************************************************************
From: Randy Brukardt
Date: Monday, August 4, 2008 9:28 PM
...
> > You think that "completion" before the thing being
> completed is not new?
>
> I said I didn't think it presented an obstacle. It is already quite
> common to declare a full type in an inner scope, and subsequently
> export it using a derived type in an outer scope, which is
> conceptually very similar.
Maybe to you, but not to me. The declaration of a new type is very different
than the completion of the *same* type.
> > Janus/Ada processes declarations linearly, and having to check legal
> > "completions" for private types would require duplicating quite a
> > bit of code (especially as that code would have to ignore visibility
> > rules).
>
> We process declarations linearly too, but I don't see the problem here.
> Instead of code duplication, you can probably achieve substantial code
> reuse.
Only by a very massive restructuring of code; the code that processes private
type declarations is completely separate from that processing full type
declarations. I'm sure it would be possible and perhaps desirable to share code,
but it surely would be a lot of work. It probably would be in your compiler, too.
...
> > Seems like a dubious advantage to me. If this proposal gets at all
> > complex, it's not going anywhere.
>
> I was actually trying to simplify things (two parts in a package
> instead of three).
True enough, but I don't think that can be done.
> What I am getting at is that maybe we could simplify the proposed
> "sandwich" structure, and give the user the option of either a leading
> visible part or a trailing visible part, but not both in the same
> package. This would have the advantage that the trailing visible part
> would be less likely to be overlooked if it included all the visible
> declarations.
I should have mentioned this the last time, but that would be very limiting
as to what you could put into the visible part.
Let's take an example pulled from Claw:
package Claw.Trackbar is
type Trackbar_Type is new Claw.Scrolling.Root_Scroll_Type with private;
type Trackbar_Ticks_Type is (No_Ticks,
Bottom_Ticks, -- On right side for vertical trackbar
Top_Ticks, -- On left side for vertical trackbar
Both_Side_Ticks);
type Trackbar_Slider_Type is (No_Slider, Fixed_Size, Auto_Size);
subtype Frequency_Subtype is Claw.Int range 0 .. Claw.Int'Last;
procedure Modify (
Trackbar : in out Trackbar_Type;
Direction : in Claw.Scrolling.Direction_Type;
Ticks : in Claw.Trackbar.Trackbar_Ticks_Type
:= Claw.Trackbar.Bottom_Ticks;
Slider : in Claw.Trackbar.Trackbar_Slider_Type
:= Claw.Trackbar.Auto_Size;
Tab_Stop : in Boolean := True;
Allow_Selection_Range : Boolean := FALSE);
-- Many more routines.
private
type Trackbar_Type is new CLAW.Scrolling.Root_Scroll_Type with record
Ticks : Claw.Trackbar.Trackbar_Ticks_Type;
Slider : Claw.Trackbar.Trackbar_Slider_Type;
...
end record;
end Claw.Trackbar;
Now, imagine that we wanted to export a Vector of trackbars. (This particular
example is a little weird, but there are other Claw datatypes, such as tooltips,
where there are operations defined on arrays and it would make sense for there
to be operations defined on vectors as well.)
Using Tucker's proposal, we would have something like:
package Claw.Trackbar is
type Trackbar_Type is new Claw.Scrolling.Root_Scroll_Type with private;
type Trackbar_Ticks_Type is (No_Ticks,
Bottom_Ticks, -- On right side for vertical trackbar
Top_Ticks, -- On left side for vertical trackbar
Both_Side_Ticks);
type Trackbar_Slider_Type is (No_Slider, Fixed_Size, Auto_Size);
procedure Modify (
Trackbar : in out Trackbar_Type;
Direction : in Claw.Scrolling.Direction_Type;
Ticks : in Claw.Trackbar.Trackbar_Ticks_Type
:= Claw.Trackbar.Bottom_Ticks;
Slider : in Claw.Trackbar.Trackbar_Slider_Type
:= Claw.Trackbar.Auto_Size;
Tab_Stop : in Boolean := True;
Allow_Selection_Range : Boolean := FALSE);
-- Many more routines.
private
type Trackbar_Type is new CLAW.Scrolling.Root_Scroll_Type with record
Ticks : Claw.Trackbar.Trackbar_Ticks_Type;
Slider : Claw.Trackbar.Trackbar_Slider_Type;
...
end record;
public
for Trackbar_Vectors is new Ada.Containers.Vectors (Trackbar_Type);
-- Define operations on Trackbar_Vectors here.
end Claw.Trackbar;
I don't see any way to move this into a single trailing visible part, because we
can't export things declared in the private part. Putting everything in the public part
doesn't work:
package Claw.Trackbar is
private
type Trackbar_Type is new CLAW.Scrolling.Root_Scroll_Type with record
Ticks : Claw.Trackbar.Trackbar_Ticks_Type; -- ERROR: not defined yet.
Slider : Claw.Trackbar.Trackbar_Slider_Type; -- ERROR: not defined yet.
...
end record;
public
... -- Combined visible parts from the previous package.
end Claw.Trackbar;
and moving the declarations into the private part doesn't work either:
package Claw.Trackbar is
private
type Trackbar_Ticks_Type is (No_Ticks,
Bottom_Ticks, -- On right side for vertical trackbar
Top_Ticks, -- On left side for vertical trackbar
Both_Side_Ticks);
type Trackbar_Slider_Type is (No_Slider, Fixed_Size, Auto_Size);
type Trackbar_Type is new CLAW.Scrolling.Root_Scroll_Type with record
Ticks : Claw.Trackbar.Trackbar_Ticks_Type; -- OK.
Slider : Claw.Trackbar.Trackbar_Slider_Type; -- OK.
...
end record;
public
type Trackbar_Type is new Claw.Scrolling.Root_Scroll_Type with private;
procedure Modify (
Trackbar : in out Trackbar_Type;
Direction : in Claw.Scrolling.Direction_Type;
Ticks : in Claw.Trackbar.Trackbar_Ticks_Type
:= Claw.Trackbar.Bottom_Ticks; -- ERROR: Not visible.
Slider : in Claw.Trackbar.Trackbar_Slider_Type
:= Claw.Trackbar.Auto_Size; -- ERROR: Not visible.
Tab_Stop : in Boolean := True;
Allow_Selection_Range : Boolean := FALSE);
-- Many more routines.
for Trackbar_Vectors is new Ada.Containers.Vectors (Trackbar_Type);
-- Define operations on Trackbar_Vectors here.
end Claw.Trackbar;
You could declare the types private, but then you'd lose the fact that they are
enumeration types and you have to declare a whole second set of literals as
deferred constants -- that would be a maintenance hazard.
You could restructure everything and pull those types into a separate package
-- but that package would exist for no reason other than to avoid this
all-or-nothing rule.
Besides, the entire point of this proposal is to avoid wholesale restructuring.
It is completely clear that you can write things like this, you just have to put
various things into child (or unrelated) packages. So if you have to substantally
restructure in order to use it, I don't think it really solves the problem.
I suppose you could try to allow limited private leakage to fix this problem, but
that seems like a slippery slope that leads straight to hell (semantic or methodological).
> > > It might also facilitate the proposal to extract the private part
> > > into a separate file.
> >
> > I hope this latter proposal never gets off the ground; it would
> > force all compilers into a source-based model where compiling of
> > specs has no significant effect.
>
> I don't follow this. I was actually thinking that being able to put
> the private part first would make it easier to split the private part
> off into a separate source file, using existing compilation models.
Maybe, but that would put substantial (new) restrictions on what could appear
in the visible part of a package associated with such a private part.
And I very much doubt that the people interested in this are going to settle
for only leading private parts (after all, they want them now, and there is
no such thing), so that is pretty much irrelevant.
****************************************************************
From: Edmond Schonberg
Date: Tuesday, August 5, 2008 7:58 AM
> > It might also facilitate the proposal to extract the private
> > part into a separate file.
> I hope this latter proposal never gets off the ground; it would force all
> compilers into a source-based model where compiling of specs has no
> significant effect. Essentially, it would require all Ada compilers to work
> like GNAT, which seems bad from a diversity-of-implementations standpoint.
Placing the private part in a separate file is independent of the "end private"
proposal, and has nothing to do with the source-model of compilation. But I
fully agree with Randy: anything that affects the linear order of elaboration
is a non-starter. The management of private parts permeates the front-end
of the compiler, and any substantial change in this machinery is simply
out of the question. The "end private" model can probably be incorporated
without major surgery, but we won't know until we try to prototype it. So far,
the only compelling reason for introducing the trailing visible part is to
provide instantiations. We should stick to this circumscribed goal.
****************************************************************
From: Randy Brukardt
Date: Tuesday, August 5, 2008 6:44 PM
> Placing the private part in a separate file is independent of the "end
private" proposal,
Certainly true.
> ... and has nothing to do with the source-model of compilation.
Definitely not true, at least in a practical sense.
Such a proposal cannot be purely an "include" file like construct, because
that would not have any place in the Ada standard. As Robert likes to say,
iles do not appear anywhere in the Ada standard, so it isn't possible
to talk about them in the standard. An implementation could do something
like it, but it would be completely outside of the standard.
So I'm presuming that any such language mechanism would look somewhat
like a stub, and could be separately compiled presuming that the public
part of the specification has already been compiled.
Now, Janus/Ada is designed to compile individual source files, one at a time,
containing one or more units, without any further restrictions than that
the units with semantic dependencies must be compiled first. There is no
naming requirements or other ordering requirements.
Moreover, it's clear that there is not enough information in the public
part alone to support any traditional compilation with it.
Thus, some major changes to the compilation model would be needed to the
Janus/Ada model in order to support separate private parts.
One possible approach would be to support some sort of partial compilation
for the public part. That (given that there is little useful information
in such a part) would essentially be tokenizing the specfication for
*real* compilation later with the private part. The net effect would be
to design a whole new file structure for this. One could ask why bother?
An alternative would be to drop the idea of single file at a time compilation.
But that would require specifying many files and their relationships to
the compiler, which would not be very user friendly. The only sane way
to make that useable would be to require source file/unit naming to follow
some standard model (or some equivalence in project files).
But that is the essense of the source-model of compilation: compiling
multiple files at a time to avoid saving partial intermediate results.
I suppose some insane implementer might try to avoid source-model at
all costs and make their compiler unusable or do three times as much work
to implement it (I may in fact resemble that implementer :-), but the
net effect is to essentially require the essense of source-model
compilation for any new implementations that might exist. (Just like
no new implementation would ever consider universal code sharing for
generics). Real innovation does not come from the existing players, in
general.
Of course, the real question is what precisely is this supposed feature
supposed to be helping? One of the main reasons for making private packages
useful with "private with" was to allow them to be used in private parts,
making version control of private alternatives much easier. I could see
separate private parts being used for handling alternatives, but private
packages are better. (And none of these solutions are a full solution to
alternatives.) So it's not at all clear to me that there are compelling
reasons for such a feature.
Which is all off-topic here anyway.
****************************************************************
From: Dan Eilers
Date: Tuesday, August 5, 2008 1:50 AM
Your example is compelling that two parts are insufficient.
But I think by extension it is equally compelling that three parts,
or any finite number, are insufficient as well.
Suppose in some package you want a public enumeration type T1, and
a private record T2 with a field of type T1, and a public vector T3
of the private record T2, and another private record T4 with a field
of the public vector T3, and another public vector T5 of the private
record T4.
Won't this require a 5-part package, with T1 in the first public part,
T2 in the first private part, T3 in the second public part, T4 in the
second private part, and T5 in the third public part?
****************************************************************
From: Tucker Taft
Date: Tuesday, August 5, 2008 9:38 AM
You are making a different proposal, in my view.
Feel free to do so, but the one I made is definitely
one where completions continue to follow the partial
view. I find your suggestion confusing and inconsistent
with other parts of the language, but if you want
to write up something, that's fine. But it is definitely
not something I would want to include in this "end private"
AI. It seems like one of those ideas that can kill a proposal
by making it at least appear more complicated than most
people can stomach.
****************************************************************
From: Dan Eilers
Date: Tuesday, August 5, 2008 5:45 PM
> But it is definitely not something I would want to include in this
> "end private" AI.
I think the existing AI would benefit from a little better reasoning
in the first paragraph of the !proposal where it says:
> In the "post-private" visible part, no new private views nor deferred
> constants may be declared, as there would be no place to complete them.
Such private views or deferred constants _could_ in fact be "completed"
in the preceding private part, if we wanted to allow that.
Randy and Ed have both referred to abandoning the linear
elaboration/declaration model, but it's not at all clear to me that
this change would cause any elaboration difficulties. If there are
such difficulties, then maybe the AI should point those out as the
reason for not allowing private views or deferred constants in the
trailing visible part.
> I find your suggestion confusing and inconsistent with other parts of
> the language, ...
Yes, it's certainly inconsistent with longstanding terminology such
as "deferred" constants, which implies that the full definition is
coming later. But that's just a terminology issue. The essence of
a deferred constant is not that its partial view is provided textually
earlier than its full view, but that it _has_ a partial view. If
deferred constants had originally been called "opaque" constants,
I don't think providing the partial view textually later than the
full view, but in an outer scope, would be confusing.
****************************************************************
From: Randy Brukardt
Date: Tuesday, August 5, 2008 6:20 PM
> Won't this require a 5-part package, with T1 in the first public part,
> T2 in the first private part, T3 in the second public part,
> T4 in the second private part, and T5 in the third public part?
I tend to agree. We all know the rule about having zero, one, or many
of something. Clearly, this proposal as written fails that rule. One reason
that I'm not very excited about this proposal is the feeling that it
eventually will morph into an unlimited number of such parts, with
the loss of readability (and implementation complexity) that that entails.
But we do have to avoid FUD and "slippery slope" arguments by themselves.
The current single part proposal doesn't really have those problems, and
it does solve the instantiation problem in a useful way.
Note that if we were willing to abandon the linear elaboration/linear
freezing model, we probably already would have solved this with a no-freeze
instantiation. Or we could keep the visible part together by having a
deferred part that logically comes after the private part but is written
before it. But both of these would add a huge amount of new complexity
to compilers to solve a problem which can be (less conviniently) solved
with existing language constructs. The search here is to find a solution
that allows convinient solving of the problem without too much
implementation or conceptual pain.
****************************************************************
From: Tucker Taft
Date: Tuesday, August 5, 2008 7:55 PM
Calling this just a "terminology" issue reflects how differently
we see this. I am not worried about terminology. I am worried
about the way the typical Ada programmer thinks about the
world. I know when moving to Java, one of the things I find
the most confusing is that references can come before definitions
to fields of a class. Yes, you can get used to it, but it is
most definitely not just a "terminology" issue. It is a completely
different mind set.
****************************************************************
From: Dan Eilers
Date: Tuesday, August 5, 2008 8:26 PM
> I know when moving to Java, one of the things I find the most
> confusing is that references can come before definitions to fields of a class.
I'm not sure if you were using this just as an example, or if you
understood my proposal to involve references before definitions.
I didn't intend it to.
I was proposing to change your example:
package Abc is
type T is private;
Null_T : constant T;
private
type T is new Integer;
Null_T : constant T := 0;
end private;
package Lists_Of_T is new Generic_Lists(T);
type T_List is new Lists_Of_T.List;
end ABC;
to:
package Abc2 is
private
type T is new Integer;
Null_T : constant T := 0;
end private;
type T is private; -- moved here
Null_T : constant T; -- moved here
package Lists_Of_T is new Generic_Lists(T);
type T_List is new Lists_Of_T.List;
end ABC2;
where everything is elaborated in the order declared, and nothing is
referenced before being fully defined.
Yes, there is somewhat of a shift in mind set because the reader sees
the full definition of type T before they see that T is exported.
But that doesn't seem too different from the way pragma export makes
something externally visible after its full definition is seen.
btw, I'm much less enthusiastic about this option now that Randy has
shown a realistic case where this still wouldn't be adequate.
****************************************************************
From: Tucker Taft
Date: Wednesday, August 6, 2008 4:18 PM
Thanks for elaborating. I understand what you are trying to accomplish.
It is somewhat analogous to the way Modula 3 works, where you partially
"reveal" properties of a type in a spec, separately from an implementation
where you define the type. It could be an elegant model. However, I
see it as too big a shift for Ada.
I am trying to solve a problem I have run into
*many* times with minimal disruption to the Ada private-type model.
You are suggesting an approach that would really make more sense in
a completely new language. One of Bob Duff's ideas (if I remember it
correctly) was to eliminate all elaboration from specs, and thereby
eliminate all linear-elaboration order restrictions from them.
I think that has some real appeal, but it ain't Ada.
Bob might want to "elaborate" further... ;-)
****************************************************************
From: Dan Eilers
Date: Wednesday, August 6, 2008 11:24 AM
> I tend to agree. We all know the rule about having zero, one, or many
> of something. Clearly, this proposal as written fails that rule. One
> reason that I'm not very excited about this proposal is the feeling
> that it eventually will morph into an unlimited number of such parts,
> with the loss of readability (and implementation complexity) that that entails.
Maybe having an unlimited number isn't all that bad.
We already allow an unlimited number of private regions inside a
package spec, since we allow nested package specs, each with its own
private part. And similarly within the statement_list of a subprogram
we allow an unlimited number of declare blocks to add more declarations.
We could use the "declare" keyword in a private part to mean we are
restarting the visible region, which would avoid the issue of whether
or not to put a semicolon after the somewhat awkward "end private".
From a user's perspective, I think adding the ability to restart the
visible region is conceptually simpler than adding a new kind of
non-freezing instantiation. It may also be simpler to describe in the RM
and simpler to implement.
****************************************************************
From: Tucker Taft
Date: Thursday, August 7, 2008 5:57 AM
The proposal explained why it only allows having one private part.
I don't see that changing, and I would personally resist it.
****************************************************************
From: Dan Eilers
Date: Thursday, August 7, 2008 12:40 PM
I can live with that, but I don't find the argument compelling, and
it goes against the history of programming language development.
In Pascal, Wirth thought it would be adequate to have a region of
constants, followed by a region of type declarations, followed by a
region of variable declarations, followed by a region of subprogram
declarations. This was probably inspired by Cobol's ordering of
Identification Division, Environment Division, Data Division, and
Procedure Division.
This was found to be inadequate, and Ada83 did away with Const regions,
Type regions, and Var regions, allowing individual declarations to be freely
interspersed, but kept the restriction that basic_declarative_items
must come before later_declarative_items.
This was also found to be inadequate, and Ada95 eliminated this
restriction, allowing interspersing individual variable declarations with
body declarations, but kept the restriction that in package specs,
visible declarations must come before private declarations.
This was also found to be inadequate.
In each case the argument was made that the language is easier for a
human to read and easier for a compiler to process if ordering restrictions
are enforced, and textbook examples work perfectly well with such
restrictions. But real-world programs, including programs automatically
translated from other languages, eventually run into difficulties.
The proposal makes the argument that if you allow more than one private part,
then you need to know which private types are completed in which private
part. This isn't compelling because simply requiring a private type or
deferred constant to be completed in the immediately following private part
would satisfy this objection.
****************************************************************
From: Randy Brukardt
Date: Thursday, August 7, 2008 1:23 PM
I suspect that it would not be too hard to construct an example where that
would be too limiting also.
The real question is whether the added capabilities by allowing
multiple parts are really worthwhile enough to support.
My guess is that (presuming we adopt this proposal at all) we'll start with
one extra part, and then sometime down the road expand it to include more.
But it is important to remember that at some point, a designer really does
need to split this stuff into separate packages: a package shouldn't contain
bunches of loosely related types. "private with" and "limited with" were
added specifically to make having many packages easier. So it not that
clear that we really need to handle packages with many private types (with
the corresponding need for many private parts).
****************************************************************
From: Tucker Taft
Date: Thursday, August 7, 2008 8:01 PM
I think there is something elegant about sandwiching a private part in between
two visible parts. When you start to have a random alternation between visible
and private, you have a jumble rather than an "abstraction."
Remember we are talking here about a "specification"
not the implementation.
I understand your point about the Pascal restriction which required grouping
constants, variables, etc., which proved to be undesirable, but I think that
was more because Ada started supporting arbitrary computation in the
initialization expressions of variables and constants. On the other hand,
I much prefer Ada's rules requiring a specific ordering of reserved words
when you have a sequence. I find the looseness of C and C++ not at all
helpful in terms of where "const," "static," etc., may appear within a
declaration. It makes it much harder to read other people's code.
****************************************************************
From: Randy Brukardt
Date: Thursday, August 7, 2008 8:34 PM
You have a strange idea about "elegant". I think Bob has complained that the
private part is a hack, and there surely is some sense to that view. This
second visible part is then just doubling the hack, it's surely not elegant.
But you seem to have a very different view about the meaning of the private part
than I do. When you start indenting it further than the rest of the package,
effectively treating it as some nested thing, you've lost me completely. I see
it as *only* about visibility; otherwise, it is just a normal part of the
package. Extra indentation means to me extra naming is needed (I almost never
use use clauses), and that is not true for private parts. This apparent
different view worries me, because it may mean that we aren't on the same
page about this very basic feature. And that will make it harder to agree
on how to change it (if at all).
It's pretty hard for anything that doesn't meet the zero-one-many rule to be
elegant. I'd be happy to call the trailing visible part idea "clever", as I
haven't been able to poke major holes in it, and it seems to solve a number
of problems without too much disruption to the language semantics. (I very
much doubt that compilers will be as lucky, but that's a different issue.)
But not elegant.
****************************************************************
From: Dan Eilers
Date: Friday, August 8, 2008 1:15 PM
> I think there is something elegant about sandwiching a private part in
> between two visible parts. When you start to have a random
> alternation between visible and private, you have a jumble rather than
> an "abstraction."
> Remember we are talking here about a "specification"
> not the implementation.
I disagree that this proposed solution is particularly elegant, although
it's a reasonable engineering compromise.
Elegance requires consistency with high-level language goals.
As you note, the purpose of a package spec is to define the visible part
of an abstraction. Any mixing of irrelevant private details within that
spec is inelegant. The current two-part solution is somewhat elegant
in that it allows the reader to stop reading when they get to the
private keyword, but the proposed three-part solution loses that elegance.
Elegance requires consistency with other parts of the language.
As Randy notes, the proposed three-part solution invites confusion as to
whether the private part is considered to be a nested structure, or a
toggled region at the same level. If it is to be considered a nested
structure, then this is a shift in mindset, and we should require
"end private;" even when there is no trailing visible part, because in
Ada, "end" is never optional. If it is to be considered a toggled region,
then the keyword "end" shouldn't be used, because the focus should be
on beginning the next region rather than ending the previous region.
To be consistent, you'd have to change the syntax of If statements to
use "end then;" instead of "else".
Elegance requires minimal restructuring from a user's original attempt.
But each of the examples in the proposal had to be restructured in order
to work with the proposal.
In the example:
package p is
type T is private;
x: T; -- obscure error message here about freezing rules
private
type T is new integer;
end p;
Instead of requiring the user to restructure the spec into three parts,
losing the coherence of the visible part, an elegant solution might say
that the declaration of x freezes T, and it is illegal for the full type
of T to directly or indirectly depend on x. A slightly less elegant
solution might use a pragma to explicitly freeze T before the declaration
of x, but with no restructuring.
True elegance requires completeness. Newton's theory of gravity was
elegant in unifying falling apples with orbiting celestial bodies, and
served to land men on the moon. But it failed to account for some pesky
corner cases, and had to be superseded for GPS to work right, among other
things. The three-part package solution similarly works OK for packages
with a single private type, but falls down in corner cases, such as when
a private type depends on a visible type which depends on a second private
type. I think Einstein's famous rule "as simple as possible, but no simpler"
applies.
****************************************************************
From: Robert Dewar
Date: Friday, August 8, 2008 1:32 PM
> I think there is something elegant about sandwiching a private part
> in between two visible parts. When you start to have a random
> alternation between visible and private, you have a jumble rather
> than an "abstraction." Remember we are talking here about a
> "specification" not the implementation.
To me the private part should be in a separate file anyway, having
it in any sense as part of the visible spec is junk.
Now with the current definition of Ada, you can indeed put the
private part in a separate file (that's on the list of GNAT
enhancements to do some time), and we helped enable this with the
private with.
I dislike going backwards and forwards because it makes this separation
more difficult to understand and describe.
Right now, the proper way a client reads a spec is to quit reading
when you see private. It's like a sign on the door that says
"authorized personel only".
If we switch backwards and forwards it's more like a sign on the door
that says
Non-authorized personel are permitted entry solely to see if there
is a door further on that has unrestricted entry.
You may not look at anything else other than such a door if you enter
here.
:-(
> Elegance requires consistency with high-level language goals.
> As you note, the purpose of a package spec is to define the visible
> part of an abstraction. Any mixing of irrelevant private details
> within that spec is inelegant. The current two-part solution is
> somewhat elegant in that it allows the reader to stop reading when
> they get to the private keyword, but the proposed three-part solution
> loses that elegance.
indeed, see above
Can't we find a more elegant solution here that does not destroy the
entire structure of private parts?
****************************************************************
From: Randy Brukardt
Date: Saturday, August 9, 2008 1:28 AM
> Can't we find a more elegant solution here that does not destroy the
> entire structure of private parts?
Well, we've been trying for quite a while now, and we aren't any closer
to an ideal solution.
A limited instantiation (or partial instantiation) would work (see
AI05-0074-1), but hardly anyone would call it elegant. And it's hard
to describe in language terms (because the way it looks inside and
outside of the package are very different, and the client sees more)
and probably not obvious to users.
Just allowing the instantiation to work without freezing would require
lots of changes to the freezing rules and also would require changing
the way (some) instantiations elaborate. Besides the massive impact
on implementations, it would effectively drop the linear elaboration
model. Some proposals put the elaboration at some later freezing point,
which seemed like a maintenance hazard and something only rare users
could figure out. (Most users only know about the freezing rules when
they get a compiler error message, and it doesn't seem good to change
that.)
The only other idea floated was a way to make a child package look
(naming-wise) like it is part of its parent (and as such, it would
automatically come along when the parent is withed). But that makes
a pretty complex structure and doesn't help if the instance exports
types that need to be used in primitives of the parent package.
So far, the most elegant idea has been to not bother solving the
problem. :-) But it seems to be an impediment to using the containers,
so that's not ideal, either.
We all would love to hear an elegant idea for solving this problem
-- please feel free to suggest one!
****************************************************************
From: Steve Baird
Date: Friday, August 8, 2008 3:39 PM
How does the "end private" proposal cope with declarations in the
post-private part which conflict in some sense with declarations
in the private part?
Perhaps the simplest case is
package Pkg is
private
X : Integer;
end private
X : Float;
end Pkg;
8.3(26/2) states
A non-overridable declaration is illegal if there is a homograph
occurring immediately within the same declarative region that is
visible at the place of the declaration, and ... .
This does not seem to apply in this case. Neither X is visible
at the place of the declaration of the other.
There are similar questions about the overriding relationships
between declarations in the private part and the post-private part
of a package.
Given
package Pkg2 is
type Has_No_Foo is tagged null record;
type Has_Foo is new Has_No_Foo with null record;
procedure Foo (X : Has_Foo);
type Two_Part is new Has_No_Foo with private;
private
type Two_Part is new Has_Foo with null record;
end private
procedure Foo (X : Two_Part);
end Pkg2;
does the (second) explicit declaration of Foo override the implicit
declaration of Foo that occurs at the point of the completion of the
type Two_Part?
****************************************************************
From: Tucker Taft
Date: Saturday, August 9, 2008 10:01 AM
...
> This does not seem to apply in this case. Neither X is visible at the
> place of the declaration of the other.
Clearly this must be illegal. There are special words to make disallow
child units and subunits from having the same name, and perhaps this
might need some of the same words. Clearly it should always be illegal
to have two distinct homographs declared immediately in the same
declarative region if there is a place where they might both be visible.
I guess I had presumed there were already enough rules to ensure that,
but as you point out, at least the one above doesn't quite accomplish
the goal.
> There are similar questions about the overriding relationships between
> declarations in the private part and the post-private part of a
> package.
>
> Given
>
> package Pkg2 is
> type Has_No_Foo is tagged null record;
> type Has_Foo is new Has_No_Foo with null record;
> procedure Foo (X : Has_Foo);
>
> type Two_Part is new Has_No_Foo with private;
> private
> type Two_Part is new Has_Foo with null record;
> end private
> procedure Foo (X : Two_Part);
> end Pkg2;
>
> does the (second) explicit declaration of Foo override the implicit
> declaration of Foo that occurs at the point of the completion of the
> type Two_Part?
Explicit declarations always override inherited subprograms, no matter
which one comes first. I would expect the existing rules already cover
this situation, though as in the case above, the existing rules might
need more tweaking.
****************************************************************
From: Randy Brukardt
Date: Saturday, August 9, 2008 7:31 PM
> Explicit declarations always override inherited subprograms, no matter
> which one comes first. I would expect the existing rules already
> cover this situation, though as in the case above, the existing rules
> might need more tweaking.
It took me a while to figure this out from the wording, but I think
the existing wording is OK. This is another area where one's head tends to
feel like it is about to explode. :-)
Anyway, I was worried about anomolies, but so far as I can tell, they all
already exist (because of "additional characteristics") so it probably
doesn't matter. At least, I'll let Steve figure that out and let my head
deflate.
****************************************************************
From: Steve Baird
Date: Monday, August 11, 2008 11:49 AM
...
>> does the (second) explicit declaration of Foo override the implicit
>> declaration of Foo that occurs at the point of the completion of the
>> type Two_Part?
> Explicit declarations always override inherited subprograms, no matter
> which one comes first.
I agree with you, but overriding something that you aren't allowed to see
seems odd.
This becomes clearer if we add an overriding indicator to the final
explicit declaration of Foo:
overriding procedure Foo (X : Two_Part);
Now the legality of this declaration depends on the contents of a
private part that it is not supposed to be able to see into.
This is not unprecedented (rep clause checking, detection of recursive
types), but it is not the sort of thing we want to encourage.
I suppose you could tweak the rules for overriding indicators to somehow
disallow the indicator in this case (while continuing to disallow a
"not overriding" indicator).
On the other hand, one could argue that we aren't really doing serious
damage to the privacy model here because this is all within one package
spec.
What do you think?
I realize that this may be a case of prematurely diving into the low-level
details, but I think it is useful to point out that this proposal may turn
out to be more complicated than it seems at first.
****************************************************************
From: Tucker Taft
Date: Tuesday, August 12, 2008 5:49 AM
...
> I agree with you, but overriding something that you aren't allowed to
> see seems odd.
The overriding is only relevant at a place where both are visible, so
I don't see that there is an anomaly.
> This becomes clearer if we add an overriding indicator to the final
> explicit declaration of Foo:
>
> overriding procedure Foo (X : Two_Part);
>
> Now the legality of this declaration depends on the contents of a
> private part that it is not supposed to be able to see into.
> This is not unprecedented (rep clause checking, detection of recursive
> types), but it is not the sort of thing we want to encourage.
Isn't this more similar to the case where you have a private extension,
and you override an operation that is *not* inherited from the ancestor
of the private extension, but *is* inherited from the actual parent
subtype specified on the full record extension?
Using this as a model, then the overriding indicator would *not* be
permitted. However, similarly, "not overriding"
would not be permitted, because eventually the declaration
*is* overriding (in the body).
> I suppose you could tweak the rules for overriding indicators to
> somehow disallow the indicator in this case (while continuing to
> disallow a "not overriding" indicator).
I don't see the "tweak" needed, presuming it doesn't override until
you get to a point where they both are visible.
> On the other hand, one could argue that we aren't really doing serious
> damage to the privacy model here because this is all within one
> package spec.
>
> What do you think?
I don't think the overriding would be relevant until the body, which
is why neither "overriding" nor "not overriding" would be legal.
> I realize that this may be a case of prematurely diving into the
> low-level details, but I think it is useful to point out that this
> proposal may turn out to be more complicated than it seems at first.
This one doesn't seem to be a problem to me, based on the current wording.
The other problem you mentioned, where a declaration should be illegal
because it is a homograph of a non-overridable declaration, does seem
to need some kind of wording fix.
Paragraph 8.3(26/2) starts:
A non-overridable declaration is illegal if there is a homograph
occurring immediately within the same declarative region that is
visible at the place of the declaration, and is not hidden from
all visibility by the non-overridable declaration...
We could either add a special case for this new case, or modify the above
wording as follows:
A non-overridable declaration is illegal if there is a homograph
occurring immediately within the same declarative region that is
visible at the place of the declaration{, or becomes visible
somewhere later within its scope}, and is not hidden from
all visibility by the non-overridable declaration...
****************************************************************
From: Robert A. Duff
Date: Tuesday, August 12, 2008 11:21 AM
> I agree with you, but overriding something that you aren't allowed to
> see seems odd.
>
> This becomes clearer if we add an overriding indicator to the final
> explicit declaration of Foo:
>
> overriding procedure Foo (X : Two_Part);
>
> Now the legality of this declaration depends on the contents of a
> private part that it is not supposed to be able to see into.
I think privacy breakage within a single package is not really privacy
breakage. Breaking privacy means that _client_ legality depends on
private parts of with-ed packages.
****************************************************************
From: Steve Baird
Date: Tuesday, August 12, 2008 4:23 PM
Given:
package Pkg2 is
type Has_No_Foo is tagged null record;
type Has_Foo is new Has_No_Foo with null record;
procedure Foo (X : Has_Foo);
type Two_Part is new Has_No_Foo with private;
private
type Two_Part is new Has_Foo with null record;
end private
overriding procedure Foo (X : Two_Part); -- legal?
end Pkg2;
Steve said:
> I suppose you could tweak the rules for overriding indicators to
> somehow disallow the indicator in this case (while continuing to
> disallow a "not overriding" indicator).
Tuck said:
> I don't see the "tweak" needed, presuming it doesn't override until
> you get to a point where they both are visible.
8.3.1(5/2) says
"If ..., then the operation shall override a homograph at
the place of the declaration ..."
Section 8.3 gives clear rules about whether a given declaration overrides
another declaration, but it is less clear in defining the point at
which the overriding occurs. One definition would be "at the second
of the two declarations". Another would be "at the earliest point where
both of the two declarations are directly visible" (I think we both favor
this choice). Without post-private parts, these two definitions are
equivalent. With post-private parts, I think that some form of
clarification (perhaps just an AARM note) would be needed.
I'd certainly concede that this is a minor point.
****************************************************************
From: Robert A. Duff
Date: Tuesday, August 12, 2008 11:35 AM
> I am trying to solve a problem I have run into
> *many* times with minimal disruption to the Ada private-type model.
> You are suggesting an approach that would really make more sense in a
> completely new language. One of Bob Duff's ideas (if I remember it
> correctly) was to eliminate all elaboration from specs, and thereby
> eliminate all linear-elaboration order restrictions from them.
> I think that has some real appeal, but it ain't Ada.
My idea has all kinds of nice properties:
- Preserves linear elaboration model.
- Eliminates the need for freezing rules (which no programmer understands!)
and access-before-elab run-time checks.
- Less restrictive. E.g. it is possible to do something like:
package Big_Nums is
type Big_Num is private;
function Value (S : String) return Big_Num;
Zero : constant Big_Num := Value ("0");
which doesn't work in Ada. Same issue as the instantiations we're
talking about here.
- A module _spec_ can import its own children, without the requirement for
pointers we have for "limited with".
- Checks all the rules at compile time, except in certain cases where
link-time checks are required. ("Certain cases" = cyclic dependences
among modules.)
- Elab order is fully portable.
- Elab order can be calculated "locally" -- it depends only on the
dependences between siblings (as opposed to Ada, where adding a
long-distance with clause can require recalculating the order for the
whole program). This makes it more incremental.
- When reading the body, you can tell which declarations are public without
switching over to look at the spec.
But...
> Bob might want to "elaborate" further... ;-)
I'm not sure I see the point, since as you say, "it ain't Ada".
I doubt my ideas can be inserted into Ada without major incompatible
changes to Ada.
****************************************************************
From: Randy Brukardt
Date: Wednesday, August 13, 2008 2:13 PM
Bob Duff writes:
...
Seems like all goodness here. What's the catch? There must be one,
especially as no details are given as to how you accomplish the above.
One obvious question, given the example above, when does the elaboration
(initialization if you prefer) of Zero occur? How do you prevent
dependence on uninitialized things (that is, things that were not
elaborated yet)? (Imagine that Value depends on a local object to
the body of Big_Nums, perhaps a performance counter.)
It's well known that the run-time elaboration checks of Ada aren't
really needed (if you are willing to take a few more restrictions),
but it isn't at all obvious how to avoid the other limitations of
the linear elaboration model without adding erroneousness.
****************************************************************
From: Robert A. Duff
Date: Wednesday, August 13, 2008 3:39 PM
> Seems like all goodness here.
:-)
>...What's the catch?
The catch is that it ain't Ada. ;-)
>... There must be one,
> especially as no details are given as to how you accomplish the above.
I'm not sure I want to go into full detail, since I don't think
these ideas are relevant to Ada. Maybe later, especially if you
goad me some more... ;-)
> One obvious question, given the example above, when does the
> elaboration (initialization if you prefer) of Zero occur?
All elaboration happens in the body. Specs are not elaborated. Specs
are a purely compile time thing, with no presence whatsoever at run time.
Specs are a description of what's exported by the module. Specs are all
about visibility, and have nothing to do with elaboration/initialization.
One of the "details" I didn't mention is that everything exported has to
have two decls -- one in the spec, one in the body. Most things work this
way in Ada (since most exported things are subprograms). In my language,
you have:
Zero : Big_Num := Value ("0"); -- constant is the default in my language!
in the spec, and:
public Zero : Big_Num := public Value ("0");
in the body, and this body decl is elaborated where it occurs.
The first "public" indicates that the name Zero is exported, and the
second "public" indicates that the initial value is also exported. If
you wanted something like a deferred constant in Ada, it would be:
Zero : Big_Num;
in the spec, and:
public Zero : Big_Num := Value ("0");
in the body.
>... How do you prevent dependence
> on uninitialized things (that is, things that were not elaborated yet)?
> (Imagine that Value depends on a local object to the body of Big_Nums,
>perhaps a performance counter.)
The body-decl of Zero must occur after the body-decl of Value, which must
occur after the local object you mentioned.
These things are elaborated (only in the body!) in that order.
> It's well known that the run-time elaboration checks of Ada aren't
> really needed (if you are willing to take a few more restrictions),
> but it isn't at all obvious how to avoid the other limitations of the
> linear elaboration model without adding erroneousness.
****************************************************************
From: Tucker Taft
Date: Wednesday, August 13, 2008 6:56 PM
Modula-3 talks about "revelations" rather than "declarations" I believe.
It is a somewhat similar idea. The module "spec" merely "reveals" some
parts of the module. It doesn't by itself declare or elaborate anything.
****************************************************************
From: Robert A. Duff
Date: Wednesday, August 13, 2008 11:21 PM
> One of the "details" I didn't mention is that everything exported has
> to have two decls -- one in the spec, one in the body. Most things
> work this way in Ada (since most exported things are subprograms). In
> my language, you have:
...
I see. It's the second declaration that is the catch, and that's why
it's not Ada (and can't be in any practical sense).
I'm not sure there is any real need for "public" in these declarations
(if you want to see what's exported, look at the spec!)
Anyway, thanks for enlightening us. Back to the Ada salt mines. ;-)
****************************************************************
From: Jean-Pierre Rosen
Date: Friday, August 8, 2008 2:39 AM
Here is a possible alternative to "end private". If it raises sufficient
interest, I'll write up an AI...
During discussions, it struck me that all this could be accomplished
with subpackage. I.e. Randy's example could be written as:
with Gen;
package Abc is
package Inner is
type X is private;
procedure Foo(X : Integer);
private
type X is record null; end record;
end Inner;
use Inner;
package P is new Gen(X);
procedure Foo(P : X) renames P.Fum;
end Abc;
But of course, we don't want to impose the burden of adding the
extra "Inner." to all users. So my proposal is to allow "anonymous
packages":
with Gen;
package Abc is
package is
type X is private;
procedure Foo(X : Integer);
private
type X is record null; end record;
end;
package P is new Gen(X);
procedure Foo(P : X) renames P.Fum;
end Abc;
Everything declared in an anonymous package would belong to
the scope of the enclosing construct (so, no use clause needed).
Except for that, all the rules would be the same as for regular
packages.
Benefits:
- All rules are defined by the current rules => simpler model
- At first sight (pending more discussion), we could allow multiple
anonymous packages, maybe nested anonymous packages. Note that the
same identifier could not be defined in two different anonymous
packages, since they both belong to the enclosing scope.
Drawbacks:
- Syntactically slightly more complicated than "end private"
- If multiple anonymous packages are allowed, there is an issue with
the package bodies. How do we recognize them? I see at least three
possibilities:
1) (anonymous) package bodies must be in the same textual order as
the specifications
2) provide only one body for all specs
3) provide no package body at all. The completions would be directly
part of the enclosing construct (consistent with the fact that
entities declared in anonymous packages are, in effect, part of
this enclosing construct).
We might well discover other benefits of anonymous packages: for
example, for grouping a tagged type and its primitive operations, making
like a "class" construct.
OK, just let me the time to put my asbestos suite on...
****************************************************************
From: Robert Dewar
Date: Sunday, August 10, 2008 11:09 AM
Well I can't see that the second example here is enough of a gain on
the first one to justify adding this mechanism, what's the big deal
in choosing a name (almost always useful anyway) and adding a use clause?
****************************************************************
From: Robert A. Duff
Date: Tuesday, August 12, 2008 11:17 AM
The difference is in the client view. In the first example, the
client has to say both "with Abc" and "with Abc.Inner", and then
either "Abc.Inner.Whatever", or "use Abc.Inner". The second example
makes it as if the stuff in the inner package is declared immediately
within Abc. In other words, it allows the existence of the inner
package to be an invisible implementation detail.
This idea reminds me of another one, which I think we discussed a
while back:
Have a way to import the entire content of a package (nested or not)
into another package, making it as if all those declarations appear
in the second package. This allows to present to clients a single
package as an interface, while splitting it up into several for
convenience of implementation.
****************************************************************
From: Jean-Pierre Rosen
Date: Monday, August 11, 2008 3:06 AM
> Well I can't see that the second example here is enough of a gain on
> the first one to justify adding this mechanism, what's the big deal in
> choosing a name (almost always useful anyway) and adding a use clause?
Actually, I think one of the early proposals was to define a package
that would automatically be "used". I don't remember why the proposal
was withdrawn, but probably under pressure of the gang of anti-users...
I agree with you, but it's a different issue: is it broken enough to
justify the "end private"?
****************************************************************
From: Robert A. Duff
Date: Tuesday, August 12, 2008 3:45 PM
I don't know, but the "signature generic" idea that Tuck trots out now
and then is a pretty compelling example. Maybe we all need some more
examples of such, to convince us of the value. This is related to
Ada.Containers.
****************************************************************
From: Jean-Pierre Rosen
Date: Wednesday, August 13, 2008 12:37 AM
Yes, sure, but is there an example that cannot be solved with nested
packages? If not, it turns the issue from "impossibility" to
"inconvenience".
BTW, my proposal is not that different. If you read "package is" as
"public", "private" as "private" :-) and "end" as "end private",
it is pretty much equivalent to Tuck's proposal. But the idea is
that by basing it on an existing notion, it would make it easier to
answer many questions simply by saying: "just as for a regular
subpackage".
****************************************************************
From: Randy Brukardt
Date: Wednesday, August 13, 2008 3:17 PM
> Yes, sure, but is there an example that cannot be solved with nested
> packages? If not, it turns the issue from "impossibility" to
> "inconvenience".
There are such examples, because nested packages don't allow the declaration of primitive operations of the outer type. But "public" doesn't work for that, either.
Consider an example like (based on a Claw abstraction):
package Menus is
type Menu_Item_Type is tagged private;
type Menu_Item_List_Type is array (Positive range <>) of Menu_Item;
procedure Make_Submenu (Menu_Item : in out Menu_Item_Type;
New_Submenu : in Menu_Item_List_Type);
-- Make Menu_Item into a submenu with the given items.
function Get_Submenu (Menu_Item : in Menu_Item_Type) return
Menu_Item_List_Type;
-- Many other operations here.
private
type Menu_Item_Type is ...;
end Menus;
Make_Submenu and Get_Submenu are primitive, dispatching operations of Menu_Item_Type.
Now, imagine that you want to have a version of Make_Submenu for vectors of menu items. (This seems a very reasonable thing to want to do, especially given the difficulty of handling arrays with unknown bounds.)
The basic conversion would look like:
with Ada.Containers.Vectors;
package Menus is
type Menu_Item_Type is tagged private;
package Menu_Vector is new Ada.Containers.Vectors (Menu_Item_Type);
-- Illegal!
procedure Make_Submenu (Menu_Item : in out Menu_Item_Type;
New_Submenu : in Menu_Vector.Vector'Class);
-- Make Menu_Item into a submenu with the given items.
function Get_Submenu (Menu_Item : in Menu_Item_Type) return
Menu_Vector.Vector'Class;
-- Many other operations here.
private
type Menu_Item_Type is ...;
end Menus;
but as noted, this is illegal because Menu_Item_Type is not completed at the point of the instantiation (and thus freezing it is illegal). [I added the 'Class because a Vector is a tagged type, and non-primitive parameters of a tagged specific type are dub
ious: they would not work with extensions and that defeats the purpose of taggedness.]
Trying Tucker's proposal:
with Ada.Containers.Vectors;
package Menus is
type Menu_Item_Type is tagged private;
private
type Menu_Item_Type is ...;
public
package Menu_Vector is new Ada.Containers.Vectors (Menu_Item_Type);
procedure Make_Submenu (Menu_Item : in out Menu_Item_Type;
New_Submenu : in Menu_Vector.Vector'Class);
-- Illegal!
-- Make Menu_Item into a submenu with the given items.
function Get_Submenu (Menu_Item : in Menu_Item_Type) return
Menu_Vector.Vector'Class; -- Illegal!
-- Many other operations here.
end Menus;
but now the primitive operations are illegal because you can't declare primitive operations for a type (Menu_Item_Type) after the type is frozen.
Tucker had proposed using renames-as-body to get around that, but that doesn't work here because we don’t have visibility on the type Menu_Vector.Vector before the instance.
There is also a complication if Menu_Item_Type needs an
access-to-Menu_Item_Vector: you'd need to use a Taft-incomplete type in the private part which would be completed in the body (probably with a subtype).
A named nested package doesn't work either: (I'm using a named one here so it is clear what is going on, but the extension to Jean-Pierre's proposal should be obvious.)
with Ada.Containers.Vectors;
package Menus is
package Inner is
type Menu_Item_Type is tagged private;
-- Many other operations here.
private
type Menu_Item_Type is ...;
end Inner;
package Menu_Vector is new Ada.Containers.Vectors (Inner.Menu_Item_Type);
procedure Make_Submenu (Menu_Item : in out Inner.Menu_Item_Type;
New_Submenu : in Menu_Vector.Vector);
-- Make Menu_Item into a submenu with the given items.
function Get_Submenu (Menu_Item : in Inner.Menu_Item_Type) return
Menu_Vector.Vector;
end Menus;
This works, but the routines Make_Submenu and Get_Submenu are no longer primitive. If you need them for dispatching purposes, you are sunk. Again, you also need a Taft-incomplete type in the private part.
At one point, Tucker had suggested making this Vector type private to get it
exported:
with Ada.Containers.Vectors;
package Menus is
type Menu_Item_Type is tagged private;
type Menu_Vector_Type is tagged private;
procedure Make_Submenu (Menu_Item : in out Menu_Item_Type;
New_Submenu : in Menu_Vector_Type'Class);
-- Make Menu_Item into a submenu with the given items.
function Get_Submenu (Menu_Item : in Menu_Item_Type) return
Menu_Vector_Type'Class;
-- Many other operations here.
private
type Menu_Item_Type is ...;
package Menu_Vector is new Ada.Containers.Vectors (Inner.Menu_Item_Type);
type Menu_Vector_Type is new Menu_Vector.Vector with null record;
end Menus;
[The 'Class is required here, else we'd have a primitive of two tagged types at once.] Now we have a legal spec., but we no longer have a visible vector type, so we'd have to manually export all of the vector operations. This does get rid of the Taft-incom
plete type, so it is not all bad.
I can't think of any way to make this work with the rules Tucker proposed.
However, we could make it work if we allowed a private type to be completed in the *second* visible part:
with Ada.Containers.Vectors;
package Menus is
type Menu_Item_Type is tagged private;
type Menu_Vector_Type is tagged private;
procedure Make_Submenu (Menu_Item : in out Menu_Item_Type;
New_Submenu : in Menu_Vector_Type'Class);
-- Make Menu_Item into a submenu with the given items.
function Get_Submenu (Menu_Item : in Menu_Item_Type) return
Menu_Vector_Type'Class;
-- Many other operations here.
private
type Menu_Item_Type is ...;
public
package Menu_Vector is new Ada.Containers.Vectors (Inner.Menu_Item_Type);
type Menu_Vector_Type is new Menu_Vector.Vector with null record;
end Menus;
Now we do have the right visibility on Menu_Vector_Type to know that it is a Vector. As an added bonus, the vector operations are inherited here, so they have the same visibility as the type Menu_Vector_Type.
This problem *can* be solved with my limited instance proposal (see
AI05-0074-1):
with Ada.Containers.Vectors;
package Menus is
type Menu_Item_Type is tagged private;
package Menu_Vector is limited new Ada.Containers.Vectors
(Menu_Item_Type);
procedure Make_Submenu (Menu_Item : in out Menu_Item_Type;
New_Submenu : in Menu_Vector.Vector'Class);
-- Make Menu_Item into a submenu with the given items.
procedure Get_Submenu (Menu_Item : in Menu_Item_Type;
Submenu : out Menu_Vector.Vector'Class);
-- Many other operations here.
private
type Menu_Item_Type is ...;
package Menu_Vector is new Ada.Containers.Vectors (Menu_Item_Type);
end Menus;
But note that we had to change Get_Submenu to a procedure in order to do it (parameters of tagged limited types are allowed, return values of them are not allowed). As an added bonus, no Taft-incomplete type is needed in the private part, as the instantiat
ion provides the needed limited view.
But I'm not pushing very hard for this solution, because the client view of the "limited" instance is *not* limited, and that's pretty weird (even though it works fine semantically). I haven't been able to think of syntax that makes that dichotomy clear, a
nd I think it needs to be fairly clear.
The question has to be whether this (and similar corner cases) are important enough to make a solution for. Most of the examples can be solved with nested or child packages; while that is not ideal, it may be enough.
****************************************************************
From: Robert A. Duff
Date: Wednesday, August 13, 2008 4:57 PM
> Now, imagine that you want to have a version of Make_Submenu for
> vectors of menu items. (This seems a very reasonable thing to want to
> do, especially given the difficulty of handling arrays with unknown
> bounds.)
Very reasonable indeed. I'd say that vectors should behave _exactly_
like arrays, except that vectors are growable. We're pretty far from that
goal, though.
> [The 'Class is required here, else we'd have a primitive of two tagged
> types at once.] Now we have a legal spec., but we no longer have a
> visible vector type, so we'd have to manually export all of the vector operations.
So what do you think of the idea I mentioned yesterday -- a syntactic
shorthand that means "rename all the stuff from one place into another".
****************************************************************
From: Randy Brukardt
Date: Wednesday, August 13, 2008 7:08 PM
...
> > Now, imagine that you want to have a version of Make_Submenu for
> > vectors of menu items. (This seems a very reasonable thing to want
> > to do, especially given the difficulty of handling arrays with
> > unknown bounds.)
>
> Very reasonable indeed. I'd say that vectors should behave _exactly_
> like arrays, except that vectors are growable.
> We're pretty far from that goal, though.
Well, I do think that user-defined indexing, dereferences, and literals
would surely be valuable additions to Ada. The only issue is whether
they can be done cheaply enough. (Note that we sort of have user-defined
selection via the prefixed view mechanism.)
> > [The 'Class is required here, else we'd have a primitive of two
> > tagged types at once.] Now we have a legal spec., but we no longer
> > have a visible vector type, so we'd have to manually export all of
> the vector operations.
>
> So what do you think of the idea I mentioned yesterday -- a syntactic
> shorthand that means "rename all the stuff from one place into
> another".
I don't think that would help this particular example; as I noted in
my original note, nested packages (or child packages) don't work. So
renaming the contents of such a unit don't help, either.
In the wider view, I'd be somewhat concerned about the name pollution
that would result (you'd need a number of nested or child packages to
create the small views that would then be combined into the usual
public face). I'd also worry somewhat that the source code wouldn't
reflect the actual declarations. We already have that problem with
inherited routines, and it can be quite severe: it's often necessary
to have some tool support to figure out what is actually declared
where. (Overriding indicators provide a [very] poor-mans way of
figuring that out.)
But I don't want to rule any idea out-of-hand, as nothing has worked
that well so far...
****************************************************************
From: Tucker Taft
Date: Wednesday, August 13, 2008 7:37 PM
...
> Yes, sure, but is there an example that cannot be solved with nested
> packages? If not, it turns the issue from "impossibility" to
> "inconvenience".
>
> BTW, my proposal is not that different. If you read "package is" as
> "public", "private" as "private" :-) and "end" as "end private", it is
> pretty much equivalent to Tuck's proposal. But the idea is that by
> basing it on an existing notion, it would make it easier to answer
> many questions simply by saying: "just as for a regular subpackage".
I believe that in several of the examples I gave, a generic
instantiation is followed by one or more type derivations used to
effectively rename the type into the outer scope, and to in some cases
complete an incomplete type.
I don't see how in a nested package you could complete an incomplete type
declared in the outer package.
****************************************************************
Questions? Ask the ACAA Technical Agent