*Ada HomeJune 16, 1997*

*The following is from John Volan - posted to comp.lang.ada*

It might have been nice if Ada somehow included such procedures as built-in features (e.g., as attributes), so that you wouldn't have to write them every time you introduced a new scalar type, but this is not the case. However, generic packages can help reduce the work of writing them by hand. Here's an example for integer types (similar generic packages can be worked up for modular, enumeration, floating and fixed-point types):

generic type Integer_Type is range <>; package Generic_Integer_Ops is procedure Increment (This : in out Integer_Type'Base); -- ++ analog procedure Decrement (This : in out Integer_Type'Base); -- -- analog procedure Increase (This : in out Integer_Type'Base; -- += analog By : in Integer_Type'Base); procedure Decrease (This : in out Integer_Type'Base; -- -= analog By : in Integer_Type'Base); procedure Multply (This : in out Integer_Type'Base; -- *= analog By : in Integer_Type'Base); procedure Divide (This : in out Integer_Type'Base; -- /= analog By : in Integer_Type'Base); procedure Modulo (This : in out Integer_Type'Base; -- %= analog By : in Integer_Type'Base); procedure Remainder (This : in out Integer_Type'Base; -- %= analog By : in Integer_Type'Base); pragma Inline (Increment, Decrement, Increase, Decrease, Multiply, Divide, Modulo, Remainder); end Generic_Integer_Ops;

Here's the body for this generic. As you can see, the implementations of these procedures are fairly trivial:

package body Generic_Integer_Ops is -- ++ analog procedure Increment (This : in out Integer_Type'Base) is begin This := Integer_Type'Succ(This); end Increment; -- -- analog procedure Decrement (This : in out Integer_Type'Base) is begin This := Integer_Type'Pred(This); end Decrement; -- +- analog procedure Increase (This : in out Integer_Type'Base; By : in Integer_Type'Base) is begin This := This + By; end Increase; -- -= analog procedure Decrease (This : in out Integer_Type'Base; By : in Integer_Type'Base) is begin This := This - By; end Decrease; -- *= analog procedure Multply (This : in out Integer_Type'Base; By : in Integer_Type'Base) is begin This := This * By; end Multiply; -- /= analog procedure Divide (This : in out Integer_Type'Base; By : in Integer_Type'Base) is begin This := This / By; end Divide; -- %= analog procedure Modulo (This : in out Integer_Type'Base; By : in Integer_Type'Base) is begin This := This mod By; end Modulo; -- %= analog procedure Remainder (This : in out Integer_Type'Base; By : in Integer_Type'Base) is begin This := This rem By; end Remainder; end Generic_Integer_Ops;

Users could instantiate this generic for any of their own integer types:

Error_Limit : constant := ...; type Error_Count_Type is range 0 .. Error_Limit; package Error_Count_Ops is new Generic_Integer_Ops (Error_Count_Type); ... Error_Count, Unhandled_Error_Count, Handled_Error_Count : Error_Count_Type := 0; ... -- when a given error first occurs: Error_Count_Ops.Increment (Error_Count); Error_Count_Ops.Increment (Unhandled_Error_Count); ... -- once a given error is handled: Error_Count_Ops.Decrement (Unhandled_Error_Count); Error_Count_Ops.Increment (Handled_Error_Count);

The users' work isn't reduced to zero: They still have to do the instantiation, and they have to include the instance name as a prefix on every call to one of these procedures (unless they throw in use-clauses).

There is also the possibility of code-bloat if these generics are instantiated for many user-defined types. However, this is alleviated somewhat by inlining all of the procedures: They'll only get inline-expanded where they are called, so they will be equivalent to the user's having written "X := X + 1;" and so forth by hand. (Also, code-bloat is really an implementation concern; it depends on how your particular compiler happens to implement generics.)

Another alternative is to exploit Ada's inheritance mechanism for non-tagged derived types. Instead of just providing the ops, the generic could provide a new type as well, in which case the ops become inheritable primitives for that type. For example:

generic type Implementation_Type is range <>; package Generic_Integers is type Integer_Type is new Implementation_Type'Base; procedure Increment (This : in out Integer_Type); -- ++ analog procedure Decrement (This : in out Integer_Type); -- -- analog procedure Increase (This : in out Integer_Type; -- += analog By : in Integer_Type); procedure Decrease (This : in out Integer_Type; -- -= analog By : in Integer_Type); procedure Multply (This : in out Integer_Type; -- *= analog By : in Integer_Type); procedure Divide (This : in out Integer_Type; -- /= analog By : in Integer_Type); procedure Modulo (This : in out Integer_Type; -- %= analog By : in Integer_Type); procedure Remainder (This : in out Integer_Type; -- %= analog By : in Integer_Type); pragma Inline (Increment, Decrement, Increase, Decrease, Multiply, Divide, Modulo, Remainder); end Generic_Integers;

(The body of Generic_Integers would be almost identical to Generic_Integer_Ops.)

This generic could be instantiated just for the standard types supported by the given compiler (avoiding potential code-bloat):

with Generic_Integers; package Integers is new Generic_Integers (Integer); with Generic_Integers; package Long_Integers is new Integers (Long_Integer); with Generic_Integers; package Short_Integers is new Integers (Short_Integer); ... etc.

(Or the users could define a few application-specific base types and instantiate for those. Or they could use types from Interfaces or whatever...)

Then a given user-defined type could be declared by deriving from one of the types from one of these instantiations:

Error_Limit : constant := ...; type Error_Count_Type is new Short_Integers.Integer_Type range 0 .. Error_Limit; -- "automagically" inherits Increment, Decrement, etc... ... -- when a given error first occurs: Increment (Error_Count); Increment (Unhandled_Error_Count); ... -- once a given error is handled: Decrement (Unhandled_Error_Count); Increment (Handled_Error_Count);

The downside of this is that a given user-defined type must be tied to some underlying implementation, which reduces its portability.

Do you want to share ideas or tricks about elegant Ada answers to common objections or false claims? |

- Ada Can Do It!

URL http://www.adahome.com/articles/1997-06/am_cando.html - Appendix 1: Assignment and increment operators, code examples

URL http://www.adahome.com/articles/1997-06/am_cando_a1.html - Appendix 2: Variable-length argument lists, code examples

URL http://www.adahome.com/articles/1997-06/am_cando_a2.html - Ada Programming FAQ

URL http://www.adahome.com/FAQ/programming.html#title

Page last modified: 1997-06-16