How to have strongly typed numerical values?

anonymous anonymous at example.com
Tue Sep 4 18:49:36 PDT 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,
>
> 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!()));
}



More information about the Digitalmars-d-learn mailing list