www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Units of Measure in F#

reply bearophile <bearophileHUGS lycos.com> writes:
I have found this interesting old thread, I don't know how much those things
are true today too:
http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=36939

Beside allowing algebraic data types, that are quite useful, a functional-like
type system allows to implement Units of Measure in a nice way:
http://blogs.msdn.com/andrewkennedy/archive/2008/08/20/units-of-measure-in-f-part-one-introducing-units.aspx

For people that don't remember what algebraic data types are:
http://en.wikipedia.org/wiki/Algebraic_data_type

This power also allows to use pattern matching, absent in Python, present in
Haskell, Ocaml, Scala, etc.

Bye,
bearophile
Oct 06 2008
next sibling parent "Nick Sabalausky" <a a.a> writes:
"bearophile" <bearophileHUGS lycos.com> wrote in message 
news:gcdt5p$10t0$1 digitalmars.com...
I have found this interesting old thread, I don't know how much those 
things are true today too:
 http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=36939

 Beside allowing algebraic data types, that are quite useful, a 
 functional-like type system allows to implement Units of Measure in a nice 
 way:
 http://blogs.msdn.com/andrewkennedy/archive/2008/08/20/units-of-measure-in-f-part-one-introducing-units.aspx

 For people that don't remember what algebraic data types are:
 http://en.wikipedia.org/wiki/Algebraic_data_type

 This power also allows to use pattern matching, absent in Python, present 
 in Haskell, Ocaml, Scala, etc.

 Bye,
 bearophile

That's awesome. I want it!
Oct 06 2008
prev sibling next sibling parent BCS <ao pathlink.com> writes:
Reply to bearophile,

 I have found this interesting old thread, I don't know how much those
 things are true today too:
 http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmar
 s.D&article_id=36939
 
 Beside allowing algebraic data types, that are quite useful, a
 functional-like type system allows to implement Units of Measure in a
 nice way:
 
 http://blogs.msdn.com/andrewkennedy/archive/2008/08/20/units-of-measur
 e-in-f-part-one-introducing-units.aspx
 
 For people that don't remember what algebraic data types are:
 http://en.wikipedia.org/wiki/Algebraic_data_type
 
 This power also allows to use pattern matching, absent in Python,
 present in Haskell, Ocaml, Scala, etc.
 
 Bye,
 bearophile

I think it is fully doable right now. I've considered doing it off and on for some time. I even figured out how to make it do rational powers for the dimensions. The only thing I would want is to be able to avoid needing to wrap everything by way of operator overloads on typedefs typedef real Unit(..stuff..) { Unit!(stuff) opAdd(T)(T t) {...} } and some way to re-type functions alias sqrt Unit!(stuff/2) sqrt(Unit!(stuff)); // if sqrt called with Unit!(stuff), return type is Unit!(stuff/2)
Oct 06 2008
prev sibling next sibling parent reply "Denis Koroskin" <2korden gmail.com> writes:
On Tue, 07 Oct 2008 00:41:29 +0400, bearophile <bearophileHUGS lycos.com>  
wrote:

 I have found this interesting old thread, I don't know how much those  
 things are true today too:
 http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=36939

 Beside allowing algebraic data types, that are quite useful, a  
 functional-like type system allows to implement Units of Measure in a  
 nice way:
 http://blogs.msdn.com/andrewkennedy/archive/2008/08/20/units-of-measure-in-f-part-one-introducing-units.aspx

 For people that don't remember what algebraic data types are:
 http://en.wikipedia.org/wiki/Algebraic_data_type

 This power also allows to use pattern matching, absent in Python,  
 present in Haskell, Ocaml, Scala, etc.

 Bye,
 bearophile

Arghh! I've almost done the trick with D templates and then I got this: Assertion failure: 'i < parameters->dim' on line 784 in file 'template.c' DMD is not ready for my funky templates yet! :) I'll post my results (and a bug report) soon.
Oct 06 2008
parent reply BCS <ao pathlink.com> writes:
Reply to Denis,

 On Tue, 07 Oct 2008 00:41:29 +0400, bearophile
 <bearophileHUGS lycos.com>  wrote:
 
 I have found this interesting old thread, I don't know how much those
 things are true today too:
 http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalma
 rs.D&article_id=36939
 
 Beside allowing algebraic data types, that are quite useful, a
 
 functional-like type system allows to implement Units of Measure in a
 
 nice way:
 
 http://blogs.msdn.com/andrewkennedy/archive/2008/08/20/units-of-measu
 re-in-f-part-one-introducing-units.aspx
 
 For people that don't remember what algebraic data types are:
 http://en.wikipedia.org/wiki/Algebraic_data_type
 
 This power also allows to use pattern matching, absent in Python,
 present in Haskell, Ocaml, Scala, etc.
 
 Bye,
 bearophile

this: Assertion failure: 'i < parameters->dim' on line 784 in file 'template.c' DMD is not ready for my funky templates yet! :) I'll post my results (and a bug report) soon.

svn.dsource.org seems to be having problmes or I'd post a system I just put together. total code, WS comments: ~ 400 LOC supports 44 different units, support +,-,* and / as well as pow and root
Oct 06 2008
parent reply bearophile <bearophileHUGS lycos.com> writes:
BCS:
 svn.dsource.org  seems to be having problmes or I'd post a system I just 
 put together.
 total code, WS comments: ~ 400 LOC
 supports 44 different units,
 support +,-,* and / as well as pow and root

A lot of work. Does is use a (syntax) strategy similar to this? http://www.boost.org/doc/libs/1_36_0/doc/html/boost_units.html Now D just needs a pow infix operator (** ?) and that's done :-) Bye, bearophile
Oct 07 2008
next sibling parent reply BCS <ao pathlink.com> writes:
Reply to bearophile,

 BCS:
 
 svn.dsource.org  seems to be having problmes or I'd post a system I
 just
 put together.
 total code, WS comments: ~ 400 LOC
 supports 44 different units,
 support +,-,* and / as well as pow and root

http://www.boost.org/doc/libs/1_36_0/doc/html/boost_units.html Now D just needs a pow infix operator (** ?) and that's done :-) Bye, bearophile

SVN is working again (or I'm somewhere it works from) http://www.dsource.org/projects/scrapple/browser/trunk/units/ take a look at si.d first as it's the most useful intro (look way down at the bottom)
Oct 07 2008
parent reply bearophile <bearophileHUGS lycos.com> writes:
BCS:
 take a look at si.d first as it's the most useful intro (look way down at 
 the bottom)

I think there's a need of some syntactic sugar :-) Bye, bearophile
Oct 07 2008
parent BCS <ao pathlink.com> writes:
Reply to bearophile,

 BCS:
 
 take a look at si.d first as it's the most useful intro (look way
 down at the bottom)
 

Bye, bearophile

I put up some better examples. http://www.dsource.org/projects/scrapple/browser/trunk/units/si.d
Oct 07 2008
prev sibling parent "Denis Koroskin" <2korden gmail.com> writes:
On Tue, 07 Oct 2008 22:39:10 +0400, BCS <ao pathlink.com> wrote:

 Reply to bearophile,

 BCS:

 svn.dsource.org  seems to be having problmes or I'd post a system I
 just
 put together.
 total code, WS comments: ~ 400 LOC
 supports 44 different units,
 support +,-,* and / as well as pow and root

http://www.boost.org/doc/libs/1_36_0/doc/html/boost_units.html Now D just needs a pow infix operator (** ?) and that's done :-) Bye, bearophile

SVN is working again (or I'm somewhere it works from) http://www.dsource.org/projects/scrapple/browser/trunk/units/ take a look at si.d first as it's the most useful intro (look way down at the bottom)

Well, I have done it completely different. Here is my (simplified) class hierarchy from memory: // Unit is 's', 'kg', 'n' etc, i.e. they are basic orthogonal units class Unit(string name) { enum AsString = name; } // A Powered Unit :) PUnit is s^2, kg^(-3.14) etc. // It is class PUnit(Unit, float power) { alias UnitType Unit; enum Power = power; } // Quantity consists of a unique set of PUnits and a value. It also // defines a set of operations like opMul, opDiv, opAdd and opSub // Example: 5 m/s^2 class Quantity(U...) { alias Units U; private float value; // here is how my opMul looks like: Multiply!(Units, OtherUnits) opMul(OtherUnits)(OtherUnits other) { Multiply!(Units, OtherUnits) result = void; result.value = value * other.value; return result; } } here is how I merge Units for multiplication: template GetUnitPower(Unit, Units...) { static if (Units.length == 0) { enum GetUnitPower = 0; } else static if (is (Units[0].UnitType == Unit)) { enum GetUnitPower = Units[0].Power; } else { enum GetUnitPower = GetUnitPower!(Unit, Units[1..$]); } } template AddPowers(Unit, Units...) { // GetUnitPower returns 0 if there is no such Unit in Units // put '-' for Divide! here enum Power = Unit.Power + GetUnitPower!(Unit.UnitType, Units); // get all the Units without Unit alias Without!(Unit.UnitType, Units) Rest; // Add the Unit with a new Power to the list of rest units alias Tuple!(PUnit!(Unit.UnitType, Power), Rest) Result; } As a result you can have any arbitrary amount of orthogonal Units. Add them with a single line: mixin(defineUnit("Time", "s")); mixin(defineUnit("Mass", "kg")); mixin(defineUnit("Distance", "m")); mixin(defineUnit("Speed", "m/s")); Distance d = 6 * m; Time t = 3 * s; Speed s = d / t;
Oct 07 2008
prev sibling next sibling parent ore-sama <spam here.lot> writes:
Hmm... do we want to add pressure to energy density?
Oct 07 2008
prev sibling parent reply Walter Bright <newshound1 digitalmars.com> writes:
This has been done in D already:
----------------------------------
// by Oskar Linde Aug 2006
// This is just a quick hack to test
// IFTI operators opMul and opDel

import std.stdio;
import std.math;
import std.string;

version = unicode;

struct SiQuantity(T,int e1, int e2, int e3, int e4, int e5, int e6, int 
e7) {
	T value = 0;
	alias T ValueType;
	const exp1 = e1;
	const exp2 = e2;
	const exp3 = e3;
	const exp4 = e4;
	const exp5 = e5;
	const exp6 = e6;
	const exp7 = e7;

	static assert(SiQuantity.sizeof == ValueType.sizeof);

	template AddDimensions(int mul, U) {
		static assert(is(U.ValueType == ValueType) ||
			      is(U == ValueType),
			      "incompatible value types");
		static if (is(U == ValueType))
			alias SiQuantity AddDimensions;
		else
			alias SiQuantity!(T,exp1+mul*U.exp1,exp2+mul*U.exp2,
					  exp3+mul*U.exp3,exp4+mul*U.exp4,
					  exp5+mul*U.exp5,exp6+mul*U.exp6,
					  exp7+U.exp7) AddDimensions;
	}

	SiQuantity opAddAssign(SiQuantity rhs) {
		value += rhs.value;
		return *this;
	}

	SiQuantity opSubAssign(SiQuantity rhs) {
		value -= rhs.value;
		return *this;
	}

     const
     {

	SiQuantity opAdd(SiQuantity rhs) {
		SiQuantity ret;
		ret.value = value + rhs.value;
		return ret;
	}

	SiQuantity opSub(SiQuantity rhs) {
		SiQuantity ret;
		ret.value = value - rhs.value;
		return ret;
	}

	SiQuantity opNeg() {
		SiQuantity ret;
		ret.value = -value;
		return ret;
	}

	SiQuantity opPos() {
		typeof(return) ret;
		ret.value = value;
		return ret;
	}

	int opCmp(SiQuantity rhs) {
		if (value > rhs.value)
			return 1;
		if (value < rhs.value)
			return -1;
		return 0; // BUG: NaN
	}

	AddDimensions!(+1,Rhs) opMul(Rhs)(Rhs rhs) {
		AddDimensions!(+1,Rhs) ret;
		static if (is(Rhs : T))
			ret.value = value * rhs;
		else
			ret.value = value * rhs.value;
		return ret;
	}

	AddDimensions!(-1,Rhs) opDiv(Rhs)(Rhs rhs) {
		AddDimensions!(-1,Rhs) ret;
		static if (is(Rhs : T))
			ret.value = value / rhs;
		else
			ret.value = value / rhs.value;
		return ret;
	}

	SiQuantity opMul_r(T lhs) {
		SiQuantity ret;
		ret.value = lhs * value;
		return ret;
	}

	SiQuantity!(T,-e1,-e2,-e3,-e4,-e5,-e6,-e7) opDiv_r(T lhs) {
		SiQuantity!(T,-e1,-e2,-e3,-e4,-e5,-e6,-e7) ret;
		ret.value = lhs / value;
		return ret;
	}

	string toString() {
		string prefix = "";
		T multiplier = 1;
		T value = this.value;
		string unit;
		
		static if (is(typeof(UnitName!(SiQuantity))))
			unit = UnitName!(SiQuantity);
		else {
			value *= pow(cast(real)1e3,cast(uint)e2); // convert kg -> g
			// Take mass (e2) first to handle kg->g prefix issue	
			if (e2 != 0) unit ~= format("·g^%s",e2);
			if (e1 != 0) unit ~= format("·m^%s",e1);
			if (e3 != 0) unit ~= format("·s^%s",e3);
			if (e4 != 0) unit ~= format("·A^%s",e4);
			if (e5 != 0) unit ~= format("·K^%s",e5);
			if (e6 != 0) unit ~= format("·mol^%s",e6);
			if (e7 != 0) unit ~= format("·cd^%s",e7);
			if (unit)
				unit = unit[2..$].split("^1").join("");
		}

		if (value >= 1e24) { prefix = "Y"; multiplier = 1e24; }
		else if (value >= 1e21) { prefix = "Z"; multiplier = 1e21; }
		else if (value >= 1e18) { prefix = "E"; multiplier = 1e18; }
		else if (value >= 1e15) { prefix = "P"; multiplier = 1e15; }
		else if (value >= 1e12) { prefix = "T"; multiplier = 1e12; }
		else if (value >= 1e9) { prefix = "G"; multiplier = 1e9; }
		else if (value >= 1e6) { prefix = "M"; multiplier = 1e6; }
		else if (value >= 1e3) { prefix = "k"; multiplier = 1e3; }
		else if (value >= 1) { }
		else if (value >= 1e-3) { prefix = "m"; multiplier = 1e-3; }
		else if (value >= 1e-6) {
			version(unicode)
				prefix = "µ";
			else
				prefix = "u";
			multiplier = 1e-6; }
		else if (value >= 1e-9) { prefix = "n"; multiplier = 1e-9; }
		else if (value >= 1e-12) { prefix = "p"; multiplier = 1e-12; }
		else if (value >= 1e-15) { prefix = "f"; multiplier = 1e-15; }
		else if (value >= 1e-18) { prefix = "a"; multiplier = 1e-18; }
		else if (value >= 1e-21) { prefix = "z"; multiplier = 1e-21; }
		else if (value >= 1e-24) { prefix = "y"; multiplier = 1e-24; }

		return format("%.3s %s%s",value/multiplier, prefix, unit);
	}	
     }
}

//length  			meter  		m
//mass 				kilogram       	kg
//time 				second 		s
//electric current 		ampere 		A
//thermodynamic temperature     kelvin 		K
//amount of substance 		mole 		mol
//luminous intensity 		candela 	cd

// Si base quantities
alias SiQuantity!(real,1,0,0,0,0,0,0) Length;
alias SiQuantity!(real,0,1,0,0,0,0,0) Mass;
alias SiQuantity!(real,0,0,1,0,0,0,0) Time;
alias SiQuantity!(real,0,0,0,1,0,0,0) Current;
alias SiQuantity!(real,0,0,0,0,1,0,0) Temperature;
alias SiQuantity!(real,0,0,0,0,0,1,0) AmountOfSubstance;
alias SiQuantity!(real,0,0,0,0,0,0,1) Intensity;

alias SiQuantity!(real,0,0,0,0,0,0,0) UnitLess;

// Derived quantities
alias typeof(Length*Length) 		Area;
alias typeof(Length*Area) 		Volume;
alias typeof(Mass/Volume) 		Density;
alias typeof(Length*Mass/Time/Time) 	Force;
alias typeof(1/Time) 			Frequency;
alias typeof(Force/Area) 		Pressure;
alias typeof(Force*Length) 		Energy;
alias typeof(Energy/Time) 		Power;
alias typeof(Time*Current) 		Charge;
alias typeof(Power/Current) 		Voltage;
alias typeof(Charge/Voltage) 		Capacitance;
alias typeof(Voltage/Current) 		Resistance;
alias typeof(1/Resistance) 		Conductance;
alias typeof(Voltage*Time) 		MagneticFlux;
alias typeof(MagneticFlux/Area) 	MagneticFluxDensity;
alias typeof(MagneticFlux/Current) 	Inductance;
alias typeof(Intensity*UnitLess) 	LuminousFlux;
alias typeof(LuminousFlux/Area) 	Illuminance;

// SI fundamental units
const Length 		meter 		= {1};
const Mass 		kilogram 	= {1};
const Time 		second 		= {1};
const Current 		ampere 		= {1};
const Temperature 	kelvin 		= {1};
const AmountOfSubstance mole 		= {1};
const Intensity 	candela 	= {1};

// Derived units
const Frequency		hertz		= {1};
const Force		newton		= {1};
const Pressure		pascal		= {1};
const Energy		joule		= {1};
const Power		watt		= {1};
const Charge		coulomb		= {1};
const Voltage		volt		= {1};
const Capacitance	farad		= {1};
const Resistance	ohm		= {1};
const Conductance	siemens		= {1};
const MagneticFlux	weber		= {1};
const MagneticFluxDensity tesla 	= {1};
const Inductance	henry		= {1};
const LuminousFlux	lumen		= {1};
const Illuminance	lux		= {1};

template UnitName(U:Frequency) 	{ const UnitName = "Hz"; }
template UnitName(U:Force) 	{ const UnitName = "N"; }
template UnitName(U:Pressure)	{ const UnitName = "Pa"; }
template UnitName(U:Energy) 	{ const UnitName = "J"; }
template UnitName(U:Power)	{ const UnitName = "W"; }
template UnitName(U:Charge)	{ const UnitName = "C"; }
template UnitName(U:Voltage) 	{ const UnitName = "V"; }
template UnitName(U:Capacitance){ const UnitName = "F"; }
version(unicode) {
	template UnitName(U:Resistance) { const UnitName = "Ω"; }
} else {
	template UnitName(U:Resistance) { const UnitNAme = "ohm"; }
}	
template UnitName(U:Conductance){ const UnitName = "S"; }
template UnitName(U:MagneticFlux){ const UnitName = "Wb"; }
template UnitName(U:MagneticFluxDensity) { const UnitName = "T"; }
template UnitName(U:Inductance) { const UnitName = "H"; }

void main() {
	Area a = 25 * meter * meter;
	Length l = 10 * 1e3 * meter;
	Volume vol = a * l;
	Mass m = 100 * kilogram;
	assert(!is(typeof(vol / m) == Density));
	//Density density = vol / m; // dimension error -> syntax error
	Density density = m / vol;
	writefln("The volume is %s",vol.toString);
	writefln("The mass is %s",m.toString);
	writefln("The density is %s",density.toString);

	writef("\nElectrical example:\n\n");
	Voltage v = 5 * volt;
	Resistance r = 	1 * 1e3 * ohm;
	Current	i = v/r;
	Time ti = 1 * second;
	Power w = v*v/r;
	Energy e = w * ti;

	// One wishes the .toString was unnecessary...
	writefln("A current of ",i.toString);
	writefln("through a voltage of ",v.toString);
	writefln("requires a resistance of ",r.toString);
	writefln("and produces ",w.toString," of heat.");
	writefln("Total energy used in ",ti.toString," is ",e.toString);

	writef("\nCapacitor time curve:\n\n");
	
	Capacitance C = 0.47 * 1e-6 * farad; 	// Capacitance
	Voltage V0 = 5 * volt;			// Starting voltage
	Resistance R = 4.7 * 1e3 * ohm;		// Resistance

	for (Time t; t < 51 * 1e-3 * second; t += 1e-3 * second) {
		Voltage Vt = V0 * exp((-t / (R*C)).value);

		writefln("at %5s the voltage is %s",t.toString,Vt.toString);
	}
}
Oct 08 2008
next sibling parent BCS <ao pathlink.com> writes:
Reply to Walter,

 This has been done in D already:

It has some interesting features :) (I might have to steal a few for my version)
Oct 09 2008
prev sibling parent reply "Denis Koroskin" <2korden gmail.com> writes:
On Thu, 09 Oct 2008 23:35:58 +0400, BCS <ao pathlink.com> wrote:

 Reply to Walter,

 This has been done in D already:

It has some interesting features :) (I might have to steal a few for my version)

I still think my version is superior (it handled floating point powers, supports an arbitrary number of basic units (given that they are orthogonal) and allows adding them with no original code modification) :p
Oct 09 2008
parent BCS <ao pathlink.com> writes:
Reply to Denis,

 On Thu, 09 Oct 2008 23:35:58 +0400, BCS <ao pathlink.com> wrote:
 
 Reply to Walter,
 
 This has been done in D already:
 

my version)

powers,

Re: FP, I would count that as worse than basic integers because it runs the risk of FP rounding errors. Mine will handle rational powers (1/2, 23/43, etc) and won't suffer from the loss of precision. The only places I have ever seen non rational exponents in use are in data fitting applications and just switching to a close enough rational is as good as anything there. Also, the non rational cases where mine might suffer little are the cases where FP problem are /most/ likely to crop up.
  supports an arbitrary number of basic units (given that they
 are  orthogonal) and allows adding them with no original code
 modification) :p
 

You may have me on for that point, but extensibility also has it's down side; different people add the same dimension independently and then someone wants to mix them. The 5 dimensions I picked will cover almost all that cases for just about anyone. I think we each picked a different set of design choices and created a solution for them. I do think that mine is better than yours by the criteria I'm using. I can see legitimate criteria where yours is better.
Oct 09 2008