From mboxrd@z Thu Jan 1 00:00:00 1970 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on polar.synack.me X-Spam-Level: X-Spam-Status: No, score=-0.3 required=5.0 tests=BAYES_00, REPLYTO_WITHOUT_TO_CC autolearn=no autolearn_force=no version=3.4.4 X-Google-Language: ENGLISH,ASCII-7-bit X-Google-Thread: 103376,d4647d24f0fd6f61 X-Google-Attributes: gid103376,public From: "John G. Volan" Subject: Re: Ada can-do list Date: 1997/05/21 Message-ID: <33838D65.458D@sprintmail.com> X-Deja-AN: 243065330 References: <5lutl4$207s$1@prime.imagin.net> Reply-To: johnvolan@sprintmail.com Newsgroups: comp.lang.ada Date: 1997-05-21T00:00:00+00:00 List-Id: Samuel Mize wrote: > > ADA CAN DO IT! > Samuel Mize > > Common false claims about things Ada won't let you do, > and how to do them > (and why some are not directly supported) An excellent contribution! I applaud your effort! I do hope this is accepted at adahome. > 9. Assignment and increment operators (like += and ++ in C) One thing you might add is that it is easy to write Ada procedures which are equivalent in functionality to these C operators, but which are much safer because they would not be functions-with-side-effect. 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 procedure Increment (This : in out Integer_Type'Base) is -- ++ analog begin This := Integer_Type'Succ(This); end Increment; procedure Decrement (This : in out Integer_Type'Base) is -- -- analog begin This := Integer_Type'Pred(This); end Decrement; procedure Increase (This : in out Integer_Type'Base; -- +- analog By : in Integer_Type'Base) is begin This := This + By; end Increase; procedure Decrease (This : in out Integer_Type'Base; -- -= analog By : in Integer_Type'Base) is begin This := This - By; end Decrease; procedure Multply (This : in out Integer_Type'Base; -- *= analog By : in Integer_Type'Base) is begin This := This * By; end Multiply; procedure Divide (This : in out Integer_Type'Base; -- /= analog By : in Integer_Type'Base) is begin This := This / By; end Divide; procedure Modulo (This : in out Integer_Type'Base; -- %= analog By : in Integer_Type'Base) is begin This := This mod By; end Modulo; procedure Remainder (This : in out Integer_Type'Base; -- %= analog 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. ------------------------------------------------------------------------ Internet.Usenet.Put_Signature (Name => "John G. Volan", Home_Email => "johnvolan@sprintmail.com", Slogan => "Ada95: The World's *FIRST* International-Standard OOPL", Disclaimer => "These opinions were never defined, so using them " & "would be erroneous...or is that just nondeterministic now? :-) "); ------------------------------------------------------------------------