!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] ", 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. :-) ****************************************************************