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=-1.9 required=5.0 tests=BAYES_00 autolearn=ham autolearn_force=no version=3.4.4 X-Google-Language: ENGLISH,ASCII-7-bit X-Google-Thread: 103376,7fd5a5da28dace78 X-Google-Attributes: gid103376,public From: matthew_heaney@acm.org (Matthew Heaney) Subject: Re: Renaming Fixed Point Mutiplicative operator in Ada 95 Date: 1998/05/21 Message-ID: X-Deja-AN: 355280122 Content-Transfer-Encoding: 8bit References: <3561F32B.2F0B@innotts.co.uk> <01bd84c3$47215d60$440029a1@m00rq900> Content-Type: text/plain; charset=ISO-8859-1 Organization: Network Intensive Mime-Version: 1.0 Newsgroups: comp.lang.ada Date: 1998-05-21T00:00:00+00:00 List-Id: In article <01bd84c3$47215d60$440029a1@m00rq900>, "Stuart Hutchesson" wrote: (start of quote) Multiplication was just an example of the compilation problem - division is the REAL killer - how do you cope with divide-by-zero exceptions in critical system when all you are allowed is the catch-all exception handler, (and run-time checks switched off)? Terminating with an exception is not an option when you are 2 miles above the Atlantic! I would prefer to know EXACTLY what is going on in as much of the object code produced than leave it to the whim of the compiler. (end of quote) I would have handled it at a higher layer of abstraction - not by overriding the predefined arithematic operators of a scalar type. For example, type Target is limited private; procedure Move (T : in out Target) is begin exception when Constraint_Error => end Move; As you state, this may not be a solution for you, as you're trying to repair legacy code. (start of quote) (BTW - as Robert Dewar said - you can override Integer maths..... why not fixed point?) (end of quote) Yes, this is a non-orthogonal feature of the language. However, I wouldn't override the operators for an integer type either, and so the non-orthogonality never appears (to me). Maybe overriding the predefined operators for a scalar type would solve some problems, but that doesn't mean more won't appear. Consider this: generic type Integer_Type is range <>; package GP is ...; package GP is procedure Op (...) is O : Integer_Type; begin ... O := O + 10; ... type My_Integer is range ...; function Predefined_Add (L, R : My_Integer) return My_Integer renames "+"; function "+" (L, R : My_Integer) return My_Integer; package P is new GP (My_Integer); Now, which addition operation do you think package P uses? If you think it's going to use your special overridden version, then think again. Predefined operators reemerge if they're not explicitly imported. So you could fix this by doing generic type Integer_Type is range <>; with function "+" (L, R : Integer_Type) return Integer_Type is <>; package GP ... And all is well until some maintenance programmer does this package body GP is procedure Op (...) is O : Integer_Type; begin ... O := O - 5; and forgot to import the subtraction operator. If the type you pass in has overridden subtraction, then what does Op do exactly? Who knows? I am unable to mentally reason about behavior of this code, because I don't even know what subtraction means anymore. The reemergence of predefined operators should strike fear into the hearts of men. When I look at an addition or a subtraction, I have to do mental gymnastics to determine whether this is the correct operator. Or try to determine whether the generic will continue to work, if it's using the predefined operator instead of my overriding one. So, why not save me the trouble, and not override the predefined operators of a scalar type? It should give you great pause, any time you're thinking of replacing an predefined operator. How many places in the code is the type passed to a generic? Does that generic use the operator, and import it as a generic formal operation? Equality is even more pernicious. Suppose I have a stack, say generic type Stack_Item is private; Max_Depth : Positive; package Stacks is type Bounded_Stack is private; function "=" (L, R : Bounded_Stack) return Boolean; ... private type Stack_Item_Array is array (Positive range <>) of Stack_Item; type Bounded_Stack is record Items : Stack_Item_Array (1 .. Max_Depth); Depth : Natural := 0; end record; end Stacks; package body Stacks is function "=" (L, R : Bounded_Stack) is begin return L.Items (1 .. L.Depth) = R.Items (1 .. R.Depth); end; ... This looks OK, doesn't it? It compiles and runs fine. Until I do this type My_Integer is range ...; function "=" (L, R : My_Integer) return Boolean; package My_Integer_Stacks is new Stacks (My_Integer, 10); Now equality doesn't work anymore! That's because predefined equality reemerged as a result of passing the type to the generic. So let's fix the stack package, by importing the operator: generic type Stack_Item is private; with function "=" (L, R : Stack_Item) return Boolean is <>; Max_Depth : Positive; package Stacks is ...; Guess what: the package still doesn't work! Look carefully at the code. See anything wrong? You probably don't see any problems, and that's precisely the issue. Predefined equality has reemerged again, in spite of importing it as a generic formal, when I declared this array type STack_ITem_Array is ... of STack_Item; So when we do an array comparison, as in return L.Items (1 .. L.Depth) = R.Items (1 .. R.Depth); this is using the predefined equality for STack_Item, in spite of the fact that that operator was imported as a generic formal. The solution is to ditch the array comparison, so do an item-by-item compare manually: function "=" (L, R : Stack) return Boolean is begin if L.Depth /= R.Depth then return False; end if; for I in 1 .. L.Depth loop if L.Items (I) /= R.Items (I) then return False; end if; end loop; return True; end "="; That's why I like to take away equality when I declare the array: type Stack_Item_Array is ...; function "=" (L, R : Stack_Item_Array) return Boolean is abstract; Any array comparison will then be flagged as illegal at compile-time, to remind the unwary maintenance programmer who "forgot" that predefined equality re-emerges. I'm not saying, "Never override predefined operators of a scalar type." I'm just saying, do it after you've exhausted all other solutions. The Ada philosophy is that what the code does should be obvious by just looking at the code. But if you replace predefined arithematic operators, that truism doesn't apply, and it complicates mental reasoning about program behavior. Sooner or later, a predefined operator for a type will re-emerge, if you've overridden it. It may be that overriding the operator solves a problem now, but you're just playing Russian Roulette, because later on your code is going to break for another reason. The error won't even be directly visible. You just have to _know_. And how many programmers do? Perhaps you and your staff right now are fully aware of the perniciousness of re-emergence. But can you guarantee that a future developers will be aware of this "feature" of the language?