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 floats 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.