🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Tangent: Measurement infrastructure

Published December 20, 2008
Advertisement
I had some time tonight to work on Tangent. On the menu? Infrastructure for unit of measurement types (as the clever folks might've guessed by the title). Under the hood, a Measure inherits (awkwardly) from Type. It adds a list of UnitOfMeasure, a comparator, and a few util methods for now. A UnitOfMeasure contains a Measure and an exponent (in numerator/denominator form rather than float).

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.
Next Entry Bleh!
0 likes 6 comments

Comments

Daerax
Quote: Original post by Telastyn

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>, meters will default to float (or more accurately be declared as measure<float> 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.
The problem I sees here is when defining a function that is intended to be generic only over units, how do you limit the inputs without some initial form of verbosity similar to F#'s? It seems that type checking will be made more expensive if it also has to always check for and distinguish special behaviour for measures.

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?
December 21, 2008 12:29 PM
Jotaf
Woah, so you mean this is not tied to the metric system? I got that impression from the other posts. This is good, there are application-specific measures (Mb/s?) and this way we can basically construct any measure system we want :)
December 21, 2008 01:25 PM
Telastyn
Quote: Original post by Daerax
The problem I sees here is when defining a function that is intended to be generic only over units, how do you limit the inputs without some initial form of verbosity similar to F#'s?


Use Measure<T> (where T is the numeric type). I'm fine with the initial verbosity; some verbosity during the type definition. I'd like to avoid verbosity at variable declaration with the defined type.

Quote:
you seem to be saying that types other than float can serve as base types depending on use?


Ideally, the 'natural' base type for a measure will be defined alongside the measure.

Quote:
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.


It is perhaps not clear, but (in F# style) m<int>, m<float>, m<complex>, etc... all exist and are distinct types. I'd like though the declarations to not require the numeric type everywhere you want to use them. 10 meters is m<int> a bare declaration will use the 'natural' base type as defined in the measure declaration.

Quote:
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?


The only implicit coercions will be the coercions that exist for the numerics themselves. If int can promote to float (at the moment it can't, in the future it probably will), then meters<int> can promote to meters<float>. If you want the bare numeric from the measure, there will be a property to explicitly retrieve that.
December 21, 2008 04:28 PM
Telastyn
Quote: Original post by Jotaf
Woah, so you mean this is not tied to the metric system? I got that impression from the other posts. This is good, there are application-specific measures (Mb/s?) and this way we can basically construct any measure system we want :)


Yeah, the original idea was actually not metric-centric. It was to disambiguate 'piles' of things in games (x gold vs x wood; str vs int; etc.). Most of the work I've seen was though centered around physical measurement and everyone knows about them.
December 21, 2008 04:31 PM
Daerax
Quote: Original post by Telastyn
Answered

Excellent responses. thank you. Definitely more focused [in code], flexible in implementation and possibly more elegant than F#'s. although it seems in terms of verbosity not necessary leaner since F# can leverage type inference where possible.
December 22, 2008 05:31 AM
Telastyn
Quote: Original post by Daerax
although it seems in terms of verbosity not necessary leaner since F# can leverage type inference where possible.


True. Type inference does make things less verbose though I think it's often too terse.

Though I suspect the order of operation inference in Tangent is not possible with full type inference, even if I liked/wanted type inference.


Oh, and in my answers I did forget; I probably will allow implicit conversions between measures that are of the same type, but a different magnitude (kg -> g, nm -> m, and probably m -> ft, days -> seconds). Eventually.
December 22, 2008 08:30 AM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement
Advertisement