digitalmars.D.learn - How to have strongly typed numerical values?
- Nicholas Londey (14/14) Sep 04 2012 Hello.
- bearophile (8/16) Sep 04 2012 Using the static typing to avoid similar bugs is the smart thing
- Don Clugston (9/13) Sep 05 2012 I'd be interested to know if that idea is ever used in real code. I
- bearophile (25/36) Sep 05 2012 As usual I don't have usage statistics.
- Paul D. Anderson (6/43) Sep 05 2012 FWIW, there was a proposal (JSR-275) to add support for units of
- anonymous (369/383) Sep 04 2012 I don't know about any standard (or wide-spread) solution. I did
- =?UTF-8?B?QWxpIMOHZWhyZWxp?= (16/19) Sep 04 2012 UFCS enables some interesting syntax:
- ixid (17/17) Sep 04 2012 Using this:
- =?UTF-8?B?QWxpIMOHZWhyZWxp?= (47/64) Sep 04 2012 I have not used such frameworks myself but have seen them mentioned a
- Simen Kjaeraas (10/22) Sep 04 2012 Not mine, but this is the implementation I use:
- anonymous (8/14) Sep 04 2012 Fascinating how similar this looks to my (completely independent)
- David Nadlinger (8/12) Sep 06 2012 Can you elaborate on that? I must admit that I didn't actively
- anonymous (17/24) Sep 06 2012 Maybe I'm missing something fundamental, but this little test
- Nicholas Londey (23/23) Sep 06 2012 Thank you everyone for your replies.
- bearophile (6/10) Sep 06 2012 I see. If the range and precision are statically know then it's
Hello. I am trying to work out if there is existing support for strongly typed numerical values for example degrees west and kilograms such that they cannot be accidentally mixed in an expression. I have vague recollection of seeing a presentation by Walter talking about this but I cannot seem to find it. I have looked at std.typecons.Typedef and Proxy but neither seem to do what I want or at least fail to compile for the expression n = n + n;. I could easily implement my own as I have done in C++ in the past but assume there is a standard implementation which I would prefer. Any help or links to examples much appreciated. Regards, Nicholas
Sep 04 2012
Nicholas Londey:for example degrees west and kilograms such that they cannot be accidentally mixed in an expression.Using the static typing to avoid similar bugs is the smart thing to do :-) I have vague recollection of seeing a presentationby Walter talking about this but I cannot seem to find it. I have looked at std.typecons.Typedef and Proxy but neither seem to do what I want or at least fail to compile for the expression n = n + n;.std.typecons.Typedef is kind of broken.but assume there is a standard implementation which I would prefer.I think Phobos doesn't have a standard implementation. Bye, bearophile
Sep 04 2012
On 05/09/12 03:42, bearophile wrote:Nicholas Londey:I'd be interested to know if that idea is ever used in real code. I mean, it's a classic trendy template toy, but does anyone actually use it? I say this because I've done a lot of physics calculation involving multiple complicated units, but never seen a use for this sort of thing. In my experience, problems involving units are easily detectable (two test cases will catch them all). The most insidious bugs are things like when you have used constants at 25'C instead of 20'C, or when you have a sign wrong.for example degrees west and kilograms such that they cannot be accidentally mixed in an expression.Using the static typing to avoid similar bugs is the smart thing to do :-)
Sep 05 2012
Don Clugston:I'd be interested to know if that idea is ever used in real code. I mean, it's a classic trendy template toy, but does anyone actually use it?As usual I don't have usage statistics. I like dynamic languages, like Python. But if you give me a static type system, then I want something back from the language. Detecting certain bugs at compile-time is one way to pay me back. If you want most programmers to use a feature like units, it needs: - Built-in, in the standard library, or easy to install; - To have a compact and very nice looking syntax; - Easy enough to use; - Flexible, to be able to represent all units you find in many real problems; - Have zero or near-zero run-time costs, in both CPU and memory; - Give acceptable error messages. If a units system will be present in Phobos and it will be very good to use, maybe D programmers will use them. I am currently using Haskell a little, and I am enjoying giving types to values to tell them apart in terms of their usage.I say this because I've done a lot of physics calculation involving multiple complicated units, but never seen a use for this sort of thing. In my experience, problems involving units are easily detectable (two test cases will catch them all). The most insidious bugs are things like when you have used constants at 25'C instead of 20'C, or when you have a sign wrong.There are some famous problems caused by mixing units: http://lamar.colostate.edu/~hillger/unit-mixups.html Maybe this is the most famous: http://mars.jpl.nasa.gov/msp98/news/mco990930.html Bye, bearophile
Sep 05 2012
On Wednesday, 5 September 2012 at 11:50:12 UTC, bearophile wrote:Don Clugston:FWIW, there was a proposal (JSR-275) to add support for units of measure to Java that was ultimately rejected. The implementation can be found at JScience.org, written and maintained by the website owner, Jean-Marie Dautelle, who also led the JSR team. PaulI'd be interested to know if that idea is ever used in real code. I mean, it's a classic trendy template toy, but does anyone actually use it?As usual I don't have usage statistics. I like dynamic languages, like Python. But if you give me a static type system, then I want something back from the language. Detecting certain bugs at compile-time is one way to pay me back. If you want most programmers to use a feature like units, it needs: - Built-in, in the standard library, or easy to install; - To have a compact and very nice looking syntax; - Easy enough to use; - Flexible, to be able to represent all units you find in many real problems; - Have zero or near-zero run-time costs, in both CPU and memory; - Give acceptable error messages. If a units system will be present in Phobos and it will be very good to use, maybe D programmers will use them. I am currently using Haskell a little, and I am enjoying giving types to values to tell them apart in terms of their usage.I say this because I've done a lot of physics calculation involving multiple complicated units, but never seen a use for this sort of thing. In my experience, problems involving units are easily detectable (two test cases will catch them all). The most insidious bugs are things like when you have used constants at 25'C instead of 20'C, or when you have a sign wrong.There are some famous problems caused by mixing units: http://lamar.colostate.edu/~hillger/unit-mixups.html Maybe this is the most famous: http://mars.jpl.nasa.gov/msp98/news/mco990930.html Bye, bearophile
Sep 05 2012
On Wednesday, 5 September 2012 at 00:55:12 UTC, Nicholas Londey wrote:Hello. I am trying to work out if there is existing support for strongly typed numerical values for example degrees west and kilograms such that they cannot be accidentally mixed in an expression. I have vague recollection of seeing a presentation by Walter talking about this but I cannot seem to find it. I have looked at std.typecons.Typedef and Proxy but neither seem to do what I want or at least fail to compile for the expression n = n + n;. I could easily implement my own as I have done in C++ in the past but assume there is a standard implementation which I would prefer. Any help or links to examples much appreciated. Regards, NicholasI don't know about any standard (or wide-spread) solution. I did my own and it works well for me. Code follows, no rights reserved: module quantity; /// Examples: unittest { mixin BasicUnit!"ampere"; mixin BasicUnit!"second"; // multiplication of different quantity types yields a new type auto coulomb = ampere * second; static assert(!is(typeof(coulomb) == typeof(ampere))); static assert(!is(typeof(coulomb) == typeof(second))); static assert(is(typeof(coulomb / second) == typeof(ampere))); real dimensionless = second / second; // quantities of the same type can be added, quantities can be multiplied // with dimensionless values assert(coulomb + coulomb == coulomb * 2); // can't add different types static assert(!__traits(compiles, ampere + second)); static assert(!__traits(compiles, ampere + coulomb)); static assert(!__traits(compiles, ampere + 1)); } import std.traits: isIntegral; import std.typetuple; debug import std.stdio; /** Mixin to define a basic unit with the given name. Params: name = must be a valid identifier */ mixin template BasicUnit(string name, Value = real) { struct Id {} enum q = Quantity!(UnitSpec!(Id, 1), Value)(make!Value(1)); mixin(" static if(is(typeof(" ~ name ~ "))) { static assert(false); //TODO: better error message } else { alias q " ~ name ~ "; } "); } /// Examples: unittest { mixin BasicUnit!"ampere"; static assert(isQuantity!(typeof(ampere))); } /** A physical quantity, i.e. a numerical value and a unit of measurement. Do not instantiate Quantity directly. Use BasicUnit instead. */ private struct Quantity(UnitSpec, Value) { Value value; /// the (dimensionless) numerical value of the quantity static assert(isUnitSpec!UnitSpec); static assert(UnitSpec._Spec.length >= 2); private alias UnitSpec _UnitSpec; private alias Value _Value; /** Overloads for arithmetic operators. Quantities can be added to other quantities of the same type. Dimensionsless quantities can also be added to scalars, resulting in a scalar. Quantities can be multiplied by other quantities and by scalars. */ Quantity opBinary(string op)(in Quantity right) const if(op == "+" || op == "-") { mixin("return Quantity(value " ~ op ~ " right.value);"); } /// ditto void opOpAssign(string op)(in Quantity right) if(op == "+" || op == "-") { mixin("value " ~ op ~ "= right.value;"); } Quantity opUnary(string op)() const if(op == "-" || op == "+") { mixin("return Quantity(" ~ op ~ "value);"); } /// ditto auto opBinary(string op, R)(in R right) const if(op == "*" || op == "/") { static if(is(R == Quantity)) { // => dimensionsless mixin("return value " ~ op ~ " right.value;"); } else static if(isQuantity!R) { alias _UnitSpec.Binary!(op, R._UnitSpec) ResultUnitSpec; mixin("Value v = value " ~ op ~ " right.value;"); return Quantity!(ResultUnitSpec, Value)(v); } else static if(is(R : Value)) { mixin("return Quantity(value " ~ op ~ " right);"); } else { static assert(false); } } /// ditto Quantity opBinaryRight(string op)(in Value left) const if(op == "*" || op == "/") { mixin("return Quantity(left " ~ op ~ " value);"); } /// ditto void opOpAssign(string op)(in Value right) if(op == "*" || op == "/") { mixin("value " ~ op ~ "= right;"); } /** comparison with other quantities of the same type */ bool opEquals(ref const(Quantity) other) const { return typeid(Value).equals(&value, &other.value); } /// ditto bool opEquals(const(Quantity) other) const { return opEquals(other); // forward to the ref version } /// ditto int opCmp(ref const(Quantity) other) const { return typeid(Value).compare(&value, &other.value); } /// ditto int opCmp(const(Quantity) other) const { return opCmp(other); // forward to the ref version } /// hash_t toHash() const nothrow safe { return typeid(Value).getHash(&value); } /// static property Quantity zero() { return Quantity(make!Value(0)); } } unittest { static assert(!is(Quantity!(UnitSpec!(), real))); mixin BasicUnit!"ampere"; mixin BasicUnit!"second"; auto coulomb = ampere * second; real dimensionless = second / second; static assert(!__traits(compiles, 1 + ampere)); coulomb += coulomb; assert(coulomb.value == 2); coulomb *= 2; assert(coulomb.value == 4); assert(-ampere == -1 * ampere); assert(+ampere == ampere); } /// alias Curry!(isTemplatedType, Quantity) isQuantity; /* Params: Spec = Alternating basic types and exponents. Basic types must be sorted by their mangled names. No duplicates allowed. This is a struct just because a type is easier to work with than a template. */ private struct UnitSpec(Spec ...) { private alias Spec _Spec; private template less(Lhs, Rhs) { enum bool less = Lhs.mangleof < Rhs.mangleof; } static if(Spec.length >= 2) { static assert(is(Spec[0])); static assert(is(typeof(Spec[1]))); static assert(isIntegral!(typeof(Spec[1]))); static if(Spec.length > 2) { static assert(less!(Spec[0], Spec[2])); } alias Spec[0 .. 2] Head; alias Spec[0] HeadId; enum headExp = Spec[1]; alias UnitSpec!(Spec[2 .. $]) Tail; // Merges U's Spec with the current one. template Binary(string op, U) if(op == "*") { static assert(isUnitSpec!U); static if(U._Spec.length == 0) { alias UnitSpec Binary; } else static if(less!(HeadId, U.HeadId)) { alias UnitSpec!(Head, Tail.Binary!("*", U)._Spec) Binary; } else static if(less!(U.HeadId, HeadId)) { alias UnitSpec!(U.Head, Binary!("*", U.Tail)._Spec) Binary; } else { static assert(is(HeadId == U.HeadId)); private alias Tail.Binary!("*", U.Tail) BinaryTail; private enum expSum = headExp + U.headExp; static if(expSum == 0) { alias BinaryTail Binary; } else { alias UnitSpec!(HeadId, expSum, BinaryTail._Spec) Binary; } } } } else { static assert(Spec.length == 0); // see above template Binary(string op, U) if(op == "*") { static assert(isUnitSpec!U); alias U Binary; } } // Division is multiplication by the inverse. template Binary(string op, U) if(op == "/") { static assert(isUnitSpec!U); alias Binary!("*", U.Inverse) Binary; } // Exponentiation is repeated multiplication. template Pow(int e) { static assert(e > 0); static if(e == 1) { alias UnitSpec Pow; } else { alias Binary!("*", Pow!(e - 1)) Pow; } } // Negates the exponents. static if(Spec.length == 0) { alias UnitSpec Inverse; } else { alias UnitSpec!(_Spec[0], -1 * _Spec[1], Tail.Inverse._Spec) Inverse; } } unittest { static struct AmpereId {} static struct SecondId {} alias UnitSpec!(AmpereId, 1) Ampere; alias UnitSpec!(SecondId, 1) Second; alias UnitSpec!(SecondId, -1) Hertz; alias UnitSpec!(AmpereId, 1, SecondId, 1) Coulomb; static assert(is(Second.Binary!("*", Ampere).Binary!("*", Ampere) == UnitSpec!(AmpereId, 2, SecondId, 1))); static assert(is(Second.Inverse == Hertz)); // no types version(none) static assert(!__traits(compiles, UnitSpec!(1, 1))); /*NOTE: dmd 2.060 complains even though this is supposed to fail compilation */ // no exponents static assert(!__traits(compiles, UnitSpec!(SecondId, AmpereId))); // not ordered alphabetically static assert(!__traits(compiles, UnitSpec!(SecondId, 1, AmpereId, 1))); } private alias Curry!(isTemplatedType, UnitSpec) isUnitSpec; /** Exponentiation for quantities. The exponent has to be known at compile time, because it changes the result type. */ auto pow(int exponent, Q)(Q quantity) if(isQuantity!Q) { alias Q._UnitSpec.Pow!exponent U; return Quantity!(U, Q._Value)(quantity.value ^^ exponent); } /// Examples: unittest { mixin BasicUnit!"meter"; assert(pow!3(meter) == pow!2(meter) * meter); } /// the same as pow!2(quantity) auto square(Q)(Q quantity) if(isQuantity!Q) { return pow!2(quantity); } /// the same as pow!3(quantity) auto cubic(Q)(Q quantity) if(isQuantity!Q) { return pow!3(quantity); } unittest { mixin BasicUnit!"meter"; assert(cubic(meter) == square(meter) * meter); } // below: various assorted helper functions // initialize an assignable type to x T make(T)(int x) { T result; result = x; return result; } /** Doesn't work on function templates. */ template Curry(alias Template, Args ...) { template Curry(MoreArgs ...) { alias Template!(Args, MoreArgs) Curry; } } /// Examples: unittest { static int foo(int a, int b)() { return a + b; } alias Curry!(foo, 42) bar; assert(bar!1() == 43); } /** If Type is an instance of Template, get the template parameters. */ template TemplateParameters(alias Template, Type) { static if(is(Type _ == Template!Args, Args ...)) { alias Args TemplateParameters; } else { alias void TemplateParameters; } } /// Examples: unittest { struct S(P ...) {} static assert(is(TemplateParameters!(S, S!(int, float)) == TypeTuple!(int, float))); struct T(P ...) {} static assert(is(TemplateParameters!(T, S!(int, float)) == void)); } /** true if Type is an instance of Template */ template isTemplatedType(alias Template, Type) { enum isTemplatedType = !is(TemplateParameters!(Template, Type) == void); } /// Examples: unittest { struct A() {} struct B() {} static assert(isTemplatedType!(A, A!())); static assert(!isTemplatedType!(A, B!())); alias Curry!(isTemplatedType, A) isA; static assert(isA!(A!())); static assert(!isA!(B!())); }
Sep 04 2012
On 09/04/2012 05:55 PM, Nicholas Londey wrote:I could easily implement my own as I have done in C++ in the past but assume there is a standard implementation which I would prefer. Any help or links to examples much appreciated.UFCS enables some interesting syntax: struct Grams { size_t amount; } property Grams grams(size_t amount) { return Grams(amount); } void main() { auto weight = 5.grams; } C++11 brings similar conveniences as well. Ali
Sep 04 2012
Using this: struct Grams { size_t amount; } property Grams grams(size_t amount) { return Grams(amount); } void main() { auto weight = 5.grams; weight = weight + 10.grams; } How would you use it? I thought the point of this sort of strong typing was to be able to carry out arithmetic using your type that other units cannot accidentally be mixed with.
Sep 04 2012
On 09/04/2012 08:11 PM, ixid wrote:Using this: struct Grams { size_t amount; } property Grams grams(size_t amount) { return Grams(amount); } void main() { auto weight = 5.grams; weight = weight + 10.grams; } How would you use it? I thought the point of this sort of strong typing was to be able to carry out arithmetic using your type that other units cannot accidentally be mixed with.I have not used such frameworks myself but have seen them mentioned a number of times. I think the type above should have been Weight, not Grams. The idea is to build the relationships between types and allow only those operations. For example, the following program defines dividing Distance by Time to produce Speed: import std.stdio; import std.string; struct Distance { double amount; Speed opBinary(string op)(in Time time) const if (op == "/") { return Speed(this.amount / time.amount); } } property Distance meters(double amount) { return Distance(amount); } struct Time { double amount; } property Time seconds(double amount) { return Time(amount); } struct Speed { double amount; string toString() const { return format("%s m/s", amount); } } void main() { auto distance = (10.5).meters; auto time = (2.5).seconds; auto speed = distance / time; writeln(speed); } The parentheses around the floating point literals are not necessary. I thought this reads better. Ali
Sep 04 2012
On Wed, 05 Sep 2012 02:55:45 +0200, Nicholas Londey <londey gmail.com> wrote:Hello. I am trying to work out if there is existing support for strongly typed numerical values for example degrees west and kilograms such that they cannot be accidentally mixed in an expression. I have vague recollection of seeing a presentation by Walter talking about this but I cannot seem to find it. I have looked at std.typecons.Typedef and Proxy but neither seem to do what I want or at least fail to compile for the expression n = n + n;. I could easily implement my own as I have done in C++ in the past but assume there is a standard implementation which I would prefer. Any help or links to examples much appreciated. Regards, NicholasNot mine, but this is the implementation I use: https://github.com/klickverbot/phobos/tree/units/std Files are units.d and si.d. Documentation: http://klickverbot.at/code/units/std_units.html http://klickverbot.at/code/units/std_si.html -- Simen
Sep 04 2012
On Wednesday, 5 September 2012 at 05:03:38 UTC, Simen Kjaeraas wrote:Not mine, but this is the implementation I use: https://github.com/klickverbot/phobos/tree/units/std Files are units.d and si.d. Documentation: http://klickverbot.at/code/units/std_units.html http://klickverbot.at/code/units/std_si.htmlFascinating how similar this looks to my (completely independent) implementation. I noticed two flaws in std.units: 1) Can't add quantities of the same type (this is probably trivial to fix). 2) Different scopes don't make different quantity types.
Sep 04 2012
On Wednesday, 5 September 2012 at 05:53:49 UTC, anonymous wrote:I noticed two flaws in std.units: 1) Can't add quantities of the same type (this is probably trivial to fix). 2) Different scopes don't make different quantity types.Can you elaborate on that? I must admit that I didn't actively work on std.units for quite some while now, as general interest in it seemed to have faded (I'm glad to be proven wrong, though), but adding quantities of the same type should definitely work. And what do you mean by "different scopes"? If the unit types are different, the quantity types should be different as well. David
Sep 06 2012
On Thursday, 6 September 2012 at 12:22:08 UTC, David Nadlinger wrote:Can you elaborate on that? I must admit that I didn't actively work on std.units for quite some while now, as general interest in it seemed to have faded (I'm glad to be proven wrong, though), but adding quantities of the same type should definitely work.Maybe I'm missing something fundamental, but this little test fails: --- auto foo = baseUnit!"foo"; auto foo2 = foo + foo; --- Error: incompatible types for ((foo) + (foo)): 'BaseUnit!("foo",null)' and 'BaseUnit!("foo",null)'And what do you mean by "different scopes"? If the unit types are different, the quantity types should be different as well.In the following, S1.foo and S2.foo are of the same type. I think they shouldn't be; just like S1.Bar and S2.Bar are different types. --- struct S1 {enum foo = baseUnit!"foo"; struct Bar {}} struct S2 {enum foo = baseUnit!"foo"; struct Bar {}} ---
Sep 06 2012
Thank you everyone for your replies. Initially I think I am going to role my own as my requirements are fairly simple compared to a full blown system with units of measure. My goal is to have large number of application specific types which may be implemented as the same but are semantically different. A real world case in which I have used this approach was when cleaning up a large body of code using a mixture of time units (sec,msec,µsec) stored as 32 and 64bit signed and unsigned integers. We needed to standardized on µsec and explicit typing greatly simplified the migration process. I have also worked on protects in the past where we speculated on the benefits of using strong typing to avoid incorrect mixing of matrices (world, model, bone etc.). I think there is allot of value to be had in simply requiring explicit casting however I am less convinced on systems that allow implicit combining of units of the same quantity type. I feel part of the type is its range and precision and so there is no valid way to implicitly add kilometers to millimeters for example. I hope that at some point support for the semantically different versions of the same type will be added to the standard library but for now I am happy to write my own. D is still a new language to me and it has some vary nice features to help accomplish this.
Sep 06 2012
Nicholas Londey:however I am less convinced on systems that allow implicit combining of units of the same quantity type. I feel part of the type is its range and precision and so there is no valid way to implicitly add kilometers to millimeters for example.I see. If the range and precision are statically know then it's possible to design types that contain such values too. If they are known at run-time they need some run-time tests. Bye, bearophile
Sep 06 2012