www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - How to have strongly typed numerical values?

reply "Nicholas Londey" <londey gmail.com> writes:
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
next sibling parent reply "bearophile" <bearophileHUGS lycos.com> writes:
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 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;.

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
parent Don Clugston <dac nospam.com> writes:
On 05/09/12 03:42, bearophile wrote:
 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'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.
Sep 05 2012
prev sibling next sibling parent "anonymous" <anonymous example.com> writes:
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,

 Nicholas

I 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
prev sibling next sibling parent reply =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
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
parent =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
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
prev sibling next sibling parent "ixid" <nuaccount gmail.com> writes:
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
prev sibling next sibling parent "Simen Kjaeraas" <simen.kjaras gmail.com> writes:
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,

 Nicholas

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.html -- Simen
Sep 04 2012
prev sibling next sibling parent "anonymous" <anonymous example.com> writes:
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.html

Fascinating 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
prev sibling next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
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. I think in F# people are using units often enough. 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
prev sibling next sibling parent "Paul D. Anderson" <paul.d.removethis.anderson comcast.andthis.net> writes:
On Wednesday, 5 September 2012 at 11:50:12 UTC, bearophile wrote:
 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. I think in F# people are using units often enough. 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

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. Paul
Sep 05 2012
prev sibling next sibling parent "Nicholas Londey" <londey gmail.com> writes:
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
prev sibling next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
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
prev sibling next sibling parent "David Nadlinger" <see klickverbot.at> writes:
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
prev sibling parent "anonymous" <anonymous example.com> writes:
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