!standard 6.01 (18) 03-01-22 AI95-00323/01 !standard 6.06 (03) !standard 9.5.1 (02) !class amendment 03-01-23 !status No Action (7-1-0) 03-02-08 !status work item 03-01-23 !status received 03-01-23 !priority High !difficulty Easy !subject Allow in out parameters for functions. !summary Parameters of all modes are allowed in functions. !problem The restriction on parameter modes for functions is an embarassing wart on the language. Ada functions can have arbitrary side-effects, but are not allowed to announce that in their specifications. When constructing reusable object-oriented abstractions, it is important to allow future extensions to have the maximum flexibility. This means that parameters of object-oriented types should generally be declared as in out, so that future extensions can modify the parameter's contents if necessary. However, this prevents using functions for most operations. If there is any conceptual chance of an extension needing to modify its argument, then a function cannot be used. Even for operations that are conceptually pure, caching of results may require modification of extensions. If an unconstrained type (such as String) needs to be returned, there is no real alternative to using a function. When this problem occurs, only three solutions are possible: -- Abandon functions; -- Use the functions, leaving the onus of solving the problem on extensions. -- Use an access parameter. For the second solution, the author of an extension would have to allocate (probably from the heap) a separate, writable object, and include an access to it in the extension. This means the author now has to handle memory management (with all of its possibilities for errors, like storage leaks and double freeing) which they previously did not have to do. Access parameters are a very flawed solution as well. Access parameters require altering the calling syntax of calls to the function (to include an approriate 'Access, or to remove a dereference). They also require altering the declaration of the object to be passed (to include "aliased"). Finally, access parameters are more expensive at run-time than regular parameter passing, as they are required to include a run-tine indication of their accessibility level. The run-time check can even be dangerous, as the check may fail only with arguments declared in nested scopes, and that may not happen in unit testing. For all of these reasons, the restriction on parameter modes for functions has been removed. !proposal (See summary.) !wording The last sentence of 6.1(18) is deleted. Add "of mode in" to 6.6(3): The subprogram_specification of a unary or binary operator shall have one or two parameters of mode in, respectively. A generic function instantiation whose designator is an operator_symbol is only allowed if the specification of the generic function has the corresponding number of parameters of mode in. Replace 9.5.1(2) by: Within the body of a protected function (or a function declared immediately within a protected_body), all of whose parameters have mode in, the current instance of the enclosing protected unit is defined to be a constant [(that is, its subcomponents may be read but not updated)]. Within the body of a protected subprogram other than a function with parameters of mode in (or a such a subprogram declared immediately within a protected_body), and within an entry_body, the current instance is defined to be a variable [(updating is permitted)]. !discussion This is primarily a political issue, rather than a technical one. Access parameters act very similarly to in out parameters on functions, they just provide a much less convenient syntax and are less efficient. Two additional issues are addressed by this change. Operator symbols are conceptually functions with one or two in parameters. We do not want infix expressions to have side effects on their arguments. Thus, we add a rule restricting operator symbols to parameters of mode in. The unfortunate tying of read-only vs. read-write access to a protected object to the use of a function or procedure also needs to be changed. We've used the minimal fix here, which is to say that functions with in out or out parameters have read-write access to the protected object. A better solution would be to add a modifier similar to that used for overriding to determine whether a subprogram has read-only versus read-write access. However, that was rejected as being too heavy of a solution, particularly as Ada compilers rarely take advantage of read-only protected object access. !example In the Claw Builder, we have a function that can return an access to the actual Claw object for simulation. This function is usually used to directly call a dispatching Claw operation: Claw.Move (Get_Claw_Object (My_Button).all, ); Because the access returned is never used for more than a single subprogram, we implemented the function with Unchecked_Access: function Get_Claw_Object (Window : in Button_Type) return Claw.Any_Window_Access_Type is begin return Window.My_Button'Unchecked_Access; end Get_Claw_Object; This code is illegal, of course, as Window.My_Button is a constant, and Any_Window_Access_Type is an access-to-variable. Since Claw.Move takes an in out parameter, changing Any_Window_Access_Type is out of the question. Changing this to the "obvious" implementation: function Get_Claw_Object (Window : access Button_Type) return Claw.Any_Window_Access_Type is begin return Window.My_Button'Unchecked_Access; end Get_Claw_Object; means that calling objects have to be declared as aliased, and an additional 'Access used. Moreover, there is the run-time overhead of passing an accessibility level, even though it will never be used in this case. What we really want, of course, is to write: function Get_Claw_Object (Window : in out Button_Type) return Claw.Any_Window_Access_Type is begin return Window.My_Button'Unchecked_Access; end Get_Claw_Object; which we can now do. Conceptually, of course, the parameter to this function isn't even modified. We just need to use in out here to prevent someone from passing a constant, which could cause all manner of trouble. !ACATS test ACATS tests are needed to test that this is allowed, and that the restrictions on operator symbols are enforced. Moreover, existing ACATS B-Test tests prohibiting in out parameters on function needs to be withdrawn. !appendix From: Robert A. Duff Sent: Saturday, November 9, 2002 11:36 AM Robert Dewar said: > As for Bob's :-( lament about not being able to build abstractions in Ada, > I think that's overdrawn. Indeed there is a real danger in languages that > allow too much syntactic abstraction, because then people start to essentially > create sublanguages that are out of style with Ada. My philosopy is: 1. Make it easy to write good programs. 2. Make it hard to write bad programs BY ACCIDENT. but not: 3. Make it hard to write bad programs. The reason I reject number (3) is that it inevitably makes it hard to write *good* programs as well. For instance, number (3) may well discourage "sublanguages that are out of style with Ada". But it also discourages the example you mentioned in an earlier message: making an array-like abstraction. A good example is the Unbounded_Strings package. IMHO, it should be possible to make the interface to that be just as clean as the interface to Standard.String. That requires allowing user-defined: literal notation indexing notation assignment operators (like "=" and "&") Ada draws the line at an odd spot -- it allows the last item above, but not the others. It is true that these kinds of features allow programmers to write bad programs, through stupidity, ignorance, or even malice. But not "by accident". To redefine "+" to mean "-", you have to do it on purpose. Note that I would not push this all the way. For example, I am happy that the syntax for 'if' and 'while' statements is built-in to Ada, and cannot be changed by programmers. It *could* be -- you can do that sort of thing in Lisp. In Lisp, you even have hooks into the lexical analysis. But that's going too far, IMHO. > An example of doing this is GNAT.Spitbol.Patterns, but this is a very > concious effort to create an abstraction that has a SNOBOL4 rather than > an Ada flavor. I think that's not so bad in the case where you are mimicking > an existing language. > > But suppose this package instead mimiced "dewars own not very well documented > and not very well thoughout language xxx". Well that's not such a good idea > at all. True, but I don't want to make the Spitbol package more difficult to write, or make its interface less clean, just because loose-canon Dewar might foolishly define the horrible "language xxx" abstraction. The solution must better education (teach that Dewar to be a better programmer). ;-) I'm a language designer, not an educator, so I have little to say about that. > For example, suppose I allow := to be redesigned quite generally, and then, > being a functional programming fan, I decide to make it's semantics equivalent > to LET in scheme, rather than an assignment. I think that would be rather > horrible :-) Well, we *do* let you redefine := indirectly via Adjust, and you can indeed redefine it to do crazy things. P.S. Sorry for straying off topic. I can't help but discuss language-design philosophy, given that's it my main interest, and I'm forced to make a living writing mere compilers. ;-) **************************************************************** From: Robert Dewar Sent: Saturday, November 9, 2002 12:09 PM > . Make it hard to write bad programs. Well given that a lot of the Ada design philosophy is EXACTLY about this I find this unacceptable as a general principle. For example, we don't allow people to do Unchecked_Conversions without saying a lot of stuff. The fact of the matter is that most bad programming is NOT by accident, it is by poorly-intentioned design. And yes, one function of the design of a programming language is to try to disrupt this activity as much as possible without getting too much in the way of legitimate stuff. Certainly I agree with Bob in certain cases, I just don't accept it as a general principle (once again, Bob, you seem to be stating this as a general principle, and it is that generality I object to). For example, suppose I introduce an attribute that can be applied to a private type or object called 'Implementation, which reveals the underlying implementation and allows the privacy barrier to be broken. That does not make it easier to write bad programs by accident, no one can accidentally type those 15 characters, but it sure makes it easier to write bad programs by design, and although this pragma is indeed legitimate according to the standard, it would represent a horrible attack on the integrity of the language and I assume (hope?) that Bob would not be happy with it. On the other hand, one of the things that the SPIBOL patterns implementation has to work around (*) is the stupid rule that functions can't have IN OUT parameters which to me is definitely in the category of trying to stop people writing bad programs at the expense of all kinds of legitimate use (you see here the problem is that I don't agree it is a terrible thing for functions in Ada to have IN OUT parameters, in fact it seems rather natural for example for a random number function to take a seeded object that is updated, rather than have to mess with generic object nonsense. So once again, I think that all three principles ennunciated by Bob are not absolute, they represent extremes in the design space and you have to find the best spot. I incidentally find the Ada decisions of where to cut the abstraction quite reasonable (they correspond incidentally to the choices made for Algol-68 and the reasoning is simlar -- parts of Ada were quite strongly inspired by A68). (*) what is done in the spitbol patterns implementation is to force the relevant parameters to be passed by reference, then take the parameter address by use of Unrestricted_Access, then to change the parameter by assignment via the pointer, YUGH! But it works fine and presents a much more spitbol like language the pattern span (a) $ b (which matches a bunch of a's and assigs the substring matched to b) becomes span (a) * b (pity I can't use $ as an operator :-) **************************************************************** From: Robert A. Duff Sent: Saturday, November 9, 2002 12:48 PM > > . Make it hard to write bad programs. > > Well given that a lot of the Ada design philosophy is EXACTLY about this > I find this unacceptable as a general principle. > > For example, we don't allow people to do Unchecked_Conversions without > saying a lot of stuff. I agree with that design decision. I think it fits in with my principle: you can't do it by accident. It certainly doesn't stop menaces from sprinkling U_C all over the place. I've seen programs that do that, and the extra typing work didn't stop the menace. This decision also reflects another principle I didn't mention, which I know you agree with: Force programmers to document what they're doing, especially when they're doing dangerous stuff. This is the "readability over writeability" principle, which I've heard you preaching in favor of. Saying "with U_C" at the top of the file is good documentation. > The fact of the matter is that most bad programming is NOT by accident, > it is by poorly-intentioned design. And yes, one function of the design > of a programming language is to try to disrupt this activity as much > as possible without getting too much in the way of legitimate stuff. Well, I don't know about that. I've seen many many bugs in my life, and an awful lot of them were just silly little mistakes. More in, say, C than in, say, Ada. > Certainly I agree with Bob in certain cases, I just don't accept it as > a general principle (once again, Bob, you seem to be stating this as a > general principle, and it is that generality I object to). *This* time, I really meant it to be fairly general. I'm sure there are exceptions, but by and large, I think it is wise for the language designer to avoid heavy-handed attempts to prevent stupid programmers from doing stupid things. You're a professor. It's *your* job to educate new programmers, not mine. ;-) Although I suppose there's always the School of Hard Knocks. ;-) > For example, suppose I introduce an attribute that can be applied to a > private type or object called 'Implementation, which reveals the underlying > implementation and allows the privacy barrier to be broken. That does not > make it easier to write bad programs by accident, no one can accidentally > type those 15 characters, but it sure makes it easier to write bad programs > by design, and although this pragma is indeed legitimate according to the > standard, it would represent a horrible attack on the integrity of the > language and I assume (hope?) that Bob would not be happy with it. I agree (I would not be happy with that feature). I can use the "readability" principle to back me up: "is private" along with some procedures is good documentation of an interface. That property would be destroyed if 'Implementation could be sprinkled all over. Even if 'Implementation were not used in a given program, it would still do damage, because the reader has to search all over to know that fact. > On the other hand, one of the things that the SPIBOL patterns implementation > has to work around (*) is the stupid rule that functions can't have IN OUT > parameters which to me is definitely in the category of trying to stop people > writing bad programs at the expense of all kinds of legitimate use (you see > here the problem is that I don't agree it is a terrible thing for functions > in Ada to have IN OUT parameters, in fact it seems rather natural for example > for a random number function to take a seeded object that is updated, rather > than have to mess with generic object nonsense. I've said before: I agree with you on the IN OUT parameters in functions point. > So once again, I think that all three principles ennunciated by Bob are > not absolute, they represent extremes in the design space and you have to > find the best spot. Well, yes, I agree that no principle is absolute. Including *this* one? ;-) > I incidentally find the Ada decisions of where to cut the abstraction quite > reasonable (they correspond incidentally to the choices made for Algol-68 > and the reasoning is simlar -- parts of Ada were quite strongly inspired > by A68). > > (*) what is done in the spitbol patterns implementation is to force the > relevant parameters to be passed by reference, then take the parameter > address by use of Unrestricted_Access, then to change the parameter by > assignment via the pointer, YUGH! This is a good example of a well-intentioned rule that actually damages readability, because the workaround is uglier than just saying "in out". By the way, if you make the thing tagged, you can say 'Unchecked_Access, rather than 'Unrestricted_Access. (Which I'm sure doesn't matter in *your* case, since you control the compiler.) Here's a similar trick: type T is limited private; ... type T limited record Self: T_Ptr := T'Unchecked_Access; (presumably with a hefty commment explaining what's going on!) Then a function with an 'in' parameter X of type T can say: Var: T renames X.Self.all; and thereby evilly gain write access to X. Yuck! But it does solve the problem. >... But it works fine and presents a much > more spitbol like language > > the pattern span (a) $ b > > (which matches a bunch of a's and assigs the substring matched to b) > > becomes > > span (a) * b > > (pity I can > t use $ as an operator :-) Yeah. Using $ would be *more* readable. Not just because it mimics the Snobol syntax. Even if you were designing this from scratch without knowledge of Snobol, it's good to use an operator that does not cause confusion with the standard "multiply" meaning. One problem with allowing arbitrary operator symbols like $ is how to define the precedence. I've seen several error-prone solutions to that in various languages. I think I know a *good* solution, but this is a case where I can fully understand JDI's reluctance to allow it. **************************************************************** From: Robert Dewar Sent: Saturday, November 9, 2002 5:18 PM > > The fact of the matter is that most bad programming is NOT by accident, > > it is by poorly-intentioned design. And yes, one function of the design > > of a programming language is to try to disrupt this activity as much > > as possible without getting too much in the way of legitimate stuff. > > Well, I don't know about that. I've seen many many bugs in my life, and > an awful lot of them were just silly little mistakes. More in, say, > C than in, say, Ada. Well when I talk about "bad programming" I am not talking about bugs, but about bad coding. There is lots of bad coding that is bug-free in the sense that there are no functoinal errors and the code works. I think it worth making a very strong distinction between design errors/bad code, and bugs. It is the function of a language to provide an environment in which both are harder to create while not unduly restricting what you can do. For the most part (IN OUT parameters for functions are a signal exception) I think Ada makes the right choices in this arena. > One problem with allowing arbitrary operator symbols like $ is how to > define the precedence. I've seen several error-prone solutions to that > in various languages. I think I know a *good* solution, but this is a > case where I can fully understand JDI's reluctance to allow it. I assume you know the PRIORITY statement in Algol-68. But I am dubious about this approach. in fact I think it would be just fine to say that these arbitrary operator symbols have no defined precedence and require parenthesization if mixed with any other operators. After all the reader will have no intuition on the precedence of $. **************************************************************** From: Robert Dewar Sent: Sunday, November 10, 2002 2:55 PM > Go ahead and write up an AI. ;-) I would, except that I don't see anything has changed since the Ada9X discussions. Both JDI and I strongly supported introducing one extra user redefinable unary operator, to be used for conversion operations (much as unary "+" is sometimes used now). The argument for this was presented in I think as effective form as possible, but it was not enough to win approval, so I guess it's collectively not viewed as a good idea. A similar sort of appraisal applies to IN OUT parameters for functions. I don't think there is any point in re-introducing an issue that has previously been discussed and resolved one way or another in the context of the Ada 9X discussions unless there is new evidence to bring to bare, or new experience. It is not good enough to simply decide that the old decision was wrong and fight the same fight again (I have that reaction to a number of proposals being discussed for Ada 0Y). **************************************************************** From: Alexander Kopilovitch Sent: Sunday, November 10, 2002 5:21 PM >> Go ahead and write up an AI. ;-) > >I would, except that I don't see anything has changed since the Ada9X >discussions. Both JDI and I strongly supported introducing one extra >user redefinable unary operator, to be used for conversion operations >(much as unary "+" is sometimes used now). Perhaps, even a "stalled" AI for that might be useful: at least it is interesting how such redefinable conversion operator may be introduced without obviously wicked possibilities. >A similar sort of appraisal applies to IN OUT parameters for functions. > >I don't think there is any point in re-introducing an issue that has >previously been discussed and resolved one way or another in the >context of the Ada 9X discussions unless there is new evidence to >bring to bare, or new experience. It is not good enough to simply >decide that the old decision was wrong and fight the same fight >again (I have that reaction to a number of proposals being >discussed for Ada 0Y). Perhaps there is no need to fight, but those arguments for IN OUT parameters for functions (in AI style, not comp.lang.ada) may be insteresting not only for participants of those Ada 9X discussions. In particular, is there any practical need for that, except of well-known need for procedures that return result? **************************************************************** From: Robert Dewar Sent: Sunday, November 10, 2002 5:34 PM > Perhaps, even a "stalled" AI for that might be useful: at least it is > interesting how such redefinable conversion operator may be introduced > without obviously wicked possibilities. Feel free to submit one, I certainly will not :-) > Perhaps there is no need to fight, but those arguments for IN OUT parameters > for functions (in AI style, not comp.lang.ada) may be insteresting not only > for participants of those Ada 9X discussions. In particular, is there any > practical need for that, except of well-known need for procedures that return > result? Sure there are cases, I gave one (the random number example) but there are may others, but again, it is pointless to restate the arguments, since these have been thoroughly presented before, and it would just be treading well known ground. **************************************************************** From: Robert A. Duff Sent: Sunday, November 10, 2002 6:09 PM > I would, except that I don't see anything has changed since the Ada9X > discussions. I agree. (You don't see *me* writing up that AI, either...) But it's funny if you look at some of the AI's, and some of the non-standard features supported by vendors, some of them were dangerous, impossible to implement, and all manner of other bad things in 1993, but somehow over time, people changed their collective mind. In some cases, what seemed like unnecessary bells and whistles then, now seems like filling an obvious "hole". >... Both JDI and I strongly supported introducing one extra > user redefinable unary operator, to be used for conversion operations > (much as unary "+" is sometimes used now). The argument for this was > presented in I think as effective form as possible, but it was not > enough to win approval, so I guess it's collectively not viewed as a > good idea. Well, the details matter. For example, I can't get very excited about the single "conversion" operator symbol, whereas a more general feature would be of more interest to me. (Assuming, for either feature, that there was more general support -- I'm not going to push for either one.) > A similar sort of appraisal applies to IN OUT parameters for functions. That's a more important bug in the language design, but I agree there's no point in continuing to push for it. By the way, this is one of the few language design decisions that Tucker and I disagreed on (and still do). ;-) **************************************************************** From: Robert Dewar Sent: Sunday, November 10, 2002 6:48 PM Right, it's quite interesting. Although I have often disagreed with Tucker on scope issues (objecting to features that seemed in context over complicated) this is the only case where I have disagreed with him on a fundamental judgment issue. very odd :-) :-) **************************************************************** From: Alexandre E. Kopilovitch Sent: Wednesday, November 13, 2002 12:11 PM I'm not going to restate the arguments, but just want to know what is bad or wrong in the following solution for a problem: ---------- Let us introduce new mode for formal parameter: RETURN mode. This RETURN mode is the same as OUT mode except the following rules: in the procedure specification: 1) only the first parameter of the procedure may be in RETURN mode; 2) IN mode is incompatible with RETURN mode, that is, there is no IN RETURN mode. in the procedure body: if the formal parameter in RETURN mode is of indefinite type then the attributes, which depend on undefined properties (for example, 'First, 'Length, etc.) cannot be used for that parameter. in a procedure call: 1) a procedure with the first parameter in RETURN mode may be invoked as function (without argument for the first parameter); 2) normal (that is, procedural) call for that procedure is also available; in that call RETURN mode is treated as OUT mode. **************************************************************** From: Robert Dewar Sent: Wednesday, November 13, 2002 11:17 PM This is a warmed over version of the DEC Valued_Procedure pragmas ... **************************************************************** From: Alexandre E. Kopilovitch Sent: Thursday, November 14, 2002 10:35 AM Well, of course, and GNAT implements those pragmas also. But this similarity is a good thing, as it certifies for a proven mechamism. The difference is that it is not an implementation-defined pragma(s), but a straight language form. **************************************************************** From: Robert Dewar Sent: Thursday, November 14, 2002 5:17 PM Well I regard both the pragmas and the AK proposed substitute as too junky to consider. If you want IN OUT parameters for functions, just allow them. The DEC pragmas are merely a kludgy device to get around the absence of a needed feature. I don't see the AK proposal here as any improvement over the pragmas. **************************************************************** From: Robert A. Duff Sent: Thursday, November 14, 2002 6:07 PM Robert Dewar said: Well, "just allow them" would work for Ada 83. But now Ada 95 has protected functions, which have special status. Do you think it makes sense to allow 'in out' in protected functions, given their special status as "multiple readers"? I really think it would have been better to use some other syntax to distinguish readers/writers of protected objects. The function/procedure distinction was a mistake, IMHO. (As I said, one of the *very* few technical disagreements between Tuck and me!) Perhaps it was driven by the "new reserved word phobia" that plagued the Ada 9X process? I mean, the MRT was allowed to drag in all manner of *semantic* complexity (see finalization, for example), but new syntax was frowned upon, and new reserved words were anathema. I never understood that. A related example: The MRT at one point proposed to allow "aliased" on formal parameters. Like "procedure P(X: aliased Some_Record);". The reviewers put the kibosh on that (new syntax, plus "alias" is an ugly name used by criminals ;-)). Later, we decided that *all* tagged parameters are aliased, whether they want to be or not, and that rule made it into the final Ada 95. So I find myself making things tagged just so I can have aliased parameters of the type, and I resent the extra tag field. Besides, I didn't want *all* parameters of that type to be aliased. Just some. **************************************************************** From: Randy Brukardt Sent: Thursday, November 14, 2002 6:44 PM > Let us introduce new mode for formal parameter: RETURN mode. > This RETURN mode is the same as OUT mode except the following rules: >... This basic idea might serve as a solution to the "constructor function" problem that Bob Duff is working in AI-318. The problem with the existing proposals for that were that the methods of getting a name for the return result were just plain weird - objects with boatloads of restrictions. It may be easier to define the needed restrictions in terms of a parameter mode. OTOH, I don't see any reason to use it as a back door to getting 'In Out' parameters on functions. If that happens, it should be through the front door. (I know that allowing 'In Out's on functions would make a real hash out of the Janus/Ada optimizer and code generators, which use the 'no need to access the parameters after the call' as a fundimental invariant. But I'm still in favor of allowing them. As I've said previously, I was very against the change in Ada 95, for the optimization/code generator reasons, so I can call for reopening the issue. Which I have. The reason is that 'In Out' parameters are fundemental to OOP, and 'Access' is a poor substitute (as the fact that the parameter is access is visible at the call site). **************************************************************** From: Robert Dewar Sent: Thursday, November 14, 2002 9:30 PM > Well, "just allow them" would work for Ada 83. But now Ada 95 has > protected functions, which have special status. Do you think it makes > sense to allow 'in out' in protected functions, given their special > status as "multiple readers"? True, this nasty mistake in the Ada 95 design (one if you remember I strongly opposed) make the change more difficult. > I really think it would have been better to use some other syntax to > distinguish readers/writers of protected objects. The > function/procedure distinction was a mistake, IMHO. (As I said, one of > the *very* few technical disagreements between Tuck and me!) Abnsolutely! This is part and parcel of the same odd technical position :-) > A related example: The MRT at one point proposed to allow "aliased" on > formal parameters. Like "procedure P(X: aliased Some_Record);". The > reviewers put the kibosh on that (new syntax, plus "alias" is an ugly > name used by criminals ;-)). Later, we decided that *all* tagged > parameters are aliased, whether they want to be or not, and that rule > made it into the final Ada 95. So I find myself making things tagged > just so I can have aliased parameters of the type, and I resent the > extra tag field. Besides, I didn't want *all* parameters of that type > to be aliased. Just some. I would find it difficult to live without 'Unrestricted_Access :-) **************************************************************** From: Michael F. Yoder Sent: Friday, November 15, 2002 10:23 AM The decision could be escaped in an upwardly compatible manner by making readerhood and writerhood be default conditions that could be overridden. (I won't suggest any syntax.) And if 'out' and 'in out' parameters were allowed in functions, the default could be to make only functions lacking such parameters be readers. (That would seem kludgy to me, but it would be compatible with legacy code and wouldn't require adding extra syntax or pragmas to override the defaults.) **************************************************************** From: Robert Dewar Sent: Friday, November 15, 2002 10:34 AM Possibly so. The real point here is that I see no new arguments and no need to revisit the issue. Given that there is nothing new, and that it has been discussed to death before, and there has never been even a simple majority let alone a consensus in favor of IN OUT parameters for functions, I think it is a dead issue. **************************************************************** From: Alexandre E. Kopilovitch Sent: Friday, November 15, 2002 3:22 PM >OTOH, I don't see any reason to use it as a back door to getting 'In Out' >parameters on functions. If that happens, it should be through the front >door. I believe that both these doors - front and back - should be present because each of them has its own use. For example, for a subroutine (say, within a bindings) that returns error code I prefer valued procedure. But for random number generator I certainly prefer function with IN OUT parameter. **************************************************************** From: Robert Dewar Sent: Friday, November 15, 2002 5:11 PM Ah, the irresistable taste for features and more features :-) Anyway, I repeat at this stage I would oppose any change in this area, because I do not believe it can possibly get consensus support. **************************************************************** From: Florian Weimer Sent: Friday, November 15, 2002 4:31 PM > Well, "just allow them" would work for Ada 83. But now Ada 95 has > protected functions, which have special status. Do you think it makes > sense to allow 'in out' in protected functions, given their special > status as "multiple readers"? Why would that be a problem? If the mode is "out" or "in out", this doesn't automatically imply that the function changes the state of the protected object. Or am I missing something? **************************************************************** From: Robert A. Duff Sent: Friday, November 15, 2002 4:45 PM I'm not sure. Sometimes PO's protect data that is not inside the PO. **************************************************************** From: Robert Dewar Sent: Friday, November 15, 2002 5:25 PM Well no of course it doesn't, but it makes this non-orthogonal and rather weird treatment fo functions in protected objects one notch more peculiar if you admit that functions really aren't mathematical functions :-) **************************************************************** From: Robert Dewar Sent: Friday, November 15, 2002 5:28 PM > I'm not sure. Sometimes PO's protect data that is not inside the PO. Sure, but functions aren't really functions in Ada, no matter how much some people :-) would like to think that. They are procedures with a) a silly restriction on IN OUT parameters b) a method of returning a result They can do anything they want by calling other routines, messing with address clauses, applying 'Access to their tagged parameters, modifying things pointed to by access parameters etc. So allowing IN OUT parameters does not actually change things. The rule in Ada is quite simple. Functions are allowed to have arbitrary side effects, but are not allowed to indicate any such side effects int the specification :-) **************************************************************** From: Alexandre E. Kopilovitch Sent: Sunday, November 17, 2002 11:42 AM >I do not believe it can possibly get consensus support. Lacking a list of arguments against IN OUT in functions, I only guess that perhaps following restrictions will satisfy the opponents: 1) function with IN OUT parameter cannot be operator (and cannot be renamed to operator) 2) actual argument for IN OUT formal parameter cannot be used in any place within the whole statement. >functions aren't really functions in Ada, no matter how much >some people :-) would like to think that. Well, even in pure mathematics functions sometimes have effectively OUT parameters - when used in equations (with a suitable form of equation it is possible to simulate IN OUT parameter also). **************************************************************** From: Robert Dewar Sent: Sunday, November 17, 2002 11:48 AM You guess completely wrong, you should really make an effort to dig up the old threads on this subject. There is no point in wasting time going over an argument again just because a new arguer has arrived on the scene :-) The objection is that IN OUT parameters are fundamentally inappropriate for functions. **************************************************************** From: Matthew Heaney Sent: Monday, November 18, 2002 5:12 PM > But for random number generator I certainly > prefer function with IN OUT parameter. If the full view of the type is limited, you don't need inout parameters. The predefined random number Generator type works just fine, without inout params. (My only complaint is that that package isn't pure.) If your only concern is being able to have side effect for functions, then inout params aren't necessary (if the type is limited). **************************************************************** From: Robert A. Duff Sent: Monday, November 18, 2002 6:24 PM > If the full view of the type is limited, you don't need inout parameters. Only via ugly and inefficient kludgery. > The predefined random number Generator type works just fine, without > inout params. (My only complaint is that that package isn't pure.) I don't see how the package could be Pure. If it were, then: X: T := Random(...); Y: T := Random(...); the compiler would be allowed to optimize away one of the calls, thus making X = Y, which is clearly *not* what random number generators are supposed to do! > If your only concern is being able to have side effect for functions, > then inout params aren't necessary (if the type is limited). My concern is to document the 'in out'-ness in the spec of the function. Also, I don't like the "limited" restriction. Sometimes I make a conceptually-limited type nonlimited, purely to enable aggregates (which ought to be allowed for limited types, but are not -- see AI-287). By the way, making it tagged can also work. **************************************************************** From: Robert Dewar Sent: Monday, November 18, 2002 9:07 PM You had better post code. I can't guess what you mean by "works just fine". **************************************************************** From: Robert A. Duff Sent: Tuesday, November 19, 2002 8:09 PM The GNAT version says: > -- The design of this spec is very awkward, as a result of Ada 95 not > -- permitting in-out parameters for function formals (most naturally > -- Generator values would be passed this way). In pure Ada 95, the only > -- solution is to use the heap and pointers, and, to avoid memory leaks, > -- controlled types. > > -- This is awfully heavy, so what we do is to use Unrestricted_Access to > -- get a pointer to the state in the passed Generator. This works because > -- Generator is a limited type and will thus always be passed by reference. The part about "awkward" is correct, but the part about the "only way" being heap usage is wrong. I don't know what Matthew Heaney had in mind, but the trick I sometimes use is this: In private part of random numbers package: type Generator_Ptr is access all Generator; type Generator is limited record Self: Generator_Ptr := Generator'Unchecked_Access; ... other fields end record; In body: function Random(Gen: Generator) return ... is Variable_Generator: Generator renames Gen.Self.all; begin Variable_Generator.Something := ...; ... end Random; Variable_Generator is a writeable view of Gen. Whether this is, as Matthew claims, "just fine", is a matter of opinion, I suppose. It is at least pure, portable, Ada 95. I don't like it because: - It's tricky. - It requires the type to be limited -- the 'Unchecked_Access would be illegal otherwise. - It wastes a word of storage. That's fine in this case (I suspect the average number of Generator objects in programs that use random numbers is approximately 1). But if I'm creating a million Things, and each is 2 words, I don't want to add an extra Self field to type Thing. - (Most importantly) The spec of function Random is a lie! Apparently, the author of the GNAT version didn't know about this trick. Apparently, the MRT didn't know about it either, when the RM was written, because the AARM suggests a heap-based implementation. In fact, that AARM comment was wrong; the version-with-corrigendum fixes it (to a different heap-based implementation). **************************************************************** From: Robert Dewar Sent: Tuesday, November 19, 2002 8:32 AM But the GNAT code is clearly superior, because it avoids the extra word by a (safe) use of unrestricted access. The GNAT run-time definitely is not written with portability to other GNAT compilers in mind :-) **************************************************************** From: Robert A. Duff Sent: Tuesday, November 19, 2002 8:44 AM It is perhaps be superior as a part of GNAT, but that's irrelevant in a discussion of the Ada language (as opposed to the GNAT dialect of Ada). Of course, the 'Unrestricted_Access attribute can easily be replaced by an Unchecked_Conversion. I still prefer the "Self" trick, unless you're going to be creating a lot of objects of the type. And *all* of these tricks, including the 'Unrestricted_Access, are inferior to simply saying "in out". Except for one minor flaw -- it's illegal. ;-) **************************************************************** From: Robert Dewar Sent: Tuesday, November 19, 2002 8:54 AM > It is perhaps be superior as a part of GNAT, but that's irrelevant in a > discussion of the Ada language (as opposed to the GNAT dialect of Ada). Well you commented that the GNAT author's apparently did not know the trick, but in fact we did and conciously preferred the implementation we have :-) > And *all* of these tricks, including the 'Unrestricted_Access, are > inferior to simply saying "in out". Except for one minor flaw -- it's > illegal. ;-) Indeed :-) > Of course, the 'Unrestricted_Access attribute can easily be replaced by > an Unchecked_Conversion. I still prefer the "Self" trick, unless you're > going to be creating a lot of objects of the type. Yes, that's always the case, for pointer types *other* than pointers to unconstrained arrays, the Unrestricted_Access attribute is simply equivalent to 'Address followed by an unchecked conversoin. However, for pointers to unconstrained arrays, this attribute is much more interesting :-) For example, you can create a pointer to a slice with proper bounds. That of course is *very* implementation dependent (it depends on the use of fat pointers). **************************************************************** From: Matthew Heaney Sent: Tuesday, November 19, 2002 10:32 AM > > If the full view of the type is limited, you don't need > inout parameters. > > Only via ugly and inefficient kludgery. I don't know if this implementation falls into the "ugly and inefficient" category, but here it is: package Random_Numbers is pragma Pure; type Generator is limited private; function Random (G : Generator) return Float; private type State_Type is record ... end record; type Handle (G : access Generator) is limited null record; type Generator is limited record H : Handle_Type (Generator'Access); --per Jean-Pierre State : State_Type; end record; end Random_Numbers; Now you can implement function Random like this: function Random (G : Generator) return Float is State : State_Type renames G.H.G.State; begin return Result; end; > > The predefined random number Generator type works just fine, without > > inout params. (My only complaint is that that package isn't pure.) > > I don't see how the package could be Pure. Because there are no access types. > If it were, then: > > X: T := Random(...); > Y: T := Random(...); > > the compiler would be allowed to optimize away one of the calls, > thus making X = Y, which is clearly *not* what random number > generators > are supposed to do! Well then something is amiss, because the code fragment above will compile just fine. > > If your only concern is being able to have side effect for functions, > > then inout params aren't necessary (if the type is limited). > > My concern is to document the 'in out'-ness in the spec of > the function. But not all abstractions work that way -- specifically, the random number package is *not* modelled that way. This is the same for the predefined packages, which use mode in (not inout) for Read and Write. The model is that the type is really a "handle," which designates some modifiable, changing state, but which itself (the handle) is constant. Hence only mode in is required. In the code fragment above, the "handle" and the "modifiable state" are allocated immediately adjacent to one another, on the stack. (This view of things is per Tucker.) If you don't like that model, then you could argue that there "physical" state changes that are not part of the "logical" model of the abstraction. For example, if you wanted to instrument a type to keep track of how many times a certain operation (that otherwise does not modify the object) had been called. In C++ you can do this using the "mutable" keyword. The problem with Ada95 is that the compiler has to infer this from the nature of the type (ie implemented using the Rosen Trick, as above, or it's Volatile), rather than being told explicitly be the programmer. > Also, I don't like the "limited" restriction. Sometimes I make a > conceptually-limited type nonlimited, purely to enable aggregates (which > ought to be allowed for limited types, but are not -- see AI-287). Agreed. > By the way, making it tagged can also work. Yes, I knew that, but I wasn't sure what the effect would be if you were to modify a pass-by-reference, nonlimited object through the back door (via some Chap13 mechanism), because nonlimited objects can be declared as constant. For example, if we have this: type T is tagged record ... end record; function Op (O : T) return T2; and then we do this: O : constant T := ; X : T2 := Op (O); What should happen if Op modifies object O? I was nervous about O being allocated in a read-only part of memory, because it is marked as constant. For limited types, I worry less because limited objects can't be declared as constant. Again, C++ solves that problem using the "mutable" keyword, which lets the compiler know that objects of this types can't be allocated in read-only memory. **************************************************************** From: Matthew Heaney Sent: Tuesday, November 19, 2002 10:44 AM > You had better post code. I can't guess what you mean by > "works just fine". I did -- 3 years ago, on CLA. Here was your response: From: Robert Dewar Subject: Re: Array of Variant Records Question... Date: 1999/09/17 In article <37e24575@news1.prserv.net>, "Matthew Heaney" wrote: > So there is in fact an Ada95 locution for "casting away const," a technique > I've been calling "The Rosen Trick." Yes, that's a LOT cleaner than the way that GNAT does it now, though a bit less efficient (GNAT just modifies its parameter directly and does not need the pointer). We probably won't change it since there is no particular point in throwing away the efficiency, especially when it is not broke :-) However, the heap method is clearly junk compared to this! **************************************************************** From: Robert Dewar Sent: Tuesday, November 19, 2002 10:46 AM Well it certainly falls into the OBE category (see previous messages in the thread :-) **************************************************************** From: Robert A. Duff Sent: Tuesday, November 19, 2002 11:18 AM What's OBE? **************************************************************** From: Matthew Heaney Sent: Tuesday, November 19, 2002 11:33 AM I assume he meant "overcome by events." (Although I must admit that I am somewhat nonplussed by this answer, because I thought the categorization of random number generator packages had *not* been discussed in this thread.) **************************************************************** From: Robert Dewar Sent: Tuesday, November 19, 2002 12:22 PM OBE = Overtaken By Events (a standard internet abbreviation like CULTR :-) **************************************************************** From: Michael Feldman Sent: Tuesday, November 19, 2002 5:55 PM When I read this I was ROTFL. **************************************************************** From: Matthew Heaney Sent: Tuesday, November 19, 2002 11:09 AM > The part about "awkward" is correct, but the part about the "only way" > being heap usage is wrong. I don't know what Matthew Heaney had in > mind, I had the "Rosen Trick" in mind. > but the trick I sometimes use is this: > > In private part of random numbers package: > > type Generator_Ptr is access all Generator; > type Generator is limited > record > Self: Generator_Ptr := Generator'Unchecked_Access; > ... other fields > end record; > > In body: > > function Random(Gen: Generator) return ... is > Variable_Generator: Generator renames Gen.Self.all; > begin > Variable_Generator.Something := ...; > ... > end Random; > > Variable_Generator is a writeable view of Gen. You can do accomplish the same effect without a named access type, as I showed in my previous email message, ie type Handle_Type (G : Generator) is limited null record; type Generator is limited record H : Handle_Type (G'Access); ...other fields end record; And now G.H.G.all is a variable view. No named access types are required. > Whether this is, as Matthew claims, "just fine", is a matter of opinion, > I suppose. It is at least pure, portable, Ada 95. But your method requires named access types. Jean-Pierre's does not. This affects whether the package can be declared using a Pure categorization. You and I discussed this 3 years ago, on CLA: From: Robert A Duff Subject: Re: Array of Variant Records Question... Date: 1999/09/17 "Matthew Heaney" writes: > Clearly, by using the Rosen Trick, this heavy implementation isn't > necessary. You have to wonder why the AARM described the heap-based > solution, instead of the lighter (simpler?) internal handle idiom. Well, *I* didn't think of it at the time. > I don't like it because: >... > - (Most importantly) The spec of function Random is a lie! Again, it depends on what model you're using. Not all state changes need be advertised in the public part of the spec, because in the "logical" view of the type there is no state change. The problem is that parameter modes ("in" vs. "inout") in Ada conflate these two views ("logical" vs. "physical") of the abstraction. This is not unlike the problem with overriding operators for a type; it would be better if the public and private views of a type were in separate namespaces. For example, if I do this: type T is private; function "+" (L, R : T) return T; private type T is new Integer; The problem here is that I've lost access to the predefined "+" for type T. This often trips new Ada programmers, who will try to implement the overriden "+" operator using the predefined operator, and end up with infinite recursion instead. The real issue is that there should be different ways to communicate information to the compiler vs communicate information to the programmer using the abstraction. You, Bob Duff, want a random number generator to announce its state change, but that is the compiler-writer in you speaking. For a programmer (like me) who needs to use a random number generator, I don't really care whether the function is "in" or "inout" because the syntax is the same. (Actually, this was Jean's motivation for *not* allowing the parameter-passing mode to be used in overload resolution, because it's not obvious at the point of call which operation is being invoked. This is explained in the Ada83 Rationale.) > Apparently, the author of the GNAT version didn't know about this trick. > Apparently, the MRT didn't know about it either, when the RM was > written, because the AARM suggests a heap-based implementation. In > fact, that AARM comment was wrong; the version-with-corrigendum fixes > it (to a different heap-based implementation). I hope the Rosen Trick is in there somewhere. I consider it superior to your technique of using assignment to a named access type. I still maintain that the language should give implementors freedom to declare the random number generator packages as Pure. (I would like it even better if the RM required Pure.) **************************************************************** From: Robert A. Duff Sent: Tuesday, November 19, 2002 11:28 AM Matthew Heaney said: > I don't know if this implementation falls into the "ugly and > inefficient" category, but here it is: [...access discrim method] It's uglier than "in out", but prettier than the 'Unchecked_Access method I posted. It's probably the same efficiency as the method I posted. > > If it were, then: > > > > X: T := Random(...); > > Y: T := Random(...); > > > > the compiler would be allowed to optimize away one of the calls, > > thus making X = Y, which is clearly *not* what random number > > generators > > are supposed to do! > > Well then something is amiss, because the code fragment above will > compile just fine. Well, I think what I wrote above (X could = Y) is wrong, given the exact wording of 10.2.1(18). I'm not sure whether something is amiss w.r.t. Pure, and I don't want to think about it too hard right now. ;-) > The model is that the type is really a "handle," which designates some > modifiable, changing state, but which itself (the handle) is constant. > Hence only mode in is required. I suppose... > > By the way, making it tagged can also work. I take that back. Making it tagged makes the parameter aliased, but not writeable. > Yes, I knew that, but I wasn't sure what the effect would be if you > were to modify a pass-by-reference, nonlimited object through the back > door (via some Chap13 mechanism), because nonlimited objects can be > declared as constant. For example, if we have this: > > type T is tagged record ... end record; > > function Op (O : T) return T2; > > and then we do this: > > O : constant T := ; > > X : T2 := Op (O); > > What should happen if Op modifies object O? I think it's erroneous. **************************************************************** From: Matthew Heaney Sent: Tuesday, November 19, 2002 11:30 AM > Bob Duff said: > > Apparently, the author of the GNAT version didn't know > > about this trick. > > And Robert Dewar responded: > But the GNAT code is clearly superior, because it avoids the extra word > by a (safe) use of unrestricted access. Yes, but even with the GNAT technique you still can't declare the package as Pure (because of the presence of named access types). One issue (perhaps this does not have "problem" status) is that the compiler must infer whether objects of the type are modified by looking at how the package body is implemented, rather than by simply looking at the declaration in the spec. At least in C++, the programmer can mark the full view of the type as "mutable", which is perfectly unambiguous. And you can use it for nonlimited types. **************************************************************** From: Robert Dewar Sent: Tuesday, November 19, 2002 12:22 PM Incidentally, in the GNAT world, we frequently use Unrestricted_Access to get around lack of IN OUT parameters, and yes, its ugly, but it works :-) **************************************************************** From: Tucker Taft Sent: Tuesday, November 19, 2002 9:02 AM An interesting ramification of the proposed amendment allowing aggregates for limited type initialization is that it would now be possible to have limited *constants*. Those don't exist in Ada 83 or Ada 95 (unless the full type is non-limited). This implies that tricks that treat a limited "in" object as a variable are in jeopardy of doing even more harm. Conceivably the trick that uses 'unchecked_access to generate a self-pointer could be disallowed for constants. What this effectively means is that the programmer could not use "<>" for the self-pointer component of the aggregate -- they would have to provide some explicit value (which would necessarily not be a self-pointer). Note that controlled objects are already a bit "funny" in this sense, since the Finalize routine is allowed to write on the object, even if it is a constant. However, in this case, the object is at least constant throughout its "normal" lifetime. **************************************************************** From: Robert Dewar Sent: Tuesday, November 19, 2002 6:20 PM Obviously such low level techniques have to be used carefully, just like Unchecked_Conversion. After all UC can do unlimited harm also, let's keep things in perpsective :-) **************************************************************** From: Alexander Kopilovitch Sent: Tuesday, November 19, 2002 1:56 PM >> perhaps following restrictions will satisfy the opponents: >> >> 1) function with IN OUT parameter cannot be operator (and cannot be renamed >> to operator) >> 2) actual argument for IN OUT formal parameter cannot be used in any place >> within the whole statement. > >You guess completely wrong, Not so completely - it appears that the above restrictions actually address some of those old objections. > you should really make an effort to dig up >the old threads on this subject. I just did that. I found 5 files in old Ada-Comment archive: http://archive.adaic.com/standards/95com/mrtcomments/ namely: 94.0817, 94.0818, 94.0819, 94.0821, 94.0907, relevant to the OUT parameters for functions. I assume that those files cover that old discussion (if I missed something important and there are other relevant files then I hope somebody will point me at them). It appears that most often stated objection against lifting the restriction (OUT/IN OUT parameters for functions) was simply "too late" (meaning some schedule of the Ada9X project) - I found 3 persons saying that. Obviously, today that kind of objection is "void and null". Among the essential objections some are fully covered by the proposed restrictions (above). Another is about totally unspecified fantom danger (it is about overloading). And only one seems still valid: it is about complex expressions where a function call may be used as an actual argument for another function. I believe that that objection will be covered by the following 3rd rule for IN OUT parameters for functions: 3) in a function call, an actual argument that corresponds to IN OUT formal parameter must be a variable, and must be preceeded by the keyword VAR. For example: X := Random(var Y); With this rule any confusion becomes improbable, and readability flourishes. >The objection is that IN OUT parameters are fundamentally inappropriate >for functions. Apparently that "fundamentally inappropriate" disintegrates into several objections, and they are covered by the proposed 3 rules - I repeat them here together: 1) function with IN OUT parameter cannot be operator (and cannot be renamed to operator) 2) actual argument for IN OUT formal parameter cannot be used in any place within the whole statement. 3) in a function call, actual argument that corresponds to IN OUT formal parameter must be a variable, and must be preceeded by the keyword VAR. **************************************************************** From: Robert Dewar Sent: Tuesday, November 19, 2002 6:27 PM > It appears that most often stated objection against lifting the restriction > (OUT/IN OUT parameters for functions) was simply "too late" (meaning some > schedule of the Ada9X project) - I found 3 persons saying that. Obviously, > today that kind of objection is "void and null". Not obvious at all. At this stage, any changes to Ada have to meet a very heavy burden of value to implementation difficulty. For instance at this stage, implementing IN OUT parameters for functions in GNAT would be a major effort, and it would not surprise me if the same was true of other compilers. I know that there is a kind of "hobbyist" enthusiasm for adding all sorts of neat features to Ada. Even though many of these proposals may have technical merit, that does not mean at all that it is feasible to add all these features. What we want for a revision (if there is indeed a revision, that is by no means certain yet), is a small selection of very high value features, where the value to implementation effort is high and the disruption is minimum. Personally I don't think that IN OUT modes for parameters even vaguely *begin* to meet that criterion at this stage. Remember that I think that purely from a technical point of view, this would be a good addition, but there are hundreds of equally worthy minor "improvements", almost none of which are likely to make the cut. **************************************************************** From: Robert A. Duff Sent: Tuesday, November 19, 2002 6:43 PM I agree with Robert Dewar's comments. **************************************************************** From: Robert Dewar Sent: Tuesday, November 19, 2002 6:48 PM > I know that there is a kind of "hobbyist" enthusiasm for adding all sorts > of neat features to Ada. Even though many of these proposals may have > technical merit, that does not mean at all that it is feasible to add all > these features. Just so I don't offend anyone, I certainly regard myself as a "hobbyist" when it comes to programming languages (I was teaching SPITBOL to my undergraduate students today, and it is always fun to be back in an environment of neat stuff :-) But just because features in a language seem neat and useful is not nearly enough reason to implement them. I have a rule that says that every feature you add to a language damages the language. You have to be very careful that the added value exceeds the damage. If you look at Ada 9X mapping document II, you will find all sorts of very nice features, e.g. generalized extensible exception hierarchies class types for non-tagged types lots of other neat stuff but unfortunately we did not have room for them in Ada 95 without making the language too large and too complex. It is not at all clear that we have significantly more room for added complexity now than we did then. I must say that in my experience talking with real Ada programmers, I have very seldom got the impression that people choose languages other than Ada because of lack of features in Ada. Occasionally, they do miss the availability of particular libraries etc. But the availability of libraries depends on the effort put in to generate these libraries. Yes, some improvement in abstraction mechanisms makes it marginally easier to write such libraries, but I think this is completely minor compared with finding the effort to design and produce the libraries in the first place. **************************************************************** From: Robert I. Eachus Sent: Tuesday, November 19, 2002 1:43 PM "A tale, told by an idiot, full of sound and fury. Signifying nothing..." I'm sorry, I figure I have to inject a note of sanity in here. When working on a random number package for Ada 9X, the issues of providing implicit generators, on a per thread or per partition basis was discussed at length, as well as providing a protected interface to default generators, versions of the Random function with an in out parameter (possibly as a procedure) and so on. As I recall, the decision that generators should be passed using mode in was finalized just before the final vote on allowing in out parameters for functions. As I recall, the reasons for doing so had little to do with the legality of in out parameters for functions, and everything to do with how to properly use the standard package from more than one task. Having the generator passed as mode in makes doing the right thing in a tasking environment easy--you can either have a per task attribute which is the generator for that task, or create a protected object that contains a generator, depending on what you want. So please, please, if you must continue arguing about in out parameters for functions, leave random number generators out of it. Passing generators implicitly or explicity as in out parameters doesn't work. All of the "tricks" discussed here however compose with tasking just fine. It also could have been done by making the parameter of Random be an explicit access parameter, but that was not done for aesthetic reasons. **************************************************************** From: Matthew Heaney Sent: Tuesday, November 19, 2002 6:11 PM Well, one problem with the existing schema is that the compiler lets you do this: protected Protected_Generator is function Random return Float; private G : Generator; end; protected body Protected_Generator is function Random return Float is begin return Random (G); end; end; Isn't this broken? The problem is that the compiler is allowed to return the same value for the protected function Random, and if multiple tasks happen to be calling Protected_Generator then they can all get the same random number value. What you have to do is extract the random number from Protected_Generator using a protected procedure, not a protected function, and then all is well. But this locution is hardly obvious. So unless my understanding of the interaction of protected objects and the random number generator is wrong, then I think it's being generous to characterize doing the right thing as "easy." **************************************************************** From: Robert Dewar Sent: Tuesday, November 19, 2002 6:29 PM I find this peculiar and incomprehensible. The approach used in GNAT is absolutely equivalent at the implementation level to allowing IN OUT parameters to functions. **************************************************************** From: Robert Dewar Sent: Tuesday, November 19, 2002 6:29 PM > Isn't this broken? The problem is that the compiler is allowed to > return the same value for the protected function Random, and if multiple > tasks happen to be calling Protected_Generator then they can all get the > same random number value. I don't see this as other than an obvious bug in the program. **************************************************************** From: Matthew Heaney Sent: Tuesday, November 19, 2002 6:55 PM I'm sure that you, Robert Dewar, see this as an "obvious" bug. But that doesn't mean it's obvious! To me the most obvious way to protect a generator is using the implementation I presented. I know it's wrong, now, but I had to learn the hard way... **************************************************************** From: Robert Dewar Sent: Tuesday, November 19, 2002 7:03 PM Well what caught you out was the distinction between functions (read locks) and procedures (write locks), which is bizarre distinction that can indeed cause confusion. But I am surprised, are there in fact compilers that bother with read locks on functions. Certainly GNAT does not (there is no obvious way to do this with interface to typical posix thread systems). So what compiler did you learn the hard way on :-) **************************************************************** From: Robert I. Eachus Sent: Wednesday, November 19, 2002 1:06 AM >What you have to do is extract the random number from Protected_Generator >using a protected procedure, not a protected function, and then all is well. >But this locution is hardly obvious. > >So unless my understanding of the interaction of protected objects and the >random number generator is wrong, then I think it's being generous to >characterize doing the right thing as "easy." I didn't say obvious, I said easy. ;-) Of course the body of a package that shares RNGs between tasks has to contain a (protected) procedure call, not a function call, to the "real" generator object. But it is easy to write that package in a generator independent way, and even make it a generic. What having the generator as an in parameter forces is that the state of the actual value passed can't be changed by the RNG. It doesn't matter whether the implementation uses the "Rosen trick" or some other method to come up with a constant pointer that is the real value passed in the call , or some other method. The point is that you can have a user visible function that is not a task entry or call on a protected object. All that can be hidden out of sight. Of course, I have to wonder why anyone would want to share a generator in that fashion, but it can be done. Much better in almost any real application I have run into is to have one independent generator per task--for most discrete event simulations the only sensible approach even if the different objects that use the generators are in the same task. You don't want the generator to result in objects that should be independent becoming interdependent, especially in discrete event simulations. When you do want to dispatch tasks with one or more random parameters, by far the most usual case is that you have a dispatcher task that passes out work to a number of separate processors. In this case you almost always want the results to be independent of the number of processors used. (But in every case where I have done so, the correct "magic" was to use a generator function that could be advanced quickly to a future value. Each actual working task is given N values per rendezvous with the dispatcher, and call the dispatcher again when those values are used up. When this approach is correct, it allows you to spread for example, a genetic algorithm over many processors, and get results indendent of the number of processors used.) **************************************************************** From: Dan Eilers Sent: Wednesday, January 15, 2003 6:00 PM On 19 Nov 2002, Matthew Heaney showed the clever "Rosen trick" for "casting away const". I've found a simpler way that works at least on Gnat, but alas doesn't seem to work on all compilers. -- Rosen trick: package Random_Numbers is pragma Pure; type Generator is limited private; function Random (G : Generator) return Float; private type State_Type is record i: integer := 0; end record; type Handle (G : access Generator) is limited null record; type Generator is limited record H : Handle(Generator'Access); --per Jean-Pierre State : State_Type; end record; end Random_Numbers; package body random_numbers is procedure newstate(x: in out State_type) is begin x.i := x.i + 1; -- just increment for now end; function Random (G : Generator) return Float is State : State_Type renames G.H.G.State; begin newstate(State); return float(State.i); -- just return state for now end; end random_numbers; with random_numbers; with text_io; procedure main is g: random_numbers.generator; begin for i in 1..10 loop text_io.put_line(float'image(random_numbers.random(g))); end loop; end; ------------------------------------------------------------------------ -- New way: package Random_Numbers is pragma Pure; type Generator is limited private; function Random (G : Generator) return Float; private type State_Type is record i: integer := 0; end record; type Generator is tagged limited record State : State_Type; end record; end Random_Numbers; package body random_numbers is procedure newstate(x: in out State_type) is begin x.i := x.i + 1; -- just increment for now end; procedure assign(x: generator'class; new_value: State_Type) is procedure doit(x: in out generator) is begin x.state := new_value; end; subtype T2 is generator; begin doit(T2(x)); end assign; function Random (G : Generator) return Float is State : State_Type := G.State; begin newstate(State); assign(G, State); return float(State.i); -- just return state for now end; end random_numbers; with random_numbers; with text_io; procedure main is g: random_numbers.generator; begin for i in 1..10 loop text_io.put_line(float'image(random_numbers.random(g))); end loop; end; **************************************************************** From: Christophe Grein Sent: Thursday, January 16, 2003 1:12 AM > procedure assign(x: generator'class; new_value: State_Type) is > procedure doit(x: in out generator) is > begin > x.state := new_value; > end; > subtype T2 is generator; > begin > doit(T2(x)); | But x is not a variable here, so I do not see why this should work (except for a bug in Gnat). A view conversion does not change a constant into a variable. > end assign; I would propose to report this to ACT. Or am I missing something? **************************************************************** From: Dan Eilers Sent: Thursday, January 16, 2003 11:24 AM Yes, you are right. A more serious proposal for how to implement Random would be to define a new pragma that could be applied to private types, that would mean: 1) no constants may be declared of this type; 2) "in" parameters are not implicitly treated as constants (either in functions or procedures) This pragma would be appropriate for type Generator in the Random packages, and also type File_Type in the I/O packages. It might also help solve the dilemma caused by AI-287 and AI-318 proposing to allow constants of limited types in the face of the Rosen trick allowing writing on objects of limited types. Perhaps the Rosen trick could be disallowed on limited types that didn't have this pragma applied, although that would be an incompatibility. **************************************************************** From the minutes of Meeting 18, February 2003: Steve Baird points out that the wording of 6.6(3) isn't quite correct. The instantiation case needs to specify that there are no other parameters. [If this AI is resurrected someday, that needs to be fixed.] **************************************************************** From: Pascal Leroy Sent: Tuesday, January 10, 2006 6:50 AM [Replying to private mail from Tucker Taft - ED] ... > I also am > not particularly comfortable with being tagged as the sole > holdout on "out" parameters for functions. But you are ;-) > When we discussed > this in the past in a more open forum than an ARG meeting, > there were many voices in favor of preserving the in-parameter-only > rule. It is conceivable that I am the only ARG member who > is still sympathetic with that rule, but I wasn't actually > aware of that (not that that changes my view, of course ;-). I am not sure what forum or what discussion you refer to, but it would be interesting to hear rational justification from real users. The reason why I am frustrated by this topic is that it has never been discussed in depth, although it keeps cropping up from time to time in unrelated discussions (the only time it was addressed in an ARG meeting was in Padua, and we didn't spend 10 minutes on it). My recollection from the Padua meeting is that many people were concerned about destabilizing the language or compilers. But it turned out that over time we became bolder and made other changes that are bigger and more complex. **************************************************************** From: Tucker Taft Sent: Tuesday, January 10, 2006 7:31 AM ... > I am not sure what forum or what discussion you refer to, but it would be > interesting to hear rational justification from real users. I believe it was discussed in depth during the Ada 9X process. It may be that was the last time there was a thorough airing of the issues. Those heavily involved in this earlier discussion remember it as being very contentious, and probably avoided it during the Ada 200Y process for that reason. > ... My > recollection from the Padua meeting is that many people were concerned > about destabilizing the language or compilers. But it turned out that > over time we became bolder and made other changes that are bigger and more > complex. It might be worth trying to find the old Ada 9X notes on this topic some day... Implementation issues were only a part of the argument -- the big part had to do with the likelihood of code being harder to read, understand, verify, and debug. Access parameters were the Ada 9X solution to both kinds of issues, because they allowed pass-by-reference for "out" parameters (pass-by-copy-out was felt to be more of a burden in the middle of an expression), and they made the use highly visible, due to the need for "aliased" and "'Access". **************************************************************** From: Pascal Leroy Sent: Tuesday, January 10, 2006 9:55 AM I guess it would be better to discuss this topic around a bottle of wine, however... > ...the big part had to do > with the likelihood of code being harder to read, understand, > verify, and debug. I have some sympathy with this, but my view is that at the moment we have the worst of both worlds: 1 - The absence of out parameters for functions is a constant annoyance, and it makes the language more awkward/less powerful than it should be. I don't think that I write particularly disgusting code, but I suspect that every other day I run into a situation where I wish I could have a function with an out parameter. I can invariably find work-arounds, but they are unnecessarily contrived, and many times they only work because my program (a compiler) is sequential. 2 - Functions are not "safe" or easy to debug/read/understand. Basically the rule is that you can do all the nastiness you wish as long as you don't document them in the profile. Side effects, or modifications through access parameters do not make things particularly easy to understand, and I would much prefer to see "in out" in the specification than to find that some function has a side effect because it needs to updates a cache. This is why I would love to have two distinct kinds of functions: those that can do anything, and those that are pure mathematical functions (John amusingly called them whores and virgins, but I don't think we would want to adopt this terminology as official technical terms). That way the programmer could choose between powerful/flexible and safe/readable, and of course make the choice on a case-by-case basis. > Access parameters were the Ada 9X > solution to both kinds of issues, because they allowed > pass-by-reference for "out" parameters (pass-by-copy-out was > felt to be more of a burden in the middle of an expression), > and they made the use highly visible, due to the need for > "aliased" and "'Access". I bought this story 10 years ago but I lost my illusions after trying to use the damned language. There is a category of problems (notably in the OOP world) where access parameters work very well, and this is one of the reasons why I was all in favor of making them more powerful in Ada 2005. But if what you are trying to do is have an in out Integer, access parameters are a big pain in the b*tt. "Access" and "aliased" stick out like two sore thumbs, and the readability of the source code is considerably degraded. Not to mention that many people probably go for Unchecked_Access to circumvent the accessibility rules, so they run the risk of being bitten by dangling pointers. The bottom line is that you don't help the programmer by making it hard to solve a problem that pops up all the time. You help the programmer by giving them a wide range of tools so that they can choose the most appropriate for a given problem. **************************************************************** From: Randy Brukardt Sent: Tuesday, January 10, 2006 1:12 PM > I guess it would be better to discuss this topic around a bottle of wine, > however... The more bottles, the better. :-) :-) Tucker said: > I believe it was discussed in depth during the Ada 9X > process. It may be that was the last time there was > a thorough airing of the issues. Those heavily involved > in this earlier discussion remember it as being very > contentious, and probably avoided it during the > Ada 200Y process for that reason. The problem with depending on this is that it is a 15-year old discussion. Much has changed about Ada, and the world, since then. Moreover, Pascal is proposing adding a real, checked Pure function, which I think would mitigate some of the concerns (it would *more* possible to write safe functions than it is now). Pascal said: > > ...the big part had to do > > with the likelihood of code being harder to read, understand, > > verify, and debug. > > I have some sympathy with this, but my view is that at the moment we have > the worst of both worlds: > > 1 - The absence of out parameters for functions is a constant annoyance, > and it makes the language more awkward/less powerful than it should be. I > don't think that I write particularly disgusting code, but I suspect that > every other day I run into a situation where I wish I could have a > function with an out parameter. I can invariably find work-arounds, but > they are unnecessarily contrived, and many times they only work because my > program (a compiler) is sequential. > > 2 - Functions are not "safe" or easy to debug/read/understand. Basically > the rule is that you can do all the nastiness you wish as long as you > don't document them in the profile. Side effects, or modifications > through access parameters do not make things particularly easy to > understand, and I would much prefer to see "in out" in the specification > than to find that some function has a side effect because it needs to > updates a cache. > > This is why I would love to have two distinct kinds of functions: those > that can do anything, and those that are pure mathematical functions (John > amusingly called them whores and virgins, but I don't think we would want > to adopt this terminology as official technical terms). That way the > programmer could choose between powerful/flexible and safe/readable, and > of course make the choice on a case-by-case basis. Exactly. One middle ground alternative would be to allow "in out" only on by-reference types (in this case, it is identical to "not null access"). But that seems a bit goofy, and contract issues would prevent much use in generics. So I think it is best to eliminate the silly restriction. > > Access parameters were the Ada 9X > > solution to both kinds of issues, because they allowed > > pass-by-reference for "out" parameters (pass-by-copy-out was > > felt to be more of a burden in the middle of an expression), > > and they made the use highly visible, due to the need for > > "aliased" and "'Access". > > I bought this story 10 years ago but I lost my illusions after trying to > use the damned language. There is a category of problems (notably in the > OOP world) where access parameters work very well, and this is one of the > reasons why I was all in favor of making them more powerful in Ada 2005. > But if what you are trying to do is have an in out Integer, access > parameters are a big pain in the b*tt. "Access" and "aliased" stick out > like two sore thumbs, and the readability of the source code is > considerably degraded. Not to mention that many people probably go for > Unchecked_Access to circumvent the accessibility rules, so they run the > risk of being bitten by dangling pointers. Exactly again. Indeed, I was one of the people that was firmly against in out parameters for functions in Ada 95. But I've found that that it makes a lot of things harder/uglier. Moreover (unlike Pascal), I try hard to avoid visible access types in OOP -- they're not usually needed (except for this stupid case), and cause accessibility issues where there would need to be none. (Claw has very few visible access types; of course there are a lot of them under the covers.) > The bottom line is that you don't help the programmer by making it hard to > solve a problem that pops up all the time. You help the programmer by > giving them a wide range of tools so that they can choose the most > appropriate for a given problem. This is one of the best summaries I've heard on the topic. Can I add it to the AI for future reference? ****************************************************************