In another topic about type system, we talked a bit about type checking of dimensioned quantities (values in meters, time in seconds etc.).
https://forum.lazarus.freepascal.org/index.php/topic,61781.0.htmlFew languages handle this natively. Units of measure are handled natively in F# and it can be implemented in Rust.
open Microsoft.FSharp.Data.UnitSystems.SI.UnitNames
[<Measure>] type miles
[<Measure>] type hour
let milesPerMeter = 0.00062137<miles/meter>
let secondPerHour = 3600.0<second/hour>
let distance = 1000.0<meter> * milesPerMeter
let time = 60.0<second> / secondPerHour
printfn "Speed: %A mph" (distance / time)
In Rust, using the crate dimensioned:
extern crate dimensioned as dim;
use dim::si;
fn main() {
let user_distance = 10.0 * M;
let user_time = 5.0 * s;
let user_speed = user_distance / user_time;
println!("Speed is {} m/s", user_speed);
}
This is checked at compile time only so there is no overhead and it can be applied to any numeric type.
I was wondering how to do that in FreePascal, playing a little bit I made a unit to have some basic support for meters, seconds and speed.
Attached is the unit to add support for units. It can be used like this:
uses UUnits;
var
distance: TLengthInKilometer;
time: TDurationInHour;
time2: TDuration; // SI unit is [s]
speed: TSpeedInKilometerPerHour;
acceleration, gravity: TAcceleration; // SI unit is [m/s2]
begin
distance := ((10*km + 100*m) * 2).ToKilometer;
time := 2*h;
speed := distance/time;
time2 := 10*s;
acceleration := speed/time2;
writeln('The distance is: ', distance.ToString);
writeln('The number of hundreds of meters is: ', distance / (100*m) );
writeln('The time is: ', time.ToString);
// casting to TDuration uses the default unit Second instead of Hour
writeln('Also: ', TDuration(time).ToString);
writeln('The speed is: ', speed.ToString);
writeln('Also: ', TSpeed(speed).ToString);
writeln;
distance := 50*km;
// ToVerboseString display the full name of the unit
writeln('Distance to go: ', distance.ToVerboseString);
time := distance / speed;
writeln('Time to get there: ', time.ToString);
writeln('Also: ', TDuration(time).ToString);
writeln;
writeln('The time to accelerate is: ', time2.ToString);
writeln('The acceleration is: ', acceleration.ToString);
writeln('Also: ', acceleration.ToKilometerPerHourPerSecond.ToString);
writeln;
gravity := 9.81*(m/s2);
writeln('Gravity is: ', gravity.ToVerboseString);
end.
Units are checked at compile time, so that's what we wanted. There is a tiny overhead because the compiler adds a few useless assembler instructions, but that's not very significant.
It is a bit tedious though to define all possible cases for operators. Maybe one could generate the code of UUnit, though it kind of makes sense to specify each operator that one actually want to allow.