Version 1.1 of acs/ac-00131.txt

Unformatted version of acs/ac-00131.txt version 1.1
Other versions for file acs/ac-00131.txt

!standard 3.11(13)          06-05-11 AC95-00131/01
!standard 10.2(
!class confirmation 06-05-11
!status received no action 06-05-11
!status received 06-04-25
!subject Elaboration checks and generic bodies
!summary
!appendix

!topic Elaboration checks and generic bodies
!reference 3.11(13), 10.2
!from Adam Beneschan 06-04-25
!discussion


I usually don't pay much attention to elaboration issues, but I ran
across a situation that required me to study the elaboration rules
closely; and when I did, I found some things that surprised me.

What surprised me was that if you write the following package:

    package Pak1 is
        procedure Dump (X : Integer);
    end Pak1;

    with Ada.Text_IO;
    package body Pak1 is
        package IIO is new Ada.Text_IO.Integer_IO (Integer);
        procedure Dump (X : Integer) is
        begin
            Ada.Text_IO.Put ("Value is ");
            IIO.Put (X);
            Ada.Text_IO.New_Line;
        end Dump;
    end Pak1;

your program could raise Program_Error (before the main subprogram is
called) because of the generic instantiation.

As always, there could be a rule in the RM that I've missed, or
misinterpreted.  But here's what it looks like the rules say:

Pak1's body semantically depends on Ada.Text_IO (10.1.1(26)).  10.2(9)
says that Pak1's body therefore has an elaboration dependence on
Ada.Text_IO.  However, the wording of this paragraph makes it clear
that this does not imply an elaboration dependence on Ada.Text_IO's
body.

10.2 allows the Environment_Task to declare library items in any order
such that the rules in 10.2(13-17) are followed (assuming there is a
possible order).  An order in which Ada.Text_IO's body is declared
after Pak1's body is consistent with these rules, since Ada.Text_IO
does not have an Elaborate_Body pragma (A.10.1).

However, if the library items are generated in such an order, then
when Pak1's body is elaborated, which causes the instantiation IIO to
be elaborated, it fails the check in 3.11(13), since Ada.Text_IO's
body and thus the body of Integer_IO have not yet been elaborated.

Thus, it seems that any library unit which instantiates one of the
generics in Ada.Text_IO, or indeed any language-defined generic
(whether a library unit or not) that has a body, has to name the
language-defined library unit in an Elaborate pragma to avoid a
possible Program_Error.  In fact, it appears that any time I declare
my own generic that has a body, I either need to say "pragma
Elaborate_Body" in the library unit that contains the generic (or the
generic itself, if the library unit is generic), or "pragma Elaborate"
on any library unit that contains an instance of the generic.

Questions (assuming my analysis is correct):

(1) Is this intended that things work this way?

(2) Is this just something I should have known?  I.e. is it common
    knowledge that an Elaborate and/or Elaborate_Body is necessary in
    such cases?  I found this to be quite a surprise, and I suspect
    most other Ada programmers would be surprised also; but it's
    certainly possible that I've missed something that Ada programmers
    are expected to know.

(3) Is the intent just to assume that compilers will do the "right"
    thing?  That is, even though it would be legal to elaborate
    Ada.Text_IO's body after Pak1's body, are we just hoping that
    won't happen?

(4) Or is this a hole in the language that needs addressing, somehow?

****************************************************************

From: Tucker Taft
Date: Tuesday, April 25, 2006  9:43 PM

I would consider it "well known" that you need
to pragma Elaborate[_All] a generic unit if you instantiate
it at the library level.  But if you didn't know it,
then I am presumably wrong!

****************************************************************

From: Adam Beneschan
Date: Wednesday, April 26, 2006  10:59 AM

Well, I wouldn't hold myself out as an example, since (if I recall
correctly) I did not learn Ada by taking any classes or reading any
sort of textbook on the language.  I pretty much learned by reading
the Ada 83 manual, looking at other programmers' code, and picking up
snippets of information from here and there, and other than that I
just drew on my knowledge of USCD Pascal.  I have no idea whether the
need for using Elaborate is taught in books or classes when they teach
about generics.

But I did a quick check of some publicly available Ada libraries.
OpenToken has, in the OpenToken package spec, a library-level instance
of Ada.Strings.Bounded.Generic_Bounded_Length, but does not say
"pragma Elaborate(Ada.Strings.Bounded)".  [And the definition of
Ada.Strings.Bounded in A.4.4 doesn't have an Elaborate_Body pragma.]
SAL has, in the System_Storage_Elements_Text_IO spec, library-level
instances of both Ada.Text_IO.Modular_IO and Ada.Text_IO.Integer_IO,
but no "pragma Elaborate(Ada.Text_IO)".  The programmers of those
libraries know their way around Ada pretty well.  This is probably
better evidence that this requirement isn't well known than my own
ignorance.

It seems like a problem, at least in theory, that this is working only
because most (or all) Ada compilers out there happen to do the right
thing.  In theory, someone's next version of the compiler could decide
to use a different elaboration order that is perfectly legal according
to the Ada rules but is going to give a lot of programmers a nasty
surprise when their programs start getting Program_Errors, if all
those programmers are as ignorant as I am.

****************************************************************

From: Randy Brukardt
Date: Wednesday, April 26, 2006  1:52 PM

...
> It seems like a problem, at least in theory, that this is working only
> because most (or all) Ada compilers out there happen to do the right
> thing.  In theory, someone's next version of the compiler could decide
> to use a different elaboration order that is perfectly legal according
> to the Ada rules but is going to give a lot of programmers a nasty
> surprise when their programs start getting Program_Errors, if all
> those programmers are as ignorant as I am.

That's true in theory, but as we all know, there is a lot that goes into
making a "good" Ada compiler that does not have anything to do with the
standard. Certainly, picking a "friendly" elaboration order is part of this.

I'm also dubious of your examples. Most of the code on the net is written
for GNAT, which has it's own static elaboration model (that isn't quite the
same as that of the Standard). Thus, it's quite possible that the authors
have never run into real elaboration problems and are not aware of them.
Perhaps if the authors used -gnatE more often, there would be more
elaboration pragmas.

In any case, a compiler that chose a *bad* elaboration order (especially for
language-defined packages) would have a lot of trouble with existing code,
and most likely would change it quickly. I see this as no different than a
compiler which has a line length of 20 characters. That would be a
legitimate choice, but not one that would be usable in practice. The
Standard is not in the business of defining what a "good" compiler does.

****************************************************************

From: Matthew Heaney
Date: Wednesday, April 26, 2006  12:04 PM

> I have no idea whether the
> need for using Elaborate is taught in books or classes when they teach
> about generics.

If you instantiate a package at library level, you probably need the
pragma.

> But I did a quick check of some publicly available Ada libraries.

Check out Charles or the GNAT implementation of Ada.Containers.*.
You'll find pragma Elaborate_All used at every point where an internal
generic package is instantiated in a container package body.

> This is probably
> better evidence that this requirement isn't well known than my own
> ignorance.

It is indeed not well known.  What I know I learned from Bob Duff via
comp.lang.ada (see below), not by reading books, which often give short
shrift to this topic.

> It seems like a problem, at least in theory, that this is working only
> because most (or all) Ada compilers out there happen to do the right
> thing.  In theory, someone's next version of the compiler could decide
> to use a different elaboration order that is perfectly legal according
> to the Ada rules but is going to give a lot of programmers a nasty
> surprise when their programs start getting Program_Errors, if all
> those programmers are as ignorant as I am.

Correct.  The package elaboration rules must be fully inculcated.

There's a very helpful chapter in one of the GNAT docs (it's either the
users' manual or the reference manual, I can never remember which...),
written by Robert Dewar to foster awareness of these issues.

There have been many threads on comp.lang.ada on the topic of necessary
and proper use of elaboration pragmas, many of them me asking questions
of Bob Duff.  Here's one example from July 2003:

http://groups.google.com/group/comp.lang.ada/browse_frm/thread/b1264e586250f470/542c09f90f3ab15d?lnk=st&q=pragma+elaborate&rnum=3#542c09f90f3ab15d

That's one thread; there are many more.  I recommend using google to
search comp.lang.ada for posts by Bob Duff with "pragma elaborate" as
the search string.

****************************************************************

From: Adam Beneschan
Date: Wednesday, April 26, 2006  12:23 PM

Thanks for the pointer.  The above thread is really helpful,
particularly the part about Bob Duff saying that this part of the
language is something of a mess---now I don't feel so bad about not
knowing what's going on with respect to elaboration. :) :)

****************************************************************

From: Stephen Leake
Date: Wednesday, April 26, 2006  12:23 PM

> But I did a quick check of some publicly available Ada libraries.
> OpenToken has, in the OpenToken package spec, a library-level instance
> of Ada.Strings.Bounded.Generic_Bounded_Length, but does not say
> "pragma Elaborate(Ada.Strings.Bounded)".  [And the definition of
> Ada.Strings.Bounded in A.4.4 doesn't have an Elaborate_Body pragma.]
> SAL has, in the System_Storage_Elements_Text_IO spec, library-level
> instances of both Ada.Text_IO.Modular_IO and Ada.Text_IO.Integer_IO,
> but no "pragma Elaborate(Ada.Text_IO)".  The programmers of those
> libraries know their way around Ada pretty well.

Thanks :).

> This is probably better evidence that this requirement isn't well
> known than my own ignorance.

Actually, we (the authors of SAL and OpenToken) don't put in the
pragma Elaborate because we typically use GNAT, which has a default
elaboration order mode that eliminates the need for the pragmas. Note
that mode will fail for some legal Ada programs; see the GNAT user's
guide for more info.

For the record, I do know about needing pragma Elaborate. I can't
speak for Ted Dennison on that specific issue.

> It seems like a problem, at least in theory, that this is working only
> because most (or all) Ada compilers out there happen to do the right
> thing.

In GNAT's case, it was deliberate that it "does the right thing"
without the pragmas.

At one point, I was using ObjectAda, and I did put in all the required
pragma Elaborates, as suggested by the GNAT warning for this
(ObjectAda did not have a warning, even though it needed the pragmas).
It was a pain :).

> In theory, someone's next version of the compiler could decide to
> use a different elaboration order that is perfectly legal according
> to the Ada rules but is going to give a lot of programmers a nasty
> surprise when their programs start getting Program_Errors, if all
> those programmers are as ignorant as I am.

Yes. And then they will ask for help, and learn about pragma Elaborate.

No one can learn _all_ of Ada's features at once. I hadn't finished
learning all of Ada 95; now I have to start on Ada 2005 :).

****************************************************************

From: Adam Beneschan
Date: Wednesday, April 26, 2006  6:05 PM

> I'm also dubious of your examples. Most of the code on the net is written
> for GNAT, which has it's own static elaboration model (that isn't quite the
> same as that of the Standard). Thus, it's quite possible that the authors
> have never run into real elaboration problems and are not aware of
> them.

But that's exactly my point.  If expert programmers are writing
potentially non-portable code that they think is portable (and I'm
assuming they didn't intend this public code to be solely for GNAT
users), something seems wrong, somewhere.  The "something" could be
defective instruction rather than a flaw in the standard, but it does
seem to me that there is something amiss.

> Perhaps if the authors used -gnatE more often, there would be more
> elaboration pragmas.
>
> In any case, a compiler that chose a *bad* elaboration order (especially for
> language-defined packages) would have a lot of trouble with existing code,
> and most likely would change it quickly. I see this as no different than a
> compiler which has a line length of 20 characters. That would be a
> legitimate choice, but not one that would be usable in practice. The
> Standard is not in the business of defining what a "good" compiler does.

Hmmm . . . I'd think that the addition of 2.2(15) to Ada 95 would
indicate that we've decided we *were* in that business . . .

I've thought about this a bit more, and now something's bothering me:
Was there any reason why, if a library unit's elaboration code calls a
subprogram in another unit U, or instantiates a generic that has a
body in another unit U, we didn't just have the language behave as if
"pragma Elaborate(U)" were present, whether it's explicitly present or
not?  It would seem to me that the program would simply raise
Program_Error if U's body were elaborated later anyway, so was there
any reason not to treat this as an implicit Elaborate situation?
Unless I'm missing something, it seems that those are cases that
should be easy for a compiler to detect.  I'm not suggesting a change
at this point---just wondering.

****************************************************************

From: Randy Brukardt
Date: Wednesday, April 26, 2006  6:48 PM

> But that's exactly my point.  If expert programmers are writing
> potentially non-portable code that they think is portable (and I'm
> assuming they didn't intend this public code to be solely for GNAT
> users), something seems wrong, somewhere.  The "something" could be
> defective instruction rather than a flaw in the standard, but it does
> seem to me that there is something amiss.

You can't write code that is truly portable unless you test it on multiple
compilers. That has not been done for most of the code on the net. Very,
very little of the code out on the net will compile and run with Janus/Ada
(because of the use of GNAT-specific stuff, assuming Integer is 32-bits,
dependence on compiler bugs, elaboration issues, etc.). My discussions with
other compiler vendors have suggested that that is true for them, too. Our
experience with Claw was that even when you are writing from the ground up
for portability, you still will have a lot of work dealing with the
implementation differences (intended or not). Some of those differences
*might* be unnecessary, but remember that the Standard is not in the
business of enforcing "goodness" -- and any issues would have to be fixed
compatibly (which is pretty much impossible in this case).

...
> I've thought about this a bit more, and now something's bothering me:
> Was there any reason why, if a library unit's elaboration code calls a
> subprogram in another unit U, or instantiates a generic that has a
> body in another unit U, we didn't just have the language behave as if
> "pragma Elaborate(U)" were present, whether it's explicitly present or
> not?  It would seem to me that the program would simply raise
> Program_Error if U's body were elaborated later anyway, so was there
> any reason not to treat this as an implicit Elaborate situation?
> Unless I'm missing something, it seems that those are cases that
> should be easy for a compiler to detect.  I'm not suggesting a change
> at this point---just wondering.

This behavior goes back to Ada's dark ages. Probably by the time that this
was identified as a problem, it was too late to fix it. Surely something
like GNAT's static elaboration model would have been preferred for Ada. But
the whole idea of elaboration was new to Ada, so it's not surprising that it
wasn't done it quite right. In any case, something better would be
incompatible (it would be easy to create programs which work now that could
not link), so it is academic at best.

****************************************************************

From: Pascal Leroy
Date: Thursday, April 27, 2006  1:41 AM

I think that the issue of elaboration order creating non-portabilities is
vastly overblown.

My view is that an exception at elaboration is nearly as good as a
compile-time error, since you'll see it the first time you run your
program (assuming that the determination of the elaboration order is done
in a deterministic fashion).  Hopefully, if you port a program to another
compiler, you will be doing some testing, so you'll just have to slap a
few pragmas when you get Program_Error.

There's a million non-portabilities in Ada (even ignoring compiler bugs)
and as far as I can tell all of them are harder to track than elaboration
issues.  In my opinion this entire discussion is much ado about nothing.

****************************************************************

From: Jeff Cousins
Date: Thursday, April 27, 2006  4:14 AM

I don't think it's that simple.  When I ported our million-and-a-half
line command system from Ada 83 to 95, back in '97, elaboration errors
due to the two compilers generating different elaboration orders was
probably the most time-consuming problem.
It took a few months to add all the necessary pragma Elaborates, as the
Program_Errors tended to occur on paths that weren't normally executed,
e.g. when a device's initialisation occasionally failed.

****************************************************************

From: Robert A. Duff
Date: Thursday, April 27, 2006  3:40 PM

> My view is that an exception at elaboration is nearly as good as a
> compile-time error, since you'll see it the first time you run your
> program (assuming that the determination of the elaboration order is done
> in a deterministic fashion).

...and assuming you don't do I/O during elaboration.

Yes, that's a good point.

What I find painful is the cycles (either caused by GNAT's conservative static
method, or by explicit pragmas).  I find the error messages produces for
cycles to be nigh unto incomprehensible in all compilers I've used.  When I
get such a message, I know which with_clause or whatever I just added, so I
can guess what caused the cycle -- but not from the error message.

>...  Hopefully, if you port a program to another
> compiler, you will be doing some testing, so you'll just have to slap a
> few pragmas when you get Program_Error.

****************************************************************

From: Pascal Leroy
Date: Thursday, April 27, 2006  1:31 AM

> I've thought about this a bit more, and now something's
> bothering me: Was there any reason why, if a library unit's
> elaboration code calls a subprogram in another unit U, or
> instantiates a generic that has a body in another unit U, we
> didn't just have the language behave as if "pragma
> Elaborate(U)" were present, whether it's explicitly present
> or not?

The Halting Problem, maybe?

Deciding whether a subprogram is called or a task created at elaboration
time is generally not possible statically.  Consider for instance:

	B : constant Boolean := Read_Boolean_From_Standard_Input and then
Pack.Func;

Whether the elaboration of B calls Pack.Func or not depends on whether the
user types True or False on her keyboard.  Not exactly computable.

So at a minimum, you'd have to be pessimistic and assume that Pack.Func is
actually called.  But more importantly (and worse) you'd have to assume
(since you cannot see its body) that Pack.Func could call any subprogram
in the universe.  Pretty quickly, you'd have a million implicit
elaboration dependences, and no real Ada program would be free from
cycles.  Not good.

You have to work a bit more to create a similar example involving a
generic instantiation, but I think that's possible.

****************************************************************

From: Adam Beneschan
Date: Thursday, April 27, 2006  10:52 AM

...
> Whether the elaboration of B calls Pack.Func or not depends on whether the
> user types True or False on her keyboard.  Not exactly computable.

Doesn't much matter, in my view.  Nobody would deliberately write code
whose effect is to continue if the user types False on the keyboard
and raises Program_Error if she types True.  So if we had decided to
implement a rule that a call to a subprogram in another unit implies
an automatic Elaborate pragma, we'd want to do that regardless of
whether we could tell that the call would actually take place at
runtime.

My language was a bit imprecise.  When I was talking about elaboration
code that calls another subprogram, I was thinking in a static sense,
not a dynamic sense.  Thus, what I really meant to talk about was
elaboration code that contained a call to a subprogram in another
package, regardless of whether that code would actually be executed.

And I also did not mean to talk about cases where elaboration code
called another subprogram in the same package that then called another
subprogram in a different unit.  That definitely would be asking too
much of the compiler; the programmer would have to use Elaborate
explicitly in such a case.  I wasn't suggesting that the compiler
should figure out all cases where Elaborate would be necessary---only
that we could have required that Elaborate be implied in cases where
it would obviously be needed.

> So at a minimum, you'd have to be pessimistic and assume that Pack.Func is
> actually called.  But more importantly (and worse) you'd have to assume
> (since you cannot see its body) that Pack.Func could call any subprogram
> in the universe.  Pretty quickly, you'd have a million implicit
> elaboration dependences, and no real Ada program would be free from
> cycles.  Not good.

No, that's not what I was saying at all.  To reiterate, I was not
suggesting that the language should require the compiler to get the
elaboration order perfect and avoid Program_Error in all cases---only
that maybe it could have been required to add a few more obviously
needed dependencies to reduce the possibility of an unexpected
Program_Error.

Of course, this is all academic at this point---my last question was
not a suggestion that we change anything, I was just wondering why the
language didn't do something that to me seemed simple enough.

****************************************************************

From: Robert A. Duff
Date: Thursday, April 27, 2006  3:39 PM

> The Halting Problem, maybe?

We "solve" the halting problem the same way we solve all impossible problems
-- redefine the problem definition so it's solvable.  Of course, you haven't
really solved the halting problem.  But if the new problem is a useful one to
solve, then that's good enough.

> Deciding whether a subprogram is called or a task created at elaboration
> time is generally not possible statically.  Consider for instance:
>
> 	B : constant Boolean := Read_Boolean_From_Standard_Input and then
> Pack.Func;
>
> Whether the elaboration of B calls Pack.Func or not depends on whether the
> user types True or False on her keyboard.  Not exactly computable.
>
> So at a minimum, you'd have to be pessimistic and assume that Pack.Func is
> actually called.

Yes, of course.  That's the way all static rules work.  Because of the halting
problem, all static rules must be conservative.

I claim that a program that can fail an elaboration check when your B above is
True, but not False, is evil.  Conservatism is good, here.

>...But more importantly (and worse) you'd have to assume
> (since you cannot see its body) that Pack.Func could call any subprogram
> in the universe.  Pretty quickly, you'd have a million implicit
> elaboration dependences, and no real Ada program would be free from
> cycles.  Not good.

No, no.  Elab order (in Ada) is determined at link time.  You could assume
that Pack.Func can call anything it can see -- you know about the
with_clauses.  Or, less conservatively, you could look at what Pack.Func
actually does call (all possible overridings, for a dispatching call).
All of this information is available at link time.  It's not easy, though.
I think a completely different approach could solve all the problems
much more simply.

> You have to work a bit more to create a similar example involving a
> generic instantiation, but I think that's possible.

I don't doubt it.

****************************************************************

From: Robert A. Duff
Date: Friday, April 28, 2006  12:04 PM

>....So if we had decided to
> implement a rule that a call to a subprogram in another unit implies
> an automatic Elaborate pragma, ...

Elaborate isn't good enough.  You need Elaborate_All.

> And I also did not mean to talk about cases where elaboration code
> called another subprogram in the same package that then called another
> subprogram in a different unit.  That definitely would be asking too
> much of the compiler; ...

GNAT does that.

>...the programmer would have to use Elaborate
> explicitly in such a case.  I wasn't suggesting that the compiler
> should figure out all cases where Elaborate would be necessary---only
> that we could have required that Elaborate be implied in cases where
> it would obviously be needed.

Ah, I see.  I think that would just further complicate the programmer's job.

I don't think a static scheme is really worth it, unless it actually solves
the complete problem.

****************************************************************

From: Robert A. Duff
Date: Thursday, April 27, 2006  3:30 PM

> Thanks for the pointer.  The above thread is really helpful,
> particularly the part about Bob Duff saying that this part of the
> language is something of a mess---now I don't feel so bad about not
> knowing what's going on with respect to elaboration. :) :)

:-)

People in this thread keep talking about pragma Elaborate.
But in almost all cases, you should be using Elaborate_All,
not Elaborate.  Elaborate_All is transitive.

I believe elaboration issues were not understood during the design of Ada 83
until fairly late in the game.  I think Ben Brosgol discovered what came to be
called the "Brosgol anomalies", which were fixed by adding pragma Elaborate.
(The forcing occurence rules are also related.)
Except pragma Elaborate doesn't really work.

I think (with 20/20 hindsight) that the elaboration order rules should have
these properties:

    - Elaboration order is portable -- all compilers must choose the same
      order.  Alternatively, the compiler should be required to prove that the
      order doesn't matter (but that's hard).

    - The order should be determinable locally/incrementally.  That is, making
      a change to a totally unrelated subsystem should not change the order
      for _this_ subsystem.

    - The rules should be checked entirely at compile time or link time.

    - There should be no need for all these elaboration-control pragmas.

    - Mutually recursive modules should be possible without a lot of horsing
      around.  A spec ought to be able to refer to its children, for example.
      Now we have limited with, but that didn't exist in 1983.

    - All the access-before-elaboration issues should be solved the same way.
      (We have freezing rules for some things, and run-time checks for other
      things, for what is essentially the same issue.)

I think I know how to achieve all that, but it wouldn't be anything close to
compatible with Ada.

****************************************************************

From: Randy Brukardt
Date: Thursday, April 27, 2006  4:24 PM

You've made this pronouncement several times, but I'm not sure that you ever
explained how you would get to this state of "unalloyed delight" (to steal a
famous line from Wirth about PL/1). It might come in handy the next time
we're dealing with problems in this area (sometimes we're forced to give up
compatibility, and in such cases, its often best to fix things "right").

****************************************************************

From: Pascal Leroy
Date: Friday, April 28, 2006  1:18 AM

Yes, I'm like Randy, I'd like to know how this is supposed to work.

****************************************************************

From: Gary Dismukes
Date: Friday, April 28, 2006  12:44 AM

> I believe elaboration issues were not understood during the design of Ada 83
> until fairly late in the game.  I think Ben Brosgol discovered what came to be
> called the "Brosgol anomalies", which were fixed by adding pragma Elaborate.
> (The forcing occurence rules are also related.)

If memory serves, I believe that the Brosgol anomaly (singular) was
Ben's observation in an early version of Ada that allowing an object
of a private type to be declared prior to the full type was, shall
we say, problematic.  This (probably among other things) led to
the formulation of the forcing occurrence rules.  I don't believe
it related to issues of elaboration among independent units.
But perhaps there were other "anomalies" he pointed out that I've
forgotten (I'm sure Ben would remember the details:-).

****************************************************************

From: Robert A. Duff
Date: Friday, April 28, 2006  11:58 AM

> You've made this pronouncement several times, but I'm not sure that you ever
> explained how you would get to this state of "unalloyed delight" (to steal a
> famous line from Wirth about PL/1).

It would take a rather long explanation.  Do you really want to see it?

>...It might come in handy the next time
> we're dealing with problems in this area (sometimes we're forced to give up
> compatibility, and in such cases, its often best to fix things "right").

I doubt it would be of any use.  I'm not talking about minor
incompatibilities, I'm talking about a whole different approach.
Which means it's probably too off-topic for an Ada forum,
which is why I haven't bothered to explain in the past.

****************************************************************

From: Randy Brukardt
Date: Friday, April 28, 2006  12:14 PM

> It would take a rather long explanation.  Do you really want to see it?

Yes, of course, or I wouldn't have asked. My gut feeling is that it isn't
possible, and I'd like to be proven wrong. I see Pascal has a similar
interest.

> >...It might come in handy the next time
> > we're dealing with problems in this area (sometimes we're forced to give
up
> > compatibility, and in such cases, its often best to fix things "right").
>
> I doubt it would be of any use.  I'm not talking about minor
> incompatibilities, I'm talking about a whole different approach.
> Which means it's probably too off-topic for an Ada forum,
> which is why I haven't bothered to explain in the past.

Yee of little faith! I doubt we could use the whole approach, but it's often
possible to steal ideas. Some of the goofiest ideas have turned into useful
ones ultimately, but only when others went to work on them. In any case,
grand pronouncements need to be backed up...

****************************************************************

From: Robert A. Duff
Date: Monday, May  8, 2006  10:37 AM

> Yes, of course, or I wouldn't have asked.

OK, you asked for it.  I'm very happy to discuss my language design ideas
with anybody who's willing to listen.  I just didn't think my solutions were
relevant in the Ada context.

>...In any case,
> grand pronouncements need to be backed up...

My point was not really intended as a "grand pronouncement".  Just the
opposite, in fact.  My point was more like this: I have thought hard about how
to solve these problems, and concluded that a rather different approach from
Ada was needed.  Therefore, people shouldn't bother trying to solve all these
problems in the Ada context.  Of course, I could be wrong, and somebody more
clever than I might be able to do it.  But not by following my approach, which
is incompatible with Ada.

Sorry if my earlier comments sounded like teasing.  ;-)

----------------

First I have to explain a little bit about my language design, so you can
understand my solutions in that context.

The following is a static model.  I also want to support dynamic loading, sort
of like in Java, but that's a whole 'nother kettle of fish, and I ignore these
fish here.

The language has "modules", which come in two parts -- module spec and module
body.  Module parts may be nested.  First, I define all the semantics ignoring
separate compilation.  There is a Root_Module (kind of like Ada's Standard),
which contains Predefined_Environment, which contains lots of predefined
modules.  User-defined top-level modules are nested within Root_Module, and
may contain further nested modules.

Then, separate compilation is defined in terms of a conceptual transformation
of the whole world into a single Root_Module with everything physically nested
within it.  The semantics then follows from that.  In the physically nested
version, modules are elaborated exactly where they occur.  [I don't actually
use the term "elaborate", by the way.]

Every piece of information in a module spec must be repeated in the body,
marked "public" in the body.  So we might have:

    My_Module: module
        P: procedure(...);
    end My_Module;

    My_Module: module body
        P: public procedure(...) do
            ...
        end P;

        Q: procedure(...) do
            ...
        end Q;
    end My_Module;

The language is designed so that you can tell by looking at the body exactly
what the spec must look like.

There are no private parts.  This raises some interesting issues, for example
with respect to stability of record layouts for persistent data structures.
I think I have solutions to these, but they're not relevant here.  Suffice to
say, we don't need no stinkin freezing rules.  ;-)

----------------

Here is the key point:  Module specs are not elaborated.  Module specs have no
presence at run time; they are purely for defining the structure/visibility.

Module bodies are elaborated where they occur in their parent.  The entire
execution of the program is defined as the elaboration of the body of
Root_Module, which contains all the predefined and user-defined modules, and
elaborates their bodies exactly where they occur, in textual order.

The fact that module specs are not elaborated leads to a huge simplification,
because it dissociates elaboration from visibility.

----------------

Forward references are allowed, but you have to mark them with "forward".
The presence or absence of "forward" does not affect overload resolution.

        P: public procedure(...) do
            if ... then
                forward Q(...);
            end if;
        end P;

        Q: procedure(...) do
            if ... then
                P(...);
            end if;
        end Q;

For elaboration code in a module, there are rules preventing things from being
accessed before being well-defined.  These rules require following the call
graph.  A call to P in between P and Q above would be illegal.  You cannot
create an object before its type is completely defined (which means all of its
dispatching ops must have been elaborated).  Similar rules for 'Access of a
procedure (except I don't use the attribute syntax).  Etc.  These rules would
be way too restrictive in the Ada context, but they're not restrictive at all
in my language; I hope to explain why below.

----------------

Imports.

A module part may import another module.  The usual syntax is "use M1, M2;",
which is a "backward import", meaning that M1 and M2 occur before this module
part.  (I'm ignoring separate compilation for the moment.)  If you want to
import something that occurs later, you say "import forward M3;".  A forward
import is sort of like Ada 2005's limited with, but with much fewer
restrictions (you don't have to put in access types where you don't want to).

A module spec may import its children (that is, modules nested within it).
No "forward" in this case.

I think the verbing of "with" is an abuse of the English language.
The keyword "use" is perfect for import clauses.  Don't be confused
into thinking that they are like Ada's use clauses -- they're not.

The rules are set up so that you can tell, by looking at the root of a
subsystem hierarchy, which other subsystems it imports.  This is the one small
problem I see with Ada child packages -- the source code doesn't give you an
overall picture of what-imports-what at a coarse (subsystem-level)
granularity.

----------------

Separate compilation.

The model is sort of a combination of Ada's subunits and child units.
Instead of physically nesting a module part within another part, you can move
it out, possibly into a different source file, and make it a separately
compiled module part.  Every separately compiled module part corresponds to a
stub in the parent.  The stub may be a "named stub", as in "My_Module:
separate module body;".  Then the separately compiled module looks like:

    separate (My_Parent)
    My_Module: module body ...

The stub may also be an "others stub", as in "others: separate module body;".
The separately compiled child looks the same as above.

So when compiling My_Module separately, we look in My_Parent.  If it contains
a named stub for My_Module, then that's the corresponding stub.  Otherwise,
there must be an others stub, and that's the corresponding stub.

So you have a choice.  Named stubs are like Ada's subunits, since the parent
names the child (except that it works for both specs and bodies).  Others
stubs are like Ada's child units -- the parent is freely extensible, and can
add children that the parent has never heard of.

The presence of an others stub indicates extensibility of the module.

Note that if module X wants to use module Y, module X must say "use Y;"
(or, rarely, "use forward Y").  It doesn't matter whether X and Y are
separately compiled, or physically nested.

----------------

Order of elaboration.

Now I can finally talk about order of elaboration of separately compiled
modules.

Recall that only bodies are elaborated!

For physically nested modules, the elaboration order is textual.

A separately compiled module body that corresponds to a named stub
is equivalent to putting that module body in place of the stub,
so the order is defined by the previous rule.  Thus, if you like,
you can directly control the order by placing a named stub where you want it
-- for example, it is sometimes useful to say "elaborate Mumble first",
without having to say "use Mumble" in every other module.

So now we're left with separately compiled module bodies that correspond to an
others stub.  They are all elaborated at the place of that stub.  The only
question is, in what order?

Consider all the child module bodies that correspond to a given others stub.
And consider all the import clauses amongst them, ignoring imports that go to
a different subsystem hierarchy, or to grandchildren, etc.

Module body A must precede module body B if B imports the spec of A backward,
or if A imports the spec of B forward.

If the "must precede" relation forms a cycle, the module parts involved in the
cycle are illegal.  (In other words, you can have cycles of import clauses,
but every cycle must contain at least one "use forward".)  Otherwise, the
"must precede" relation forms a graph which determines a partial order.  The
order in which module parts are included in place of the others stub obeys
this partial order.

The actual order is determined by a depth-first walk of the "must precede"
graph, processing the module parts in alphabetical order by defining-name
whenever there is a choice left open by the partial order.

It is considered bad style to depend on the alphabetical order property.
If you care about the order, you should use import-clauses and/or named stubs to
constrain it.  However, if you fail to do so properly,
at least the alphabetical order property will ensure that your program
will use the same order on all implementations.

Ideally, the language rules would ensure that if the order is not fully
specified by import-clauses and named stubs, then the evaluation order cannot
matter at run time.  However, such rules would require a complicated and global
analysis, and might end up being too conservative.

Note that all elaboration order issues are local to a single others stub.
Modules elsewhere in the hierarchy are irrelevant.  Thus, the compiler can
[re]compute the elaboration order somewhat incrementally.

Note also that the elaboration order does not depend on which modules are
included in the program.  If we have (possibly in separate files):

    My_Subsystem: module
        others: separate module;
    end My_Subsystem;

    My_Subsystem: module body
        others: public separate module;
        others: separate module body;
    end My_Subsystem;

    separate(My_Subsystem)
    This: public module ...

    separate(My_Subsystem)
    That: public module ...
        use My_Subsystem.This;
        ...

    separate(My_Subsystem)
    The_Other: public module
        use My_Subsystem.This;
        ...

and the main module uses My_Subsystem.That, but not My_Subsystem.The_Other,
then The_Other is not included in the program.  The compiler can still
calculate the elab order not knowing that fact.

The above is equivalent to:

    My_Subsystem: module
        This: module ...

        That: module ...
            use My_Subsystem.This;
            ...

        The_Other: module
            use My_Subsystem.This;
            ...

    end My_Subsystem;

    My_Subsystem: module body
        This: public module body ...

        That: public module body ...
            use public My_Subsystem.This;
            ...

        The_Other: public module body
            use public My_Subsystem.This;
            ...
    end My_Subsystem;

and even though physically nested, modules are not included in the program
unless used directly or indirectly by the main module.

In Ada, you might declare an array object in the visible part of a package,
and initialize it using a loop after the "begin" of the package body.
If you don't say "Elaborate_Body" in this case, some client of the package
could refer to the array before it has been initialized.  We don't even check
for this at run time!  And it's implementation defined whether this bug will
happen!  And Elaborate_Body prevents with-ing of children.  So if you want to
do that, you have to rely on all clients to say Elaborate_All.

In my language, this problem does not exist.  Any client that wishes to refer
to that array at elab time must have a backward import, causing the array to
be initialized.  If you have a forward import, you can refer to that array,
but not at elab time.  And the array can be constant.

----------------

I'm not sure, but I think it should illegal to say "use forward" _unless_ this
is part of an import cycle.  If there are no cycles, everything is simple --
if you refer to something in another module (e.g. call a procedure in it), the
other module's body must be elaborated first, so you can freely call things in
it, and create objects of types in it, at elab time.

If there is a cycle, we do the same analysis for the whole cycle that we would
do within a single module -- follow the call graph, and forbid various uses of
things before they are completely defined.

My belief is that cycles are rare (it's usually better to build things up in
layers), and that when cycles occur, they are short (typically, length 2).
If there is a cycle, the modules in that cycle must be developed together --
by the same programmer, or at least by the same team -- it doesn't make any
sense to develop them independently, since they obviously know about each
other.

Note that if you say "use forward M;", and then call procedure P in M,
whether or not at elab time, you must say "forward P(...)".

Note that all elab checks happen at compile time, except in the case of a
cycle, in which case they can happen at link time.  But once a given cycle has
been dealt with, we don't need to recheck it unless some module within the
cycle is modified.

Note that one mechanism where Ada uses (at least) two: run-time
access-before-elab checks, and compile-time freezing rules.

----------------

So, you see, this language has fundamental differences from Ada.  It was
obviously heavily influenced by various good ideas in Ada (and various other
languages).  I have made no attempt to be compatible with Ada, or any other
language (except in the sense of pragma-import-like stuff).

The key point is that bodies are elaborated, but specs are not (are not even
present at run time).  But that requires that bodies contain all the spec
information.  In Ada, if you say "X: constant Integer := 7;", there's no trace
of that in the body.

Consider:

    package P is
        type T is private;
        function Make_T (Size: Natural) return T;
        Empty: constant T := Make_T(Size => 0);
    end P;

That won't work in Ada, but the corresponding thing works in my language.
Also:

    package P is
        procedure Q;
        X: not null access procedure := Q'Access;
    end P;

That works in Ada, but needs a run-time elab check on all calls to X.all.
The corresponding thing in my language works, but the body-copy of X
must occur after the body of Q, thus ensuring statically that Q cannot
be called prematurely.  If we tried a similar restriction in Ada, it would be
too restrictive, because then we couldn't export X.

There are no semantic differences between separately compiled children and
physically nested modules, as there are in Ada.

I'm happy to discuss all of these ideas.  If someone can apply them to Ada,
I will be pleasantly surprised.

----------------

Now, if only I could come up with a good name for my language.
Suggestions welcome.  ;-)

****************************************************************

From: Jeffrey Carter
Date: Monday, May 8, 2006  1:36 PM

> [I don't actually
> use the term "elaborate", by the way.]

You certainly did here, presumably because you're addressing an Ada
audience. What do you use? "Execution"?

I'm also curious how you handle operator visibility.

> Now, if only I could come up with a good name for my language.
> Suggestions welcome.  ;-)

Other than "Duff", I presume? :) My first thoughts were Bodule or
Modbody, since everything's in the bodies. Current trends (Java, Ruby,
Python) imply you pick something you like and use that name. Other
languages (Ada, Dylan, Pascal) are named after people.

When I design the perfect language (which probably no one but me will
like), I'll call it NINA (for NINA Is Not Ada).

****************************************************************

From: Robert A. Duff
Date: Monday, May 8, 2006  6:35 PM

> You certainly did here, presumably because you're addressing an Ada
> audience.

Right.

>... What do you use? "Execution"?

Evaluation.  Evaluation of an expression produces a value.  Evaluation of a
statement produces a void value (i.e. no value at all).  Evaluation of a
declaration also produces void, and has the side effect of creating the thing,
or binding the name or something like that.

> I'm also curious how you handle operator visibility.

Operators are directly visible everywhere they're visible by selection.
Sort of as if Ada's "use type" were always in force.
You can define new operators, such as %%%%^&%^%&%.
Precedence is declared by relating one operator to another.
The default is that two operators are unrelated by precedence,
so that if one library defines %%% as an operator, and another
library defined %%%$$$, you can't "mix" them without parens.

Keep in mind this is a work in progress, and I keep changing my mind about all
kinds of things, including lexical details.

Modules may be declared "open", which means you get direct visibility on the
contents when importing, so you can say X instead of M.X.  It's like Ada's
use_package_clause, except that the module decides, rather than the client.
Rationale: the programmer of the module must choose sensible names based on
whether clients will say X versus M.X.

> > Now, if only I could come up with a good name for my language.
> > Suggestions welcome.  ;-)
>
> Other than "Duff", I presume? :)

I'm not _quite_ arrogant enough to name it after myself!  ;-)

>...My first thoughts were Bodule or Modbody,
> since everything's in the bodies.

Well, but, that's important from the point of view of this elab-order issue,
but it's really a minor detail in the scheme of things.

>...Current trends (Java, Ruby, Python) imply
> you pick something you like and use that name. Other languages (Ada, Dylan,
> Pascal) are named after people.

Yeah, I'll probably pick some famous person, if I ever get around to actually
writing a reference manual (or a compiler!).

> When I design the perfect language (which probably no one but me will like),
> I'll call it NINA (for NINA Is Not Ada).

How about Nada (for "not Ada").  ;-)

****************************************************************

From: Jeffrey Carter
Date: Monday, May 8, 2006  8:10 PM

> I'm not _quite_ arrogant enough to name it after myself!  ;-)

If you don't name it, people will call it Duff's language, and that will
probably shorten to Duff pretty quickly.

> How about Nada (for "not Ada").  ;-)

Now you've named it!

****************************************************************

From: Randy Brukardt
Date: Monday, May 8, 2006  7:48 PM

...
> My point was not really intended as a "grand pronouncement".  Just the
> opposite, in fact.  My point was more like this: I have thought hard about how
> to solve these problems, and concluded that a rather different approach from
> Ada was needed.  Therefore, people shouldn't bother trying to solve all these
> problems in the Ada context.  Of course, I could be wrong, and somebody more
> clever than I might be able to do it.  But not by following my approach, which
> is incompatible with Ada.

Well, of course this has a logical fallacy: just because there exists an
alternative approach that works, doesn't mean that there isn't an approach
that would work in Ada.

...
> The rules are set up so that you can tell, by looking at the root of a
> subsystem hierarchy, which other subsystems it imports.  This is the one small
> problem I see with Ada child packages -- the source code doesn't give you an
> overall picture of what-imports-what at a coarse (subsystem-level)
> granularity.

That seems to have a significant impact on extensibility; if a (new) child
module can't reference a subsystem unless it's parent does, there is a very
substantial limit on what can be done in a child.

Personally, I don't find much value in "subsystems", at least in any formal
way. Trying to control these things formally gets in the way more than it
does good.

For example, given your rules, you would have "use" Claw at the top of a
subsystem if any child uses it. But that would prevent creating a subsystem
that works both usefully both with and without a Claw GUI. I will put the
GUI elements into a child unit, so that they can be used if needed, and the
functional elements in the root package. That way, if the package needs to
be used in a non-GUI environment or with a different GUI, it can still be
used (just don't with the Claw child). It seems like your approach would
prevent that.

...
> I'm happy to discuss all of these ideas.  If someone can apply them to Ada,
> I will be pleasantly surprised.

It appears to me that the key to your approach is the requirement to declare
forward uses of all types. I don't think that the fact that specs don't
contain anything makes much difference -- they're really more akin to the
limited view of Ada 2005 than to an Ada package. Simply by concating them to
their body (for elaboration purposes), you would end up with effectively the
same rules.

So, I suspect that you could actually apply your technique to Ada (of
course, compatibility concerns would never allow an all-static scheme). If
you could declare units "Duffable" (for the lack of a better name), and you
could declare forward uses of names from such units, you probably could make
static checks on them. Whether this would be worthwhile is an interesting
question...

****************************************************************

From: Randy Brukardt
Date: Tuesday, May  9, 2006  8:33 AM

> Well, of course this has a logical fallacy: just because there exists an
> alternative approach that works, doesn't mean that there isn't an approach
> that would work in Ada.

Yes, that would be a fallacy.  Fortunately, I made no such claim.  ;-)

> ...
> ...
> > The rules are set up so that you can tell, by looking at the root of a
> > subsystem hierarchy, which other subsystems it imports.  This is the one small
> > problem I see with Ada child packages -- the source code doesn't give you an
> > overall picture of what-imports-what at a coarse (subsystem-level)
> > granularity.
>
> That seems to have a significant impact on extensibility; if a (new) child
> module can't reference a subsystem unless it's parent does, there is a very
> substantial limit on what can be done in a child.

Yes, that is correct.

My feeling is that a subsystem is developed by a particular team of
programmers.  If you decide (late in the game) that some great-grandchild
wants to use Claw.Mumble.Dumble, then you have to edit the root of your
hierarchy to add "use Claw".  It's your subsystem, developed by your team, so
you should have the ability to modify it.

In other words, the "substantial limit" you mention is self-imposed, and you
can lift it any time you like.

If, on the other hand, two separate teams are developing parts of the same
"program", without close coordination, then they should each have their own
subsystem.

But having said all that, I do understand your point, and I have considered
(in the past) having a way to say something like "use *" or "use all",
mean "my children can use anything the like".  My current feeling is it's
not necessary, but I'm always changing my mind about such things.

I have also thought about having a different syntax for "permission of
children to use other subsystems" versus "I want to use so-and-so module".

> Personally, I don't find much value in "subsystems", at least in any formal
> way. Trying to control these things formally gets in the way more than it
> does good.
>
> For example, given your rules, you would have "use" Claw at the top of a
> subsystem if any child uses it. But that would prevent creating a subsystem
> that works both usefully both with and without a Claw GUI. I will put the
> GUI elements into a child unit, so that they can be used if needed, and the
> functional elements in the root package. That way, if the package needs to
> be used in a non-GUI environment or with a different GUI, it can still be
> used (just don't with the Claw child). It seems like your approach would
> prevent that.

I don't think it _prevents_ anything.  It means you'd have to split that into
two subsystems.

But you make a good point, here.

> ...
> > I'm happy to discuss all of these ideas.  If someone can apply them to Ada,
> > I will be pleasantly surprised.
>
> It appears to me that the key to your approach is the requirement to declare
> forward uses of all types.

I'm not sure what you mean by that.  Example?

Referring to a type is like referring to anything else.  You say "forward T"
of the reference is textually before the declaration of T.

>...I don't think that the fact that specs don't
> contain anything makes much difference

"Contain anything"?  They contain text.  No code is generated for them;
I guess that's what you meant.  I think this point is key -- see below.

>... -- they're really more akin to the
> limited view of Ada 2005 than to an Ada package.

There's a big difference: I don't have a lot of restrictions forcing the use
of pointers.

>... Simply by concating them to
> their body (for elaboration purposes), you would end up with effectively the
> same rules.

I don't think that quite works, unfortunately.  For example:

    package P is
        function Q (...) return Boolean;
        X : not null access function (...) return Boolean := Q'Access;
    end P;

    package body P is
        Y : Boolean := X.all(...);

        procedure Q is
        begin
            ...
        end Q;

        -- (1)
    end P;

The above is legal in Ada, but we need a run-time check to prevent Y.
I don't see how to do that statically, since we can hide the fact
that X = Q'Access from the compiler (e.g. replace Q'Access with
F(Q'Access), where F is a function declared elsewhere).

In my language, the decl of X would be repeated at the place marked "-- (1)",
and that's where Q'Access would be evaluated.  Y would have to refer to X as
"forward X", and we can tell statically that we're referring to X before it is
completely defined.  Alternatively, if the decl of X is moved before Q,
we can tell that Q is not completely defined.

(I realize I have not defined "completely defined" in this discussion.)

The key difference here is that Ada wants to evaluate Q'Access as part of the
elaboration of the spec of P, whereas in my language, it is evaluated as part
of the body.  Forbidding the evaluation of Q'Access before Q is completely
defined would be an onerous restriction in Ada -- it would mean you can't
export X.  My language decouples "ability to export" from "place of
elaboration".

Note also that modules are elaborated at the place of their corresponding
stub.  And modules can import their children freely (no "limited view").

> So, I suspect that you could actually apply your technique to Ada (of
> course, compatibility concerns would never allow an all-static scheme). If
> you could declare units "Duffable" (for the lack of a better name), and you
> could declare forward uses of names from such units, you probably could make
> static checks on them. Whether this would be worthwhile is an interesting
> question...

I don't see how, without further restricting Ada, which is already too
restrictive (e.g. you can't do the example I mentioned before,
of writing "Empty: constant T := Make_T(Size => 0);" in the same
package spec where Make_T is declared).

It's certainly possible to have a fully static (link time) method in Ada.
GNAT does it, but I've used that, and ran into annoying restrictions (rarely).
And it doesn't have all the desirable properties (e.g. we can prevent calls
before the body is elab'ed, but we can't prevent uninitialized variables).

****************************************************************

From: Bj”rn Persson
Date: Tuesday, May  9, 2006  4:01 AM

> >...Current trends (Java, Ruby, Python) imply
> > you pick something you like and use that name. Other languages (Ada,
> > Dylan, Pascal) are named after people.
>
> Yeah, I'll probably pick some famous person, if I ever get around to
> actually writing a reference manual (or a compiler!).

Is there a language named Jean yet? (Ichbiah is too hard to spell.)

****************************************************************

From: Adam Beneschan
Date: Tuesday, May  9, 2006 12:21 PM

Are you famous?  Bob did express an interest in naming his language
after a famous Persson....

****************************************************************

From: Robert A. Duff
Date: Tuesday, May  9, 2006 12:48 PM

:-)  :-)

****************************************************************

From: Bj”rn Persson
Date: Tuesday, May  9, 2006  1:39 PM

I'm working on it, but so far the concept of encoding-aware strings hasn't
caused any big headlines. ;-)

****************************************************************

From: Randy Brukardt
Date: Tuesday, May  9, 2006 12:43 PM

> I don't think it _prevents_ anything.  It means you'd have to
> split that into two subsystems.

Yes it does. In order to split the subsystem, you would have to make a lot
of the innards of the subsystem visible (the GUI is likely to need a lot of
detailed access). That means giving too much visibility in order to
implement the different GUIs, or being unable to build the rest of the
subsystem independent of the GUI. That doesn't seem good, either way.

> But you make a good point, here.

Yes, there hasme way to accomplish this sort of design. What it is,
I leave to the language designer.

...
> > It appears to me that the key to your approach is the requirement to declare
> > forward uses of all types.
>
> I'm not sure what you mean by that.  Example?

A bad choice of words; I meant "types" generically (as in "all kinds of
entities"), not types specifically.

> Referring to a type is like referring to anything else.  You say "forward
T"
> of the reference is textually before the declaration of T.

Right, I was referring to "everything else".

...
> >... -- they're really more akin to the
> > limited view of Ada 2005 than to an Ada package.
>
> There's a big difference: I don't have a lot of restrictions
> forcing the use of pointers.

No, but you use the "forward" keyword instead. And that's the key, to me.

> >... Simply by concating them to
> > their body (for elaboration purposes), you would end up with effectively the
> > same rules.
>
> I don't think that quite works, unfortunately.  For example:
>
>     package P is
>         function Q (...) return Boolean;
>         X : not null access function (...) return Boolean := Q'Access;
>     end P;
>
>     package body P is
>         Y : Boolean := X.all(...);
>
>         procedure Q is
>         begin
>             ...
>         end Q;
>
>         -- (1)
>     end P;
>
> The above is legal in Ada, but we need a run-time check to prevent Y.
> I don't see how to do that statically, since we can hide the fact
> that X = Q'Access from the compiler (e.g. replace Q'Access with
> F(Q'Access), where F is a function declared elsewhere).

Well, clearly in a Duffable unit, Q has to be marked "forward", and then the
use is clearly illegal.

Yes, you can't export X in a Duffable unit, but so what? The idea is to make
*most* units safer; clearly the current Ada rules would still exist for
compatibility, and you could still use them if you wanted. And you can
always export X as a function/procedure pair (and that is often preferred
anyway).

> In my language, the decl of X would be repeated at the place marked "-- (1)",
> and that's where Q'Access would be evaluated.  Y would have to refer to X as
> "forward X", and we can tell statically that we're referring to X before it is
> completely defined.  Alternatively, if the decl of X is moved before Q,
> we can tell that Q is not completely defined.
>
> (I realize I have not defined "completely defined" in this discussion.)

Y wouldn't have to refer to X as "forward", because the declaration is given
before. But that declaration is illegal in a Duffable unit, so it doesn't
matter.

> The key difference here is that Ada wants to evaluate Q'Access as part of the
> elaboration of the spec of P, whereas in my language, it is evaluated as part
> of the body.  Forbidding the evaluation of Q'Access before Q is completely
> defined would be an onerous restriction in Ada -- it would mean you can't
> export X.  My language decouples "ability to export" from "place of
> elaboration".

I'm less convinced that it would be onerous. You could use "forward
Q'access" as a default parameter, for instance; it just can't be evaluated.
Hard to say for sure without trying realistic examples.

> Note also that modules are elaborated at the place of their corresponding
> stub.  And modules can import their children freely (no "limited view").

Yes, but I'm trying to apply these ideas to Ada. I'm not going to mess
around with visibility...

> > So, I suspect that you could actually apply your technique to Ada (of
> > course, compatibility concerns would never allow an all-static scheme). If
> > you could declare units "Duffable" (for the lack of a better name), and you
> > could declare forward uses of names from such units, you probably could make
> > static checks on them. Whether this would be worthwhile is an interesting
> > question...
>
> I don't see how, without further restricting Ada, which is already too
> restrictive (e.g. you can't do the example I mentioned before,
> of writing "Empty: constant T := Make_T(Size => 0);" in the same
> package spec where Make_T is declared).

What's wrong with further restricting Ada? I can't remember a time when I've
actually had a problem with the restrictions of Ada. (Maybe I'm just losing
my memory, though. :-0)

> It's certainly possible to have a fully static (link time) method in Ada.
> GNAT does it, but I've used that, and ran into annoying restrictions (rarely).
> And it doesn't have all the desirable properties (e.g. we can prevent calls
> before the body is elab'ed, but we can't prevent uninitialized variables).

Nothing can prevent uninitialized variables in Ada:
   A : Integer;
so I presume you mean something else. Perhaps you mean premature use of
variables that are initialized at elaboration time? That's just a symptom of
bad design. Randy's Ada rule number 1: Never, ever do anything significant
at elaboration time, because it will *always* come back to bite you. You
always need more control over the order of initialization than you can get
from elaboration. Every time I've violated this rule, I've always ended up
changing the code to use explicit initialization routines. (I just finished
doing that to the Trash Finder spam filter, grumble, grumble. In this case,
it was a need to be able to reinitialize parameters if they change; that's
very common.) Depending on elaboration is a fool's game; I don't think your
"rational elaboration" would change that any -- even decoupling visibility
and elaboration doesn't really help, because you still don't have any way to
indicate the dynamic dependencies (i.e. unit A must not start its tasks
before unit B has finished loading the parameter set).

****************************************************************

From: Robert A. Duff
Date: Tuesday, May  9, 2006  2:04 PM

> > But you make a good point, here.
>
> Yes, there has to be some way to accomplish this sort of design. What it is,
> I leave to the language designer.

I'll have to think about this some more.  Thanks for your comments.

But note that if you put "use Claw" in the root module of your subsystem,
you're not dragging in all of Claw -- just the root module, which is probably
empty, or nearly so.

...
> > There's a big difference: I don't have a lot of restrictions
> > forcing the use of pointers.
>
> No, but you use the "forward" keyword instead. And that's the key, to me.

In most cases, "forward" is not key.  It's for readability by humans.
When you refer to a thing declared in a module, the compiler can deduce
whether "forward" is needed.  Likewise, when you refer to a module itself, in
"use [forward] <module-name>", the compiler can deduce whether "forward" is
needed if there is a named stub for the module, or if the module is not a
sibling of this one.  The only remaining case is where you import a sibling,
and that one and this one both correspond to the same others stub.  In that
case, the "forward" in the import actually does something (helps determine the
order in which modules are included at the point of the others stub).

> > >... Simply by concating them to
> > > their body (for elaboration purposes), you would end up with effectively the
> > > same rules.
...
> Well, clearly in a Duffable unit, Q has to be marked "forward", and then the
> use is clearly illegal.
>
> Yes, you can't export X in a Duffable unit, but so what?

So don't call it "Duffable", because I don't endorse this sort of
restriction.

>... The idea is to make
> *most* units safer; clearly the current Ada rules would still exist for
> compatibility, and you could still use them if you wanted.

OK, but can you mix the current Ada rules with the safer rules in the same
program?

>... And you can
> always export X as a function/procedure pair (and that is often preferred
> anyway).

Here, you're making a virtue of necessity.  ;-)

I want the programmer to decide whether this "function/procedure pair" style
is preferred, without being forced by arbitrary language rules.

> > In my language, the decl of X would be repeated at the place marked "-- (1)",
> > and that's where Q'Access would be evaluated.  Y would have to refer to X as
> > "forward X", and we can tell statically that we're referring to X before it is
> > completely defined.  Alternatively, if the decl of X is moved before Q,
> > we can tell that Q is not completely defined.
> >
> > (I realize I have not defined "completely defined" in this discussion.)
>
> Y wouldn't have to refer to X as "forward", because the declaration is given
> before.

No, X comes after in the body.  That's what matters.

>... But that declaration is illegal in a Duffable unit, so it doesn't matter.
>
> > The key difference here is that Ada wants to evaluate Q'Access as part of the
> > elaboration of the spec of P, whereas in my language, it is evaluated as part
> > of the body.  Forbidding the evaluation of Q'Access before Q is completely
> > defined would be an onerous restriction in Ada -- it would mean you can't
> > export X.  My language decouples "ability to export" from "place of
> > elaboration".
>
> I'm less convinced that it would be onerous. You could use "forward
> Q'access" as a default parameter, for instance; it just can't be evaluated.

Right.

> Hard to say for sure without trying realistic examples.

Seems to me my example was pretty realistic.  Or at least reasonable.

> > Note also that modules are elaborated at the place of their corresponding
> > stub.  And modules can import their children freely (no "limited view").
>
> Yes, but I'm trying to apply these ideas to Ada. I'm not going to mess
> around with visibility...

Agreed (not mess around with visibility -- in Ada).

...
> What's wrong with further restricting Ada? I can't remember a time when I've
> actually had a problem with the restrictions of Ada. (Maybe I'm just losing
> my memory, though. :-0)

I can remember wanting to do the "Empty" thing above numerous times, and been
frustrated that it doesn't work.  And wanting to instantiate the "Sequences"
package to make a seq-of-T in the same package where T is declared (remember
that argument?).

> > It's certainly possible to have a fully static (link time) method in Ada.
> > GNAT does it, but I've used that, and ran into annoying restrictions (rarely).
> > And it doesn't have all the desirable properties (e.g. we can prevent calls
> > before the body is elab'ed, but we can't prevent uninitialized variables).
>
> Nothing can prevent uninitialized variables in Ada:
>    A : Integer;
> so I presume you mean something else.

I mean reading the value of an uninit var.  Ada says that's a bounded error
(in the case of scalars) or well-defined (in the case of access types --
return null) or meaningless (in the case of records, there's no concept of
uninit).

It seems to me that reading uninit vars is essentially the same issue as
calling a not-yet-elab'ed procedure, which is is essentially the same issue as
creating an object of a private type before the full type has been elab'ed.

>... Perhaps you mean premature use of
> variables that are initialized at elaboration time? That's just a symptom of
> bad design. Randy's Ada rule number 1: Never, ever do anything significant
> at elaboration time, because it will *always* come back to bite you. You
> always need more control over the order of initialization than you can get
> from elaboration. Every time I've violated this rule, I've always ended up
> changing the code to use explicit initialization routines. (I just finished
> doing that to the Trash Finder spam filter, grumble, grumble. In this case,
> it was a need to be able to reinitialize parameters if they change; that's
> very common.)

Reinitializing is an interesting issue.  But anyway, moving elab code into an
initialization procedure doesn't really change things.  In the former case,
you need a comment saying "all clients should pragma Elab_All this", and in
the other case, "all clients should call Init first".

>...Depending on elaboration is a fool's game; I don't think your
> "rational elaboration" would change that any -- even decoupling visibility
> and elaboration doesn't really help, because you still don't have any way to
> indicate the dynamic dependencies (i.e. unit A must not start its tasks
> before unit B has finished loading the parameter set).

My language doesn't activate tasks the same way Ada does, but if it did, I
don't see a problem -- if you activate a task at elab time, that's just like
calling a procedure at elab time, and the same rules work.

This is an interesting discussion to me, whether or not it has anything to do
with Ada.  ;-)

****************************************************************

From: Martin Dowie
Date: Tuesday, May  9, 2006  3:20 PM

> But note that if you put "use Claw" in the root
> module of your subsystem,
> you're not dragging in all of Claw -- just the root
> module, which is probably
> empty, or nearly so.

Presumably your dragging in everything that the root
module Claw "use"s too? And whatever that/those 'use',
and so on?

If it's anything like Ada dependencies, it could be a
nightmare. Just removing something like Ada.Text_IO
from a final link image can be tough - even when it's
not directly referenced!

****************************************************************

From: Randy Brukardt
Date: Wednesday, May 10, 2006  4:42 PM

...
> > Yes, there has to be some way to accomplish this sort of design. What it is,
> > I leave to the language designer.
>
> I'll have to think about this some more.  Thanks for your comments.
>
> But note that if you put "use Claw" in the root module of your subsystem,
> you're not dragging in all of Claw -- just the root module, which
> is probably empty, or nearly so.

The spec and body of the root package of Claw together are more than 10,000
lines of code. ;-)

Some of that is caused by the limitations of Ada 95 (no limited with, so we
had to jam all of the root types together). But a lot of it comes because
there are active things (the message loop task for one) that have to be
loaded and running first in *every* Claw program. If you try to put those
elsewhere than the root, you have to use (in your terminology) them in
*every* possible module that could be used. That violates the principle of
"with only what you actually use", and opens the door for mistakes ("I never
thought that someone would use the flubber widget without a green
framstat!!").

It took a lot of work to get Claw's elaboration to be transparent to the
clients, but we managed it. (We felt that an initialization call would be
missed often, and repeatedly telling programmers to call such a thing seemed
like a support headache in the making. I'm not sure I'd bother with that
design if I was doing it over...)

...
> > Well, clearly in a Duffable unit, Q has to be marked "forward", and then the
> > use is clearly illegal.
> >
> > Yes, you can't export X in a Duffable unit, but so what?
>
> So don't call it "Duffable", because I don't endorse this sort of
> restriction.

Got an alternative? I can't think of anything sensible (and surely
"Duffable" isn't sensible, either).

> >... The idea is to make
> > *most* units safer; clearly the current Ada rules would still exist for
> > compatibility, and you could still use them if you wanted.
>
> OK, but can you mix the current Ada rules with the safer rules in the same
> program?

I think so; we certainly do something similar with Preelaborate. It might be
necessary to restrict "Duffable" units to only "with" Duffable,
Preelaborate, and Pure units -- but that's only a major problem if we can't
get the runtime to be Duffable. (It's that problem that makes Preelaborate
less useful -- without Calendar and Text_IO, I can't do much. Otherwise, I'd
make most of my units Preelaborate...)

> >... And you can
> > always export X as a function/procedure pair (and that is often preferred
> > anyway).
>
> Here, you're making a virtue of necessity.  ;-)
>
> I want the programmer to decide whether this "function/procedure pair" style
> is preferred, without being forced by arbitrary language rules.

I understand. OTOH, global variables are *always* suspicious, so if the
rules make them impossible in some case, I can't get too concerned. Global
constants aren't so suspicious, so that's a more realistic concern.

...
> > Y wouldn't have to refer to X as "forward", because the declaration is given
> > before.
>
> No, X comes after in the body.  That's what matters.

Well, not in the example I gave. You moved it when you were discussing
stuff, but I was taking the original Ada code and just concating it into a
"Duff" body. That doesn't make things move!

I certainly agree if you move things around, the effects change. But it's
hardly the case that Ada stuff is going to move, and I was trying to explore
what would happen if these rules were applied to (restricted) Ada units.

...
> > Hard to say for sure without trying realistic examples.
>
> Seems to me my example was pretty realistic.  Or at least reasonable.

By "realistic", I meant trying to write real programs/libraries with it,
like Claw or the spam filter. It's real easy to dream up examples that look
like they might happen that really don't come up in practice. (Such as using
global variables in a spec.)

...
> > > I don't see how, without further restricting Ada, which is already too
> > > restrictive (e.g. you can't do the example I mentioned before,
> > > of writing "Empty: constant T := Make_T(Size => 0);" in the same
> > > package spec where Make_T is declared).
> >
> > What's wrong with further restricting Ada? I can't remember a time when I've
> > actually had a problem with the restrictions of Ada. (Maybe I'm just losing
> > my memory, though. :-0)
>
> I can remember wanting to do the "Empty" thing above numerous times, and been
> frustrated that it doesn't work.  And wanting to instantiate the "Sequences"
> package to make a seq-of-T in the same package where T is declared (remember
> that argument?).

Well, I don't remember ever running into either of those. Which is why I
tossed in the "losing my memory" comment. (And surely I've heard your
laments on this before; but I don't find your experience that typical.)

...
> > > It's certainly possible to have a fully static (link time) method in Ada.
> > > GNAT does it, but I've used that, and ran into annoying restrictions (rarely).
> > > And it doesn't have all the desirable properties (e.g. we can prevent calls
> > > before the body is elab'ed, but we can't prevent uninitialized variables).
> >
> > Nothing can prevent uninitialized variables in Ada:
> >    A : Integer;
> > so I presume you mean something else.
>
> I mean reading the value of an uninit var.  Ada says that's a bounded error
> (in the case of scalars) or well-defined (in the case of access types --
> return null) or meaningless (in the case of records, there's no concept of
> uninit).

Exactly; there is no way to prevent that in Ada; it's an accepted part of
the language. And it seems almost required to allow partially uninitted
composite objects -- which makes any attempt to detect that statically a
fools game at best. (You've complained in the past about the cost of
initializing bounded strings; surely we wouldn't want to require that sort
of overhead for all types?)

> It seems to me that reading uninit vars is essentially the same issue as
> calling a not-yet-elab'ed procedure, which is is essentially the same issue as
> creating an object of a private type before the full type has been elab'ed.

Only if you are thinking of a "chance" to initialize, as opposed to actually
knowing that it is initialized. The latter is pretty much unknowable, as it
depends on the execution of the program and the intentions of the
programmer. (Think parameters that get initialized -- or not -- from a file
structured as name/value pairs. How can you tell if the file actually
contained pairs for all of the parameters?)

> >... Perhaps you mean premature use of
> > variables that are initialized at elaboration time? That's just a symptom of
> > bad design. Randy's Ada rule number 1: Never, ever do anything significant
> > at elaboration time, because it will *always* come back to bite you. You
> > always need more control over the order of initialization than you can get
> > from elaboration. Every time I've violated this rule, I've always ended up
> > changing the code to use explicit initialization routines. (I just finished
> > doing that to the Trash Finder spam filter, grumble, grumble. In this case,
> > it was a need to be able to reinitialize parameters if they change; that's
> > very common.)
>
> Reinitializing is an interesting issue.  But anyway, moving elab code into an
> initialization procedure doesn't really change things.  In the former case,
> you need a comment saying "all clients should pragma Elab_All this", and in
> the other case, "all clients should call Init first".

Correct. Since doing this properly depends on the dynamic properties of the
program, it can't reasonably be done statically. If you try, all that you
get is a lot of "phony" initializations before the real ones...

> >...Depending on elaboration is a fool's game; I don't think your
> > "rational elaboration" would change that any -- even decoupling visibility
> > and elaboration doesn't really help, because you still don't have any way to
> > indicate the dynamic dependencies (i.e. unit A must not start its tasks
> > before unit B has finished loading the parameter set).
>
> My language doesn't activate tasks the same way Ada does, but if it did, I
> don't see a problem -- if you activate a task at elab time, that's just like
> calling a procedure at elab time, and the same rules work.

Or don't work. A may not even know about B's existence, but may still have
to start after it. (What if B is a filter designed to preprocess the
pre-existing A's inputs??) It might be inappropriate to modify A, especially
if it is shared with other programs that don't want to use B.

(The need to shut these systems down gracefully also complicates the
problems of start-up; things that run forever logically are easier to deal
with.)

I don't see any way that static elaboration can deal with these sorts of
problems; ones that come up frequently in real systems.

> This is an interesting discussion to me, whether or not it has anything to do
> with Ada.  ;-)

Yes, but we both probably ought to do some work, too. :-)

****************************************************************


Questions? Ask the ACAA Technical Agent