gore@nucsrl.UUCP (06/03/86)
>[ Discussion about lack of pointers to procedures in Ada ] > >You've gotta draw the line somewhere. I'm continually amazed how >people complain that Ada has either too much or not enough because >it's left out their favorite feature from language X. Considering the >design goals for Ada, I think they did a good (not perfect) job of >designing a language with which you can write both efficient and >abstract, safe programs. Personally, my complaint is more about the incomplete orthogonality of Ada than about lack of specific features. If task types (and hence pointers to tasks) are allowed, why not allow procedure types, package types, entry types? We should not automatically assume that addition of these automatically means making the language bigger. For example, if there was a way to maintain structures of entries, there would be no need for "families of entries". Another example: If only 'in' parameters are allowed in functions, why allow access to external variables from inside a function? Jacob Gore Northwestern Univ Comp Sci Research Lab ihnp4!nucsrl!gore
richw@ada-uts (06/20/86)
>> ***** ada-uts:net.lang.ada / cca.UUCP!holden / 8:46 am May 30, 1986 >> Rich, you are missing out on the beauty of procedure passing. >> (Russell Holden) Pardon me, but... I've actually used this feature in many languages: Lisp, CLU, C, and Smalltalk. As a matter of fact, I'm in the process of rewriting a C program (which used pointers to functions) in Ada. >> Simply put it gives you the ability to dynamically bind actions >> without the invoker of the procedure having to know about the >> procedure being called. So does the enumeration-to-procedure mapping method I proposed. >> ... The use of enumerated types does no >> good and there are no real good solutions that I know of in ADA I'm sorry, but I simply fail to see how your examples provide conclusive arguments that this method will not work in general. GRANTED, the mapping method is less convenient for the programmer, but my point was that it is nevertheless possible in Ada at a reasonable expense (which I think is justifiable given that omitting procedure pointers from Ada prevented Ada from becoming even more complex). To be more concrete, the following outlines a package spec which one could use: package Mapper is type No_Argument_Procedure_Pointer is ( proc_A_0, proc_B_0, ... ); type One_Integer_Argument_Procedure_Pointer is ( proc_A_1_int, proc_B_1_int, ... ); : procedure Call ( p : in No_Argument_Procedure_Pointer ); procedure Call ( p : in One_Integer_Argument_Procedure_Pointer; i : in Integer ); : end Mapper The implementation of each "Call" procedure would simply be a case statement to the appropriate "real" procedure call. If anyone could provide an example where a Procedure_Pointer could not be used wherever one would like to use a "real" pointer to a procedure in a different langauge, I'd be grateful for a reply. Again, I apologize if I'm just being dense, but... Rich Wagner
holden@cca.UUCP (Russ Holden) (06/22/86)
I know this has been beaten to death (and put to sleep) but... > >> Rich, you are missing out on the beauty of procedure passing. > >> Simply put it gives you the ability to dynamically bind actions > So does the enumeration-to-procedure mapping method I proposed. > > To be more concrete, the following outlines a package spec which > one could use: > > package Mapper is > > type No_Argument_Procedure_Pointer is ( > proc_A_0, proc_B_0, ... ); > > type One_Integer_Argument_Procedure_Pointer is ( > proc_A_1_int, proc_B_1_int, ... ); > : > > procedure Call ( > p : in No_Argument_Procedure_Pointer ); > > procedure Call ( > p : in One_Integer_Argument_Procedure_Pointer; > i : in Integer ); > : > end Mapper > > The implementation of each "Call" procedure would simply be a case > statement to the appropriate "real" procedure call. There were a bunch of examples better than the ones I provided given during the lengthy discussion about procedure parameters over the past few weeks which point out the insufficiency of this (not to mention its inherent ugliness and limits). Also shown were mechanism for making the checking of procedure parameters quite possible (for example by requiring that procedures used in that manner be strongly typed). > > If anyone could provide an example where a Procedure_Pointer could > not be used wherever one would like to use a "real" pointer to a > procedure in a different langauge, I'd be grateful for a reply. > > Again, I apologize if I'm just being dense, but... > OK, apology accepted. -- Russell Holden Computer Corporation of America Four Cambridge Center Cambridge, MA 02142
friesen@psivax.UUCP (07/11/86)
In article <4700042@ada-uts> richw@ada-uts writes: > >>> Simply put it gives you the ability to dynamically bind actions >>> without the invoker of the procedure having to know about the >>> procedure being called. > >So does the enumeration-to-procedure mapping method I proposed. > >>> ... The use of enumerated types does no >>> good and there are no real good solutions that I know of in ADA > >I'm sorry, but I simply fail to see how your examples provide >conclusive arguments that this method will not work in general. >GRANTED, the mapping method is less convenient for the programmer, >but my point was that it is nevertheless possible in Ada at a >reasonable expense (which I think is justifiable given that omitting >procedure pointers from Ada prevented Ada from becoming even more >complex). > I fail to see how an enumeration can solve the general problem(even given your example). Given that at the time you write the mapper package you do *not* know what procedures will be called by it, and that *most* of the procedures to be used do not even exist yet, *how* can you create an enumeration type that covers the set? The whole point of function pointers as arguments is that it allows the writing of *generic* procedures which can be "customized" by the individual user by providing a pointer to a user supplied routine, this allows the general routine to be used on data structures or views that were not anticipated when the original routine was written. If you can explain how to access non-existant functions, whose names you do not even know, using an enumeration mapper, then I will I will admit that this is equivalent to function pointers. -- Sarima (Stanley Friesen) UUCP: {ttidca|ihnp4|sdcrdcf|quad1|nrcvax|bellcore|logico}!psivax!friesen ARPA: ??
richw@ada-uts.UUCP (07/14/86)
Sarima (Stanley Friesen) writes: >> I fail to see how an enumeration can solve the general >> problem (even given your example). Given that at the time you >> write the mapper package you do *not* know what procedures will >> be called by it, and that *most* of the procedures to be used do >> not even exist yet, *how* can you create an enumeration type >> that covers the set? Your point is valid; I did not explicitly state that modification of the mapper package is necessary when newly written procedures are to be passed (I didn't realize this wasn't obvious). So, in terms of program maintainance, yes, the mapping method is less convenient; if you remember, I admitted from the onset that it WAS less convenient. In any case, thanks for pointing out this ambiguity. (Note, however, that the pain of having to modify the mapper package has nothing to do with WHAT you can do using the method; this mapping method STILL provides the same functionality as procedure passing.) This raises an interesting question: can the mapper package be written so that when new procedures are to be added, only the body of the mapper package needs to be recompiled? This is important when consider- ing program maintainance because a change to the mapper package's spec will force recompilation of all users of the mapper (something one would like to avoid). The last part of this note outlines a revised spec and body which avoids having to modify the spec when adding procedures. >> The whole point of function pointers as arguments is that it >> allows the writing of *generic* procedures which can be >> "customized" by the individual user by providing a pointer to a >> user supplied routine, this allows the general routine to be >> used on data structures or views that were not anticipated when >> the original routine was written. Ah, I'm GLAD you mentioned this. The use of Ada's generics for such situations is MUCH more advantageous than passing pointers. If you think about it, you can write a much more general "sort" procedure using generics as opposed to procedure passing because generics lets you not only vary the procedure used in comparing elements while sorting -- it also lets you parameterize the TYPE of the elements in the array you're sorting (assuming you're sorting arrays); can't do that with procedure passing... The following is that revised mapper package sketch I promised earlier; it illustrates how one would pass two types of subprograms: simple procedures which take no arguments and functions on Strings which return Integers. The basic idea involves moving the enumeration type declaration into the package body. The 'POS of the enumeration values are passed around instead of the values themselves. To make up for the inability of users to refer to enumeration values by name (since they no longer appear in the spec), the 'IMAGEs of the enumeration values are used to create procedure pointers instead. The costs of changing the spec in these ways are: (1) More overhead due to the use of 'VALUE in the body ('POS is not costly -- basically just a type conversion) and (2) A misspelled procedure name is caught at runtime (via the UNKNOWN_SUBPROGRAM exception) rather than link time. ------------------------------------------------------------------------ package Mapper is type Procedure_with_0_arguments is private; type Function_with_1_in_String_return_Integer is private; UNKNOWN_SUBPROGRAM : exception; function Create (procedure_name : in String) return Procedure_with_0_arguments; -- Raises : UNKNOWN_SUBPROGRAM function Create (function_name : in String) return Function_with_1_in_String_return_Integer; -- Raises : UNKNOWN_SUBPROGRAM procedure Call ( p : in Procedure_with_0_arguments); function Call ( f : in Function_with_1_in_String_return_Integer; s : in String) return Integer; procedure Call ( procedure_name : in String); -- -- Raises : UNKNOWN_SUBPROGRAM -- -- This is functionally equivalent to: -- Call (Create (procedure_name)); -- function Call ( function_name : in String; s : in String) return Integer; -- -- Raises : UNKNOWN_SUBPROGRAM -- -- This is functionally equivalent to: -- Call (Create (function_name), s); private type Procedure_with_0_arguments is new Integer; type Function_with_1_in_String_return_Integer is new Integer; end; ---------------------------------------------------------------------- with proc1; with proc2; with func1; with func2; : package body Mapper is type Enums_for_Procedure_with_0_arguments is ( proc1_enum, proc2_enum, ...); type Enums_for_Function_with_1_in_String_return_Integer is ( func1_enum, func2_enum, ...); -- -- The above will need to be added to when new subprograms are -- to be passed. function Create (procedure_name : in String) return Procedure_with_0_arguments is begin return Enums_for_Procedure_with_0_arguments'POS ( Enums_for_Procedure_with_0_arguments'VALUE ( procedure_name & "_enum")); exception when CONSTRAINT_ERROR => raise UNKNOWN_SUBPROGRAM; end; function Create (function_name : in String) return Function_with_1_in_String_return_Integer is begin return Enums_for_Function_with_1_in_String_return_Integer'POS ( Enums_for_Function_with_1_in_String_return_Integer'VALUE ( function_name & "_enum")); exception when CONSTRAINT_ERROR => raise UNKNOWN_SUBPROGRAM; end; -- The following two subprograms will need to be added to when -- new subprograms are to be passed. -- procedure Call (p : in Procedure_with_0_arguments) is begin case Enums_for_Procedure_with_0_arguments'VAL (p) is when proc1_enum => proc1; when proc2_enum => proc2; : end case; end; function Call ( f : in Function_with_1_in_String_return_Integer; s : in String) return Integer is begin case Enums_for_Function_with_1_in_String_return_Integer'VAL (f) is when func1_enum => return func1 (s); when func2_enum => return func2 (s); : end case; end; procedure Call ( procedure_name : in String) is begin Call (Create (procedure_name)); end; function Call ( function_name : in String; s : in String) return Integer is begin return Call (Create (function_name), s); end; end; ------------------------------------------------------------------------ Note the "_enum" suffixes for the enumeration literals; I included them because I'm not sure offhand if there'd be any problem with overloading the enumeration literals and the subprogram names they represent. If not, then removing the "_enum" suffix (and removing the call to "&" in the Create functions) would be a good thing; in any case, you get the idea... -- Rich Wagner
vilot@wanginst.UUCP (07/19/86)
In article <4700062@ada-uts> richw@ada-uts writes: > >Sarima (Stanley Friesen) writes: > >>> I fail to see how an enumeration can solve the general >>> problem (even given your example). Given that at the time you >>> write the mapper package you do *not* know what procedures will >>> be called by it, and that *most* of the procedures to be used do >>> not even exist yet, *how* can you create an enumeration type >>> that covers the set? > >Your point is valid; I did not explicitly state that modification of >the mapper package is necessary when newly written procedures are to >be passed (I didn't realize this wasn't obvious). So, in terms of >program maintainance, yes, the mapping method is less convenient; if >you remember, I admitted from the onset that it WAS less convenient. >In any case, thanks for pointing out this ambiguity. > >(Note, however, that the pain of having to modify the mapper package > has nothing to do with WHAT you can do using the method; this mapping > method STILL provides the same functionality as procedure passing.) > I noticed that passing objects of type task was rejected very early in this discussion. Why? (Please don't plead run-time "performance".) It seems to provide a reasonable approximation of C's semantics for passing function pointers. Ignoring the aesthetic arguments and assuming someone Really Needs to do this, are there any significant reasons why it cannot be used to solve the problem? (P.S. A simple bit of code passed the Verdix compiler, at least.)
stt@ada-uts (07/22/86)
Unfortunately, passing a task doesn't do the trick because the task type (which must be specified) implies the code for the task. Of course tasks are great for holding onto a bit of state, if that is your primary goal.
creedy@cca.UUCP (Christopher Reedy) (08/07/86)
Since I didn't see any responses to this: In article <4700073@ada-uts> stt@ada-uts writes: > >Unfortunately, passing a task doesn't do the trick >because the task type (which must be specified) implies >the code for the task. Of course tasks are great for >holding onto a bit of state, if that is your primary goal. There was a nice talk given at the 1985 Ada Europe conference in Paris on this issue. The thrust of the talk was that one needs an additional task to act as an intermediary between the invoking and the invoked tasks. The intermediary task makes no entry calls, simply receiving calls from both of the other tasks. Thus, the invoking task need not know the type of the invoked task. (If anyone would like to see a simple example, I have one. However, it takes about three pages of listing.) In most cases this seems like a lot of overhead to go through to make a variable procedure call. However, the classical asynchronous buffer is an example of this sort of approach. In fact, if one was to combine the intermediary task with an asynchronous buffer or some similar mechanism, this approach might make a great deal of sense. Chris Reedy Computer Corporation of America --Standard disclaimer: All opinions are my own.--