Types in Ada with their inherent strong binding lead, if applied correctly, to great data safety, i.e. they prevent comparing inadvertently apples with pears. This conception has proven so successful that languages like C, which originally had only very limited typing at their disposal, implement similar ideas in their new standards. Now also physical dimensions have the property of being combinable only in a very special way, and every physicist is used to checking equations with respect to dimensional correctness. So it is tempting to impose this chore upon the compiler by representing dimensions as types. This, however, is not that easy as can be seen by observing that indeed meter plus meter results in meter, yet meter times meter results in meter squared, and Ada operators work type-preserving: For A, B of type T, the product A*B is also of type T.
There are many examples in literature showing how to implement units of measure correctly and safely with Ada types. They all use methods similar to the one presented here.
Do not do this. The considerations below will show that Ada is even absolutely unsuitable to handling dimension checking in assignments with reasonable expenditure.
Let physical dimensions be implemented by Ada types:
type Length is new Float; type Time is new Float; type Speed is new Float;
The predefined multiplication and division operators are of no use since they remain within the type, and what is more, use of types in this naive way is liable to lead to gross misinterpretations. For error estimation e.g., the relative error Delta := Delta_x/x (a pure number) has to be declared as a dimensioned variable
Delta, X, Delta_X: Length;
for
Delta := Delta_X/X; -- dimension 1
to be compilable, an awful misleading of potential readers of this program! (It is similar nonsense to call transcendental functions like exp for dimensioned arguments.)
There exist two possibilities to represent the equation s = vt in Ada:
S := Length (V) * Length (T);
or
S := V * T;
the "*" operator in the latter case being defined as follows:
function "*" (Left: Speed; Right: Time) return Length;
Use of the first way marrs the assignment with type conversions, rendering it unreadable (at least for more complicated equations) and thus error prone, and makes the compiler lose the ability to type check the operation by comparing dimensions on the left and right hand sides, which is the very reason for introducing different types for different dimensions. So rather than gaining, we lose.
Use of the second way on the first glance seems OK. We utilize the usual style of physical equations and the compiler checks the dimension during compile time. So in order to avoid the problem mentioned in the first paragraph, we use private types for our dimensioned units:
generic type Number is digits <>; package Dimension is type Unit is private; -- "=" Equality is predefined function "<" (Left, Right: Unit) return Boolean; function "<=" (Left, Right: Unit) return Boolean; function ">" (Left, Right: Unit) return Boolean; function ">=" (Left, Right: Unit) return Boolean; function "+" (Right: Number) return Unit; function "-" (Right: Number) return Unit; function "+" (Right: Unit) return Unit; function "-" (Right: Unit) return Unit; function "abs" (Right: Unit) return Unit; function "+" (Left, Right: Unit) return Unit; function "-" (Left, Right: Unit) return Unit; function "/" (Left, Right: Unit) return Number; function "*" (Left: Number; Right: Unit ) return Unit; function "*" (Left: Unit ; Right: Number) return Unit; function "/" (Left: Unit ; Right: Number) return Unit; function Measure (X: Unit) return Number; private type Unit is new Number; pragma Inline ("<", "<=", ">", ">=", "+", "-", "abs", "*", "/", Measure); end Dimension;
The unary adding operators are used as constructors, which seems natural enough. Note that there is no multiplication of two dimensioned units; their devision results in a pure number.
Now we define our types:
package Physical_Item is new Dimension (Float); type Length is new Physical_Item.Unit; type Time is new Physical_Item.Unit; type Speed is new Physical_Item.Unit; function "*" (Left: Speed ; Right: Time ) return Length; function "/" (Left: Length; Right: Time ) return Speed; function "/" (Left: Length; Right: Speed) return Time;
Until here, everything is OK. So as long as you stay within the limits of this simplistic model, you may well use this method.
But you have to admit: This is not real physics.
Expanding this simple system to areas, volumes, accelerations ... affords more types and hence more functions.
type Area is new Physical_Item.Unit; function "*" (Left, Right: Length) return Area; function "/" (Left: Area; Right: Length) return Length; -- and so on for the other types
Now think of vectorial algebra. Computing the modulus of an acceleration vector leads to the fourth time power in the denominator. We begin to suspect this will become unruly: Until which power in each dimension shall we go? And we are far from the end of the road: The full SI system has seven base dimensions: meter, kilogramm, second, ampere, kelvin, candela, mol.
Our attempt leads us to a plethora of overloaded functions. The number of function definitions afforded runs into the hundreds. (By the way: although dimensions of intermediate results depend on the order the operators are executed in expressions like nRT/p, there is no need to introduce parentheses because operators of the same precedence level are executed in textual order from left to right).
One could object that this definition has to be made only once and for all in a reusable package, and later-on the package can simply be withed and used without any need for the user to care about the package's complexity, but unfortunately the argument is not fully correct. Apart from the most probable compile time explosion, it takes into account only simple multiplication and division. Operations like exponentiation an and root extraction root (n, a) are not representable at all. So how could we use this method for the (mixed) electrostatic unit system, which for several reasons is far better suited to theoretical physics? One esu (electrostatic unit) is defined as g1/2 cm3/2 s-1, and gauss evaluates to g1/2 cm-1/2 s-1. Here type conversions have still to be used.
So we have to confess that our attempt to let the compiler check equations at compile time has miserably failed. The only proper way to deal with dimensions in full generality is either to handle them as attributes of the numeric values that are calculated and checked at run-time or to use preprocessors. Also for these methods, there are many examples to be found in literature.
The preprocessor method can be left aside because constructing a preprocessor is normally too complicated.
The attribute method is unusable in cases where execution speed is essential.
So for hard real-time embedded systems, another way is needed to deal with dimensioned items. You can find such a method on my homepage under the title From the Big Bang to the Universe. It has successfully been in use for many years now in at least three avionic embedded systems. One Ada package, the Universe, is constructed that holds all basic definitions to allow dealing with physical quantities without type conversions while keeping the advantages of strong typing in critical cases. Basic mathematical functions like the square root and the sine are also included as predefined operations of the numeric base types, thus rendering possible the dimensionally correct computation of formulae like x=x0cos(phi) in degrees and radians again without any type conversions.