Ada Home
June 16, 1997

Ada Can Do It!

Appendix 1: Assignment and increment operators, code examples

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?

Resources


What did you think of this article?

Very interesting
Interesting
Not interesting
Too long
Just right
Too short
Too detailed
Just right
Not detailed enough
Comments:

Page last modified: 1997-06-16