SciNim / Unchained

A fully type safe, compile time only units library.
https://scinim.github.io/Unchained
109 stars 0 forks source link

Add quantity concepts #32

Closed Vindaar closed 1 year ago

Vindaar commented 1 year ago

Adds each quantity as a concept so that they can be used to match types in procedure arguments:

proc force[M: Mass; A: Acceleration](m: M, a: A): Force = m * a

(or combine with explicit return type and a .to(Foo) call to get a specific type back.

edit: Important note: Using implicit generics involving these quantity concepts is not supported, because due to some limitation we can't extract the underlying type from these generics in the common use cases. Might be a simple upstream bugfix away though.

edit 2: ended up adding a bunch more things, a few possibly breaking (for certain use cases some things behave differently now).

Full changelog:

* v0.3.0
- concepts for each quantity to match different units of same quantity
  in procedure calls!
- ~<~ for CT units now sorts positive powers before negative, this can
  be a *breaking* change
- in `.` define the resulting type based on what's given, not
  simplified, this can be a *breaking* change
- add ~toDef~ to combine the usage of ~defUnit~ with ~to~ (this can be
  problematic, see docstring)
- clean up ~defUnit~ and also always generate the short name version
  of a given unit
- update README

The sorting of CT units (the separate units in a compound like kg•m•... has changed a bit. Previously the most important aspect was the precedence of the base unit itself. The power was used as a tie breaker in case the same unit appears twice. Now the power will also be used to adjust the sorting by giving higher precedence to any unit that has a positive power, if it's compared with a unit with negative power. The motivation was mainly (assuming the user sorted their units in that order naturally) that otherwise in a unit like inch•s⁻¹ the order would be reversed to s⁻¹•inch as base units always had higher precedence than other units. This lead to a rather ugly unit notation that can cause users to trip.

Furthermore, we now generate always the long and short version when calling defUnit of the desired unit (previously only the version supplied by the user & the long one). In many cases the short and given one may be the same of course.

Next, comparisons of units finally now doesn't compare floats directly via == (as that is always problematic of course), but rather uses a custom almostEqual (copied from datamancer's existing proc). The epsilon used for comparison can be adjusted at CT by handing the parameter -d:UnitCompareEpsilon=<integer> where the integer is the negative power applied (default is 8, so ε = 1e-8).

The unit generated (if required) when using the dot operator now will also always generate the actual unit typed by the user instead of attempting a simplification. The gain for simplification (i.e reducing redundant powers of units) is not very big, but the possibility of accidentally changing the order of the units to something the user does not expect (due to something like the aforementioned change to <) is too big.

Finally, this also adds a toDef macro, which is essentially a local defUnit + to call for the use case when the unit is to be converted to a unit that isn't defined yet. It doesn't replace to, because in some use cases the compiler doesn't understand that a unit has been defined in the scope (because the macro call is written by the user via defUnit, but the macro hasn't been evaluated yet), thus redefining the unit. That can cause confusing errors with "got type X, needs type X = Alias".