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!()));
}

Reply via email to