Type operators already exist for the usual suspects. They need to be extended to be aware of the new subtype (did I mention this was a little awkward?). The equality and subclassing operators actually didn't need to be modified; since measure types are goose types, that works itself out. Same with tupling, and intersection. Bind and select (to bind a generic param, and select a method from a group respectively) don't really apply. Union (inheritance) is a TODO [eventually, you'll be able to inherit from classes without data fields]. So umm, not much work needed here.
But measures need a few more operators. Type operators to construct new types from the existing types. The three here are multiplication, division, and exponentiation. They have 2 key jobs. First is to do the math on the exponents of the Measures. The second is to do the type fiddling with the 'value' of the measure. int of meters/int of seconds should yield float of (m/s) for example. To do this, the operators will look at the implemented multiply and division methods for the numeric types and use that type for the determination. That might run into problems, but should be an okay way to keep the things generic enough for user numeric types.
So, for those who followed last post's link or know of the F# implementation, this is going to be a little different. Instead of float, meters will default to float (or more accurately be declared as measure meters{}) so the use of the measure will be a little less verbose/more focused. Of course a different type might be generated... say meters with a complex unit type if its manipulated that way. In general though, I want the programmer to be able to use what numeric they need; not annoy them with conversions or accommodations just to get measure safety.
So, the night's test code:
Measure Meters = Measure.Create("Meters"); Measure Seconds = Measure.Create("Seconds"); List mps = new List(); mps.Add(new UnitOfMeasure(Meters, 1)); mps.Add(new UnitOfMeasure(Seconds, -1)); Measure V = Measure.Create(mps); Console.WriteLine("[m,s^-1] name: {0}",V.Name); Measure v2 = Tangent.Lang.Type.PerOperator(Meters, Seconds) as Measure; Console.WriteLine("m/s -> {0}", v2.Name); Measure a = Tangent.Lang.Type.PerOperator(v2, Seconds) as Measure; Console.WriteLine("mps/s -> {0}", a.Name); Measure a2 = Tangent.Lang.Type.PerOperator(Meters, Tangent.Lang.Type.ExponentiationOperator(Seconds, 2,1)) as Measure; Console.WriteLine("m/(s^2) -> {0}", a2.Name); Measure m = Tangent.Lang.Type.MultiplyOperator(v2, Seconds) as Measure; Console.WriteLine("mps * s -> {0}", m.Name);
[m,s^-1] name: Meters per Secondsm/s -> Meters per Secondsmps/s -> Meters per Seconds^2m/(s^2) -> Meters per Seconds^2mps * s -> Meters
Eventually I aim to get aliases into measure definitions, probably with certain names marked as singular/plural/abbreviation to generate better names, and to give the option of a terse 'abbreviation-style' name.
you seem to be saying that types other than float can serve as base types depending on use? This seems like it would be so flexible as to actually render unit checking useless by hindering its ability to tell anything? Likely I am missing something. Anyways if I were to do something like this your approach vs F#'s (parameterizing base types across measure v parameterizing over floats) would be the approach I would take, it allows for more generic and flexible code.
Also how do measures properly combine when operations are done on the quantities themselves and how strong will the measure typing be? Will there be implicit coercions?