introducing metric
Well, there is finally some Nim code going on here. I just got to release metric, a small dimensional analysis library for Nim, which I did as a weekend side project. The reason for making this was me noticing, that physical units did crop up quite a bit in the documentation for some code in the lab, without really being there for the programming language. This tends to get annoying, when you have a few float
s in hand and have to remember for each one the units it should have, and how and when conversions are necessary. To do away with all that, I would like to have type-level dimensions, such that the compiler knows, that the thing I hold in hand is a length in SI units, and if I want to set it in multiples of the speed of light times a fortnight, then so be it.
We can do just that in metric
, and say, set a length to 42 light-fortnights and get our result in SI units by default:
import metric
import metric.constants
const
fortnight = 2.0 * week
lightFortnight = speedOfLight * fortnight
var
someLength: Unit[Length]
## The `.=` and `.` operators make conversion
## between units easy and comfortable.
someLength.lightFortnight = 42.0
## Automatic conversion and pretty-printing
## to SI units.
echo someLength
Implementation
Now, for how all this works. We have a few types which make most of the magic happen: ProductDimension
, QuotientDimension
and the standard dimension types representing basic dimensional quantities from the SI system. QuotientDimension
represents the quotient of two units at compile time. It does this by being a generic type:
type
QuotientDimension[Numerator, Denominator] = object of BaseDimension
where Numerator
and Denominator
are again types representing units. So far so good, this is standard stuff. Slightly more interesting is the ProductDimension
type. It represents a product of an arbitrary number of units at the type level. Though we have no static[varargs]
type parameters, or the like, and using a type of the shape ProductDimension[X, Y]
is just a bit too unwieldy, we can indeed achieve something very similar using a tuple type:
type
ProductDimension[X: tuple] = object of BaseDimension
Using this, we may now pass an arbitrary unnamed tuple type as a generic param to ProductDimension
, containing all units to be multiplied, e.g. (Current, Time)
for charge or (Length, Length, Length)
for volume.
Now, the only missing thing to get this rolling is some type-level arithmetic, so we can write Force * Length
and have the certainty that this is the same as Energy
or Mass * Length ^ 2 / Time ^ 2
for the type system. For dimensional analysis this is easily done. Any unit can be written as a quotient Numerator / Denominator
of two products of units Numerator
, and Denominator
, where no unit appears both in the numerator and the denominator. This representation becomes unique, if we enforce, for example lexical ordering within the products. If we ensure, that after every call to *
, /
, ^
and all other functions of units, the unit types are in that canonical representation, all of unit arithmetic starts to just work (TM).
Conclusion
That’s it for the introduction of metric
. I will post again, once a new version is ready. While working, metric
is still work-in-progress, so things might change and break. If you want to try it, it is available on GitHub metric, documentation pages and examples will follow shortly. Have fun and enjoy your properly typed physical units.