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