KarolS / units

Flexible, statically-checked experimental library for units of measurement
MIT License
24 stars 2 forks source link

Where would you like to see non-SI units like "rpm"? #5

Open mkiedys opened 10 years ago

mkiedys commented 10 years ago

Revolutions per minute or rpm is non standard unit but is commonly used. Where would you like to see units like this one?

KarolS commented 10 years ago

That's a good question.

SI.scala should contain only SI units or non-SI units officially accepted for use with the SI:

Since rpm is quite like 1/60 Hz, it should get the same treatment as other not officially accepted non-SI units that are derived as a simple fraction of an SI unit. So, since torr and atmosphere are already in SI.scala, nothing wrong will happen if rpm goes there too.

I should clean up this naming mess by 0.3.0. There are already non-US units in USCustomary.scala.

I also wonder if rpm should be a separate unit, or should there be a unit called "revolution", with "rpm" as an alias for "revolution/minute". In short, what should 100.of[rpm].mkString be?

mkiedys commented 10 years ago

From https://en.wikipedia.org/wiki/Rpm

Revolutions per minute (abbreviated rpm, RPM, rev/min, r/min, or r·min−1) is a measure of the frequency of a rotation. It annotates the number of turns completed in one minute around a fixed axis. It is used as a measure of rotational speed of a mechanical component. Standards organizations generally recommend the symbol r/min,[citation needed] which is more consistent with the general use of unit symbols. It is not enforced as an international standard. In French for example, tr/mn (tours par minute) is commonly used, and the German equivalent reads U/min (Umdrehungen pro Minute).

Symbol r/min recommended by Wikipedia and some unknown Standards organizations looks good for me.

scala> 100.of[rpm].mkString
res1: String = 100 r/min

There are also other issues worth addressing:

1) Joule and newton metre (both derived from SI) share same backing expression. Do we want newton metre to be an alias for joule or a standalone unit? If so I vote for a standalone unit. joule and newton metre.

2) Do we want to have newton metre definied in the first place? Or should we convince everyone to write N × m everytime they need newton metre?

3) Scala prohibits use of some characters in type definition. Should we just use Nm or give up with such symbol (vide 2). In data sheets newton metre is often written as Nm, mNm..

type N⋅m = N × m // illegal name because of ⋅

4) What about containers for bounded and unbouned spans like -40...+100°C? Any plans? :)

5) Implicit conversions from one derived unit to another (kilo, milli, micro, nano etc.) take a lot of place, introduce noise and are prone to bugs from copying and pasting. Have you considered macros?

6) Is this project a good place for useful units like %, , , ppm and similar?

KarolS commented 10 years ago

The minor problem with r/min is that in current state of how mkString works it would require to define r as DefineUnit[_r], therefore making it equal to all units with symbol r.

In the very beginnings, DefineUnit took two parameters, one was used for comparison purposes, the second was used for mkString, but that could lead to equal units with different string representation.

I'm now considering adding suffixes to unit definitions: a new symbol for dividing the displayed symbol from its variant. For example, currently Imperial gallon is defined as DefineUnit[_g~:_a~:_l~:_U~:_K], which means 1.of[imperial_gallon].mkString gives 1 galUK. A new divider could be added, so for example Imperial gallon could be redefined as DefineUnit[_g~:_a~:_l~:_variant~:_U~:_K], revolution as DefineUnit[_r~:_variant~:_e~:_v], and only the part before _variant would be used by mkString. Of course, that would break binary compatibility, so it's for 0.3.0.

As for macros, I have considered them, but I don't feel I could write them correctly. As for long unit stacks (nano-, micro-, milli-, kilo-, mega-), I think that instead of a macro, a simple definition like this: conversionStack[nanometre, micrometre, millimetre, centimetre, metre, kilometre](1000, 1000, 10, 100, 1000) would be enough. This can be done without macros, only with implicits, but on the other hand it could be slow: current conversions are constants and get inlined. Of course, I still don't know how to simplify the conversions between binary and decimal prefixes for bits and bytes. What we have now is way unsatisfactory.

As for newton-metre, there's also kilowatt-hour, and other similar units. I don't think they should be units on their own; if someone needs them, they could always define type Nm = N × m once and use Nm afterward. As for mNm, they can use type mNm = N × mm and implicit val implicit__Nm_to_mNm = implicit__m_to_mm.times[N].

As for newton-metre vs joule thing... Well, here it gets complicated. All SI units, including newton and joule, are defined in terms of basic units (metre, kilogram, second). Therefore, 1.of[newton].mkString prints 1 kg m s^(-2), and both 1.of[newton × metre].mkString and 1.of[joule].mkString give 1 kg m^2 s^(-2). For all purposes, newton × metre and joule are the same type (implicitly[joule =:= (newton × metre)] succeeds). There are several _repr variants defined, which can be considered different units, but they do not work as well as the standard ones: they are harder to convert, have no versions with prefixes, and so on. Their main goal is to allow for nicer printing in polymorphic functions.

I don't know how much typesafety you want, but for now, by default joules, newton-metres, and kilogram-square-metres-per-square-second are all considered equal. They're all the unit of the result of force * distance after all.

And finally, about percents: really unsure. Would % be a subunit of _1? Then we would get silliness like this: 5 + 23.of[%] == 523.of[%]. Would it not be? Then 0.23.convert[%] would not compile. If the semantic problems with percents were solved, then they could be added. I have already been thinking about it (that's why there are percent and permille signs available in my typelevel charset), but I wasn't sure how to implement them, so I didn't.