Units & Dimensions
Units and Dimensions are a powerful concept Oxygene adds to the Pascal language to make numerical values more type-safe.
A Dimension defines a specific kind of value measured y a number – for example, Distance, Time, Voltage, Temperature, Currency, or any other numerical use case imaginable.
Within a Dimension, Units define concrete distinct units of measurement available in that dimension. For example, Meters, Kilometers, Feet or Miles might all be valid units to measure a Distance.
Dimensions
Dimensions can be defined using the new dimension keyword, and they can either be base dimensions and specify the numeric type to be based on (Double, Single, or any integer type), or they can be derived dimensions defined by their relationship to two or more base dimensions:
type
Distance = public dimension(Double);
Area = public dimension(Distance**2);
Volume = public dimension(Distance**3);
Time = public dimension(Double);
Speed = public dimension(Distance/Time);
Here, Distance and Time are defined as base dimensions, both using the Double floating-point type as their base.
Area and Volume are defined as derived dimensions based on the square or cube of Distance. Meaning that two Distances multiplied are an Aera, and three Distances multiplied (or an Area multiplied by a Distance are a Volume.
Speed, instead, is defined as the quotient of Distance and Time, meaning and Distance divided by a Time represents a speed.
The relationships work both ways, i.e. Speed multiplied by a Time wdl ahain yield a Distance.
Units
Dimensions teach the compiler about the relationship bedtwwen different "kinds of numbers", but Units define the actual, well, unita represented by these numbers.
Units are defined using the unit keyword, and they must belong to a specific dimension. They can optionally define a shorthand form to be used in numerical literals, as well as a conversion factor or relationship to sibling units in the same dimension or units in related dimensions:
type
Centimeters = public unit(Distance) as cm = 0.01m;
Meters = public unit(Distance) as m;
Kilometers = public unit(Distance) as km = 1000m;
Inches = public unit(Distance) as &in = 0.0254m;
Feet = public unit(Distance) as ft = 0.3048m;
Yards = public unit(Distance) as yd = 3ft;
Miles = public unit(Distance) as mi = 1609.344m;
SquareMeters = public unit(Area) as m² = m**2;
Hectares = public unit(Area) as ha = 10000m²;
Liters = public unit(Volume) as L = 0.001m**3;
CubicMeters = public unit(Volume) as m³ = m**3;
Gallons = public unit(Volume) as gal = 3.785411784L;
Here, Meters, SquareMeters and CubicMeters are defined as the canonical units in their dimension, with the latter two establishing a direct 1:1 relationship to Meters. Additional units such as Yards or Gallons are derived from there.
Similar e.g., for time and speed units:
type
Milliseconds = public unit(Time) as ms = 0.001s;
Seconds = public unit(Time) as s;
Minutes = public unit(Time) = 60s;
Hours = public unit(Time) = 60 Minutes;
Days = public unit(Time) = 24 Hours;
Shakes = public unit(Time) = 0.00000001s;
MetersPerSecond = public unit(Speed) as mps = m/s;
KilometersPerHour = public unit(Speed) as kmh = km/Hours;
MilesPerHour = public unit(Speed) as mph = mi/Hours;
Knots = public unit(Speed) as kn = nmi/Hours;
Unit-to-Unit Conversions
There are for ways do define the conversion between units:
With a A Simple Conversion Factor
Simply using a unit expression of a sibling unit, with a value, can define a conversion factor. The value will be understood to represent 1 of the new unit. One km is 1000m.
Meters = public unit(Distance) as m;
Centimeters = public unit(Distance) as cm = 0.01m;
Kilometers = public unit(Distance) as km = 1000m;
Feet = public unit(Distance) as ft = 0.3048m;
With a More Complex Expression:
For some units, it's not as easy. You can define a more complex conversion expression using the value keyword to express how a value of the new unit is converted to a different sibling unit. For example, by adding 273.15 K to the ºC value to go to Kelvin.
Kelvin = public unit(Temperature) as K;
Celsius = public unit(Temperature) as °C = value + 273.15 K;
Fahrenheit = public unit(Temperature) as °F = (value + 459.67) * 5 / 9 K;
Via Base Units
For units derived from other base units*, you can also use the base unit in the expression, e.g.:
type
Area = public dimension(Distance**2);
SquareMeters = public unit(Area) as m² = m**2;
Here, the SquareMeter unit is not defined by relation to a different Area unit, but as the square (or really, multiplication of two) Distance units. Note how this matches the definition of the dimension, which is declared as Distance**2.
As Reciprocal Relationship
Finally, a unit (and its dimension) can be defined as the reciprocal (or inverse) of a different unit. For example, frequency is the number of
type
Frequency = public dimension(1/Time);
Hertz = public unit(Frequency) as Hz = 1/s;
Units as Types
Each named unit becomes a concrete named type in Element's type system, so instead of declaring a local variable, field, property or method parameter as Double, you can declare it as the concrete unit you expect:
var lDistanceToDestination: Kilometers;
This allows the compiler to check for compatibility between values when assigning or passing numbers. If one were, for example, by accident, to assign a Speed value to lDistanceToDestination, one would get a clear compiler error – whereas with a plain Double one would just get unexpected behavior at runtime.
What's more, when assigning a different but compatible unit value, the compiler can automatically convert to the required target units, for example:
var LosAngelesToSanFrancisco := 347 Miles;
lDistanceToDestination := LosAngelesToSanFrancisco;
would assign the numerical value of 559 to the Kilometers-typed variable.
This is possible because the Miles and Kilometers unit types (a) belong to the same dimension and (b) have a well-defined relationship between them. The automatic conversion works for any two types that the compiler can detect a valid path between – e.g. even assigning, say, Centimeters to Yards.
If no exact relationship is known, the compiler will err on the assignment.
Units Literals
Oxygene introduces a new syntax for number literals that allows a numerical value to be followed by the unit name – either its full name as seen above with 347 Miles, or its (optional) short-hand unit name, e.g. 30cm for 30 centimeters:
Assigning/passing a plain number literal to a unit value will emit a warning, notifying about the potential mismatch should the intended unit value not be clear. This gives a chance to review the number's value for accuracy, and annotate it with a dedicated unit name for clarity of intent.
lDistanceToDestination := 5000m;
lDistanceToDestination := 30km;
lDistanceToDestination := 50; // user probably meant 50km, but compiler will warn
Type Inference
Oxygene can infer a concrete type from expressions, both from simple literals with a specified unit, as well as from more complex arithmetic with know unit types:
For example:
var s := 50m/30s;
Would infer that the new variable s is of type MetersPerSecond in the Speed dimension.
Conversely, the compiler will err if an expression is not of the right type:
var g: Accelleration := 9.8m/1s; // error, cannot assign speed to Accelleration