!standard 8.5.5 (2) 02-06-12 AI95-00299/01 !standard 12.4 (6) !standard 12.5 (2) !standard 12.7 (2) !class amendment 02-06-12 !status work item 02-06-12 !status received 02-06-12 !priority Medium !difficulty Medium !subject Defaults for generic formal parameters !summary It is proposed to modify the syntax of generic formal type, package, and "in out" object parameters to permit default expressions. It is also proposed to modify the syntax of generic renaming declarations to permit supplying defaults. !problem Ada 95 does not have defaults for generic formal types or packages, nor can generic renamings supply defaults for (some of) the formal parameters of the renamed generic unit. While not a serious problem (users can always be forced to supply explicit values for all parameters), it is sometimes a nuisance and unnecessarily complicates some generic instantiations. Allowing more kinds of defaults for generic formal parameters can significantly ease the use of generic units. !proposal 1. Defaults for generic formal types. ------------------------------------- 12.5(2) is modified to read formal_type_declaration ::= type defining_identifier [discriminant_part] is formal_type_definition [use subtype_mark]; The idea is that the optional "use subtype_mark" defines a default type to be taken if an instantiation does not explicitly provide an actual for that parameter. The type denoted by "subtype_mark" must fulfill all the constraints/rules as if it occurred as a generic actual in a generic parameter association. Note: one might also consider using ":=" instead of "use", but that's a purely syntactic issue. 2. Defaults for generic formal packages --------------------------------------- 12.7 (2) is modified to read formal_package_declaration ::= with package defining_identifier is new generic_package_name formal_actual_part [:= package_name]; with the intention that the ":= package_name" defines a default package to be taken if an instantiation does not explicitly provide an actual for that parameter. The package denoted by "package_name" must be an instantiation of the generic package denoted by "generic_package_name" such that it fulfils the constraints imposed by the formal_actual_part. 3. Defaults in generic renamings -------------------------------- 8.5.5(2) is modified to read generic_renaming_declaration ::= generic package defining_program_unit_name renames generic_package_name [generic_actual_part]; |generic procedure defining_program_unit_name renames generic_procedure_name [generic_actual_part]; |generic function defining_program_unit_name renames generic_function_name [generic_actual_part]; The generic_actual_part may be incomplete in the sense that it is allowed to define actuals only for a subset of the generic formal parameters of the unit denoted by the generic_unit_name following the "renames". Those actuals given are taken as defaults for the corresponding generic formals for the renaming. In other word, a generic renaming may supply additional default values for generic formals that in the renamed unit did not have defaults, and it may change defaults of formals for which the declaration of the renamed unit already defined defaults. The actuals provided in a generic renaming declaration are just defaults; an instantiation of the unit declared by the generic renaming declaration may override those defaults by explicitly providing values for these formal parameters. 4. Defaults for generic formal "in out" objects ----------------------------------------------- It is proposed to delete paragraph 12.4(6). As a result, generic formal "in out" objects may have defaults, too. !discussion All of these changes aim at making generic instantiations simpler to write, or rather, to give designers of generic units the means to design their units such that instantiations can be made simpler than today. It is often possible to supply reasonable default values for some of the entities to which the above proposal applies. I cannot judge up-front how difficult or complex this would be to implement, but it appears to me to be relatively straight-forward: one just has to maintain info on what the defaults are, and in an instantiation, supply these defaults as the actuals for any generic formals that do not have explicit actuals. The whole instantiation process then should remain unchanged. All of these four change items have come up in the development of a library of container abstractions. A short discussion on comp.lang.ada revealed that apparently I'm not alone with these ideas; several developers of container libraries have come across these issues and perceive them as shortcomings of Ada 95. !example An example for defaults for generic formal types ------------------------------------------------ Consider a list container with an operation returning the number of items currently in the list. generic type Item_Type is private; with function "=" (L, R : in Item_Type) return Boolean; package Lists is type List is private; procedure Append (L : in out List; Item : in Item_Type); function Length (L : in List) return Natural; -- ... other operations private type Node; type Link is access all Node; type Node is record Data : Item_Type; Next, Prev : Link; end record; type List is new Ada.Finalization.Controlled with record N : Natural := 0; Head, Tail : Link; end record; -- .. more stuff end Lists; All operations would maintain L.N, e.g. Append would add 1. When this came up on comp.lang.ada, some people argued that if one has lists of oranges and lists of apples, these should be counted using different types to avoid inadvertantly adding apple counts and orange counts. Hence the interface should be modified: generic type Item_Type is private; type Item_Count is range <>; -- Must include zero. with function "=" (L, R : in Item_Type) return Boolean; package Lists is -- ... as above, except: function Length (L : in List) return Item_Count; private type List is new Ada.Finalization.Controlled with record N : Item_Count'Base := 0; Head, Tail : Link; end record; end Lists; Well, clearly not everybody agrees with that, and some people don't care, and some may say "well, Item_Count has that funny requirement that zero be included, so let's use Natural", so in a general container package, it might well make sense to provide a default (namely Natural) for the Item_Count type. With that, the generic package could cater for both styles, at no extra cost: generic type Item_Type is private; type Item_Count is range <> use Natural; -- New syntax now! with function "=" (L, R : in Item_Type) return Boolean; package Lists is ... end Lists; The examples below also use defaults for generic formal types. An example for defaults in generic renamings -------------------------------------------- Consider a hash table container abstraction: generic type Key_Type (<>) is private; type Item_Type (<>) is private; with function Hash (Key : in Key_Type) return Natural; with function "=" (L, R : in Key_Type) return Boolean is <>; Initial_Default_Size : Positive := 23; -- Initial size of hash tables package Hash_Tables is type Hash_Table is private; procedure Insert (Table : in out Hash_Table; Key : in Key_Type; Item : in Item_Type); -- ... Other operations on hash tables end Hash_Tables; Now suppose for some reason, someone wants a version of this generic package in which all hash tables initially get some larger size. Easy to do with a generic renaming, supplying a new default for Initial_Default_Size: generic package Large_Hash_Tables renames Hash_Tables (Initial_Default_Size => 101); And an instantiation like package My_Small_Hash_Tables is new Hash_Tables (String, My_Data, My_Hash_Function, "="); will offer hash tables that initially have a small size (23), whereas an instantiation like package My_Large_Hash_Tables is new Large_Hash_Tables (String, My_Data, My_Hash_Function, "="); will offer hash tables that initially have a larger size (101). Granted, the second instantiation could also have been written as package My_Large_Hash_Tables is new Hash_Tables (String, My_Data, My_Hash_Function, "=", 101); so maybe the example is not exactly the best, but it still nicely illustrates how providing defaults in a renaming may simplify things. Maybe a better example is to use a renaming to capture the most common case, namely the one where the key type is String (this example uses the proposed defaults for generic formal types): package Hash_Support is function Hash_String (S : in String) return Natural; end Hash_Support; with Hash_Support; generic package String_Hash_Tables renames Hash_Tables (Key_Type => String, Hash => Hash_Support.Hash_String); and then, a hash table package with string keys can be gotten simply by package My_Hash_Tables is new String_Hash_Tables (Item_Type => My_Data); package My_Other_Hash_Tables is new String_Hash_Tables (Item_Type => My_Other_Data); An example for defaults for generic formal packages --------------------------------------------------- Again, consider a generic hash table package, but this time using a signature package for the key type: generic type Key_Type (<>) is private; with function Hash (Key : in Key_Type) return Natural; with function "=" (L, R : in Key_Type) return Boolean is <>; package Hashable is -- Signature package. end Hashable; generic with package Keys is new Hashable (<>); type Item_Type (<>) is private; Initial_Default_Size : Positive := 23; package Hash_Tables is type Hash_Table is private; procedure Insert (Table : in out Hash_Table; Key : in Keys.Key_Type; Item : in Item_Type); -- ... Other operations on hash tables end Hash_Tables; Then, one could get a package for hash tables with strings as keys as follows: with Hash_Support; package String_Keys is new Hashable (Key_Type => String, Hash => Hash_Support.Hash_String); generic package String_Hash_Tables renames Hash_Tables (Keys => String_Keys); with the same instantiations for finally getting concrete hash tables: package My_Hash_Tables is new String_Hash_Tables (Item_Type => My_Data); package My_Other_Hash_Tables is new String_Hash_Tables (Item_Type => My_Other_Data); An example for defaults for generic formal "in out" objects ----------------------------------------------------------- The motivating example for case #4; i.e. default values for generic formal "in out" objects; is the storage pool parameter often found in container libraries: generic type Item_Type is private; with function "=" (L, R : in Item_Type) return Boolean is <>; Pool : in out System.Storage.Root_Storage_Pool'Class; package Lists is type List is private; -- Operations on lists... private type Node; type Link is access all Node; for Link'Storage_Pool use Pool; type Node is record Data : Item_Type; Next, Prev : Link; end record; type List is new Ada.Finalization.Controlled with record Head, Tail : Link; end record; end Lists; Many users just don't care about storage pools (or even don't know what they are be good for). Advanced users, however, may well want to specify the pool in which the list should allocate its nodes, hence adding the pool as a generic formal parameter clearly makes sense. Unfortunately, it confuses less advanced users (and generally complicates instantiation in those cases where one really doesn't care). If one could provide a default value for "Pool", this confusion or complication could be avoided: we could simply declare the package as generic type Item_Type is private; with function "=" (L, R : in Item_Type) return Boolean is <>; Pool : in out System.Storage.Root_Storage_Pool'Class := The_Default_Pool; package Lists is ... I'll leave open what "The_Default_Pool" actually might be; on GNAT, a possible working implementation might be a pre-written pool that just uses the standard pool of some arbitrary access type. (Not right in a strict sense, but this is indeed a way that works -- on GNAT, at least.) See also my other proposal on "standard storage pool". But anyway, with defaults for generic formal "in out" objects, an instantiation of this list package can be as simple as package My_Lists is new Lists (My_Type); Inexperienced users or users that chose not to care about storage pools can use the package without extra hassles, while experienced or concerned users still have the possibility to provide their own storage pool. An illegal example for #4 (default for generic formal "in out" object): declare type Ptr is access Integer; procedure Free is new Ada.Unchecked_Deallocation (Integer, Ptr); P : Ptr := new Integer'(42); generic X : in out Integer := P.all; function Foo return Natural; function Foo is begin return abs (X); end Foo; begin Free (P); declare function Bar is new Foo; begin Ada.Text_IO.Put_Line (Natural'Image (Bar)); end; end; However, I do *not* consider this case a showstopper for the proposal, because equally illegal stuff can already be done using a default value for a generic formal "in" object: declare type Ptr is access Integer; procedure Free is new Ada.Unchecked_Deallocation (Integer, Ptr); P : Ptr := new Integer'(42); generic X : in Ptr := P; function Foo return Natural; function Foo is begin return abs (X.all); end Foo; begin Free (P); declare function Bar is new Foo; begin -- Free (P); Ada.Text_IO.Put_Line (Natural'Image (Bar)); end; end; (If the first call to "Free" is commented out, and the second one commented in, the effects are even more devastating. Try this with GNAT! [On Win NT, I get a Constraint_Error with the first "Free" (that's ok), while the program prints "0" with the second one (brrr!)]. That's not a critique of GNAT, but shows that stupid things can already be done.) In summary, I think the proposed additions would significantly enhance the capabilities of generics in Ada and be very useful for designing truly general and powerful generic abstractions that still could be easily instantiated in simple cases (because any generic parameters for advanced features would have sensible defaults). I also believe that these new defaults incur only a small to moderate implementation cost for compilers (although, me not being an Ada compiler guru, I might be mistaken). !ACATS test !appendix Editor's note: The original proposal was submitted by Thomas Wolf on Wednesday, June 12, 2002. It was edited slightly, but otherwise used intact as the initial draft of this AI. **************************************************************** From: Randy Brukardt Sent: Wednesday, June 12, 2002 10:32 PM Thanks for writing this, it saved me from having to do it. (I was going to make a similar proposal when I got a round tuit. > I cannot judge up-front how difficult or complex this would be to > implement, but it appears to me to be relatively straight-forward: one > just has to maintain info on what the defaults are, and in an instantiation, > supply these defaults as the actuals for any generic formals that do not > have explicit actuals. The whole instantiation process then should remain > unchanged. I'm concerned that the generic renaming would be significant work, as the idea of "partial actual parts" would be new, and would require a new mechanism to handle. Resolving these could be tricky, especially if they are mixed positional and named parameters. I have to wonder if these would be worth the work. Otherwise, I don't think that there would be significant implementation problem (if I did, I wouldn't have planned to introduce the idea). I'm not sure about the syntax, but that just carries over from CLA. ****************************************************************