digitalmars.D - physical units checked at compile time (yet lacking IFTI capabilities)
- Norbert Nemec (133/133) Apr 23 2006 I just wanted to try some ideas based on templates. After a long odyssee
- BCS (24/157) Apr 23 2006 I feel your pain. A few post back I include a link to a unit.d program t...
- Norbert Nemec (12/39) Apr 23 2006 That's one of the main intentions of my detailed post. Walter clearly
- Dan (14/14) Apr 24 2006 I don't mean to rain on your parade, but I tend to think the units shoul...
- Norbert Nemec (22/32) Apr 24 2006 Maybe. I did not even think about that, yet.
I just wanted to try some ideas based on templates. After a long odyssee through the details of D template programming, I finally succeeded to arrive at a working piece of code, even though it is still a long way from where I hoped to arrive. I think it is interesting to share this experience for several reasons: * It shows a promising idea how to handle and check physical dimensions along with unit conversions at compile time. * It gives a good example where IFTI would be crucial. * It illustrates a point where IFTI does not work yet. * Somebody may be able to give me further ideas about how to go on. *********************************************** Let me explain: The ultimate goal was to handle physical dimensions as compile time. Basically, I would then be able to write code like: ----- | mass m = 0.5 * ounce; | velocity v = 1.0 * km / hour; | energy E_kin = 0.5 * m * v*v; | printf("E_kin = %g kcal\n",E_kin/kcal); ----- and so on, where all the dimensions are checked at compile time and all necessary conversions are done automatically. I started out with the basic idea: ------------------- | struct physical_quantity( | int m, // length | int s, // time | int kg // mass | // ignore A, K, mol and cd for the moment | ) { | double value; | } | | alias physical_quantity!(1,0,0) length; | alias physical_quantity!(0,1,0) time; | alias physical_quantity!(0,0,1) mass; | | const length meter = { value: 1.0 }; | const time second = { value: 1.0 }; | const mass kilogram = { value: 1.0 }; ------------------- which could be continued like: ------------------- | alias physical_quantity!(1,-1,0) velocity; | alias physical_quantity!(2,-2,1) energy; | | const time minute = { value: 60.0 }; | const time hour = { value: 3600.0 }; | const velocity speedoflight = { value: 299792458 }; | const energy Joule = { value: 1.0 }; | const energy kcal = { value: 4185.0 }; ------------------- So far, everything worked fine. Now, I wanted to continue defining the multiplication such that I could do, for example: ------------------- | physical_quantity!(1,4,-2) v1 = { value: 1.0 }; | physical_quantity!(2,-4,1) v2 = { value: 1.0 }; | physical_quantity!(3,0,-1) prod = v1 * v2; | // internally, equivalent to: prod.value = v1.value * v2.value; ------------------- but I failed horribly trying to define this multiplication. The most promising try so far was: ------------------------------- | struct physical_quantity( | int m, | int s, | int kg | ) { | double value; | | const int _m = m; | const int _s = s; | const int _kg = kg; | | template opMul(T) { // line 12 | physical_quantity!(m+T._m,s+T._s,kg+T._kg) opMul(T other) { | physical_quantity!(m+T._m,s+T._s,kg+T._kg) res; | res.value = value*other.value; | return res; | } | } | }; | | int main(char[][] args) | { | physical_quantity!(1,0,0) v1; v1.value = 1.0; | physical_quantity!(0,1,0) v2; v2.value = 1.0; | physical_quantity!(1,1,0) prod = v1*v2; // line 25 | printf("%f\n",prod.value); | return 0; | } -------------------------------- which fails with the compiler error: ------------ | units_minimal.d(25): incompatible types for ((v1) * (v2)): 'physical_quantity' and 'physical_quantity' | units_minimal.d(25): 'v1' is not an arithmetic type | units_minimal.d(25): 'v2' is not an arithmetic type | units_minimal.d(25): cannot implicitly convert expression ((v1) * (v2)) of type physical_quantity to physical_quantity ------------ My guess was that the problem is with the operator overloading, and indeed, replacing line 25 by ------------- | physical_quantity!(1,1,0) prod = v1.opMul(v2); // line 25 ------------- changes the compiler error to: ------------ | units_minimal.d(12): template units_minimal.physical_quantity!(1,0,0).physical_quantity.opMul(T) templates don't have properties | units_minimal.d(25): function expected before (), not v1 of type physical_quantity | units_minimal.d(25): cannot implicitly convert expression ((v1)((v2))) of type int to physical_quantity ------------ My next guess was, that the IFTI causes the problem, so I changed lines 12 and 25 to ------------ | template T_opMul(T) { // line 12 ------------ | physical_quantity!(1,1,0) prod = v1.T_opMul!(typeof(v2)).opMul(v2); // line 25 ------------ and now the program worked correctly. Of course, in this state, the whole code is far from useful. Without IFTM and operator overloading, the whole point of the library is moot: allowing readable code with compile-time error checking. Even if IFTI and operators would work, there is, of course, some way to go for a working library, but I am confident, that this it will be possible to go that way. Greetings, Norbert
Apr 23 2006
I feel your pain. A few post back I include a link to a unit.d program that does just what you are trying to do, but a run time (with all of the costs :-p that entails, OTOH it lets you do units dynamically) I have bean waiting for better template support to do exactly the same project you are looking at. Until that happens I think we're sunk. I was looking at something like this: struct Unit(T, int L, int M, int T) { T val; Unit(T, L, M, T) opAdd(Unit(T, L, M, T) op) { Unit(T, L, M, T) ret = { val : val+op.val }; return ret; } // this will need some improvements to templates Unit(T, L+opL, M+opM, T+opT) opMul(Unit(T,opL, opM, opT) op) { Unit(T, L+opL, M+opM, T+opT) ret = { val : val*op.val }; return ret; } .. } Lets hope walter makes some of these possible in the near future. In article <e2gc64$bqv$1 digitaldaemon.com>, Norbert Nemec says...I just wanted to try some ideas based on templates. After a long odyssee through the details of D template programming, I finally succeeded to arrive at a working piece of code, even though it is still a long way from where I hoped to arrive. I think it is interesting to share this experience for several reasons: * It shows a promising idea how to handle and check physical dimensions along with unit conversions at compile time. * It gives a good example where IFTI would be crucial. * It illustrates a point where IFTI does not work yet. * Somebody may be able to give me further ideas about how to go on. *********************************************** Let me explain: The ultimate goal was to handle physical dimensions as compile time. Basically, I would then be able to write code like: ----- | mass m = 0.5 * ounce; | velocity v = 1.0 * km / hour; | energy E_kin = 0.5 * m * v*v; | printf("E_kin = %g kcal\n",E_kin/kcal); ----- and so on, where all the dimensions are checked at compile time and all necessary conversions are done automatically. I started out with the basic idea: ------------------- | struct physical_quantity( | int m, // length | int s, // time | int kg // mass | // ignore A, K, mol and cd for the moment | ) { | double value; | } | | alias physical_quantity!(1,0,0) length; | alias physical_quantity!(0,1,0) time; | alias physical_quantity!(0,0,1) mass; | | const length meter = { value: 1.0 }; | const time second = { value: 1.0 }; | const mass kilogram = { value: 1.0 }; ------------------- which could be continued like: ------------------- | alias physical_quantity!(1,-1,0) velocity; | alias physical_quantity!(2,-2,1) energy; | | const time minute = { value: 60.0 }; | const time hour = { value: 3600.0 }; | const velocity speedoflight = { value: 299792458 }; | const energy Joule = { value: 1.0 }; | const energy kcal = { value: 4185.0 }; ------------------- So far, everything worked fine. Now, I wanted to continue defining the multiplication such that I could do, for example: ------------------- | physical_quantity!(1,4,-2) v1 = { value: 1.0 }; | physical_quantity!(2,-4,1) v2 = { value: 1.0 }; | physical_quantity!(3,0,-1) prod = v1 * v2; | // internally, equivalent to: prod.value = v1.value * v2.value; ------------------- but I failed horribly trying to define this multiplication. The most promising try so far was: ------------------------------- | struct physical_quantity( | int m, | int s, | int kg | ) { | double value; | | const int _m = m; | const int _s = s; | const int _kg = kg; | | template opMul(T) { // line 12 | physical_quantity!(m+T._m,s+T._s,kg+T._kg) opMul(T other) { | physical_quantity!(m+T._m,s+T._s,kg+T._kg) res; | res.value = value*other.value; | return res; | } | } | }; | | int main(char[][] args) | { | physical_quantity!(1,0,0) v1; v1.value = 1.0; | physical_quantity!(0,1,0) v2; v2.value = 1.0; | physical_quantity!(1,1,0) prod = v1*v2; // line 25 | printf("%f\n",prod.value); | return 0; | } -------------------------------- which fails with the compiler error: ------------ | units_minimal.d(25): incompatible types for ((v1) * (v2)): 'physical_quantity' and 'physical_quantity' | units_minimal.d(25): 'v1' is not an arithmetic type | units_minimal.d(25): 'v2' is not an arithmetic type | units_minimal.d(25): cannot implicitly convert expression ((v1) * (v2)) of type physical_quantity to physical_quantity ------------ My guess was that the problem is with the operator overloading, and indeed, replacing line 25 by ------------- | physical_quantity!(1,1,0) prod = v1.opMul(v2); // line 25 ------------- changes the compiler error to: ------------ | units_minimal.d(12): template units_minimal.physical_quantity!(1,0,0).physical_quantity.opMul(T) templates don't have properties | units_minimal.d(25): function expected before (), not v1 of type physical_quantity | units_minimal.d(25): cannot implicitly convert expression ((v1)((v2))) of type int to physical_quantity ------------ My next guess was, that the IFTI causes the problem, so I changed lines 12 and 25 to ------------ | template T_opMul(T) { // line 12 ------------ | physical_quantity!(1,1,0) prod = v1.T_opMul!(typeof(v2)).opMul(v2); // line 25 ------------ and now the program worked correctly. Of course, in this state, the whole code is far from useful. Without IFTM and operator overloading, the whole point of the library is moot: allowing readable code with compile-time error checking. Even if IFTI and operators would work, there is, of course, some way to go for a working library, but I am confident, that this it will be possible to go that way. Greetings, Norbert
Apr 23 2006
BCS wrote:I feel your pain. A few post back I include a link to a unit.d program that does just what you are trying to do, but a run time (with all of the costs :-p that entails, OTOH it lets you do units dynamically) I have bean waiting for better template support to do exactly the same project you are looking at. Until that happens I think we're sunk. I was looking at something like this: struct Unit(T, int L, int M, int T) { T val; Unit(T, L, M, T) opAdd(Unit(T, L, M, T) op) { Unit(T, L, M, T) ret = { val : val+op.val }; return ret; } // this will need some improvements to templates Unit(T, L+opL, M+opM, T+opT) opMul(Unit(T,opL, opM, opT) op) { Unit(T, L+opL, M+opM, T+opT) ret = { val : val*op.val }; return ret; } .. }Pretty much the same thing.Lets hope walter makes some of these possible in the near future.That's one of the main intentions of my detailed post. Walter clearly stated that the current IFTI mechanism is rudimentary and that he intends to improve it. Giving clear use cases may help in finding out the details of what is really needed. The difficulty is, that D templates are so fundamentally different from C++ templates, that it really is hard to notice which features are really missing in D and which things should simply be done in a different way. Coming from C++, meta-programming in D asks for a completely different way of thinking, yet it is important not to throw overboard all the experience that was gained in C++.
Apr 23 2006
I don't mean to rain on your parade, but I tend to think the units should be under a namespace other than the global one. I also think the unit.d should most definitely be inlined because the few dude already wrote are a drop in the bucket compared to the full barrage of units that we use. There will be *alot* of code in that file that will be useless in any given program. Uncompiled code size doesn't matter for anything other than the amount of time spent compiling (and scrolling). Templates aren't needed, they just make code more legible. Well anyways, I'm glad it's being figured out. I do wish it was part of phobos though because where there are numbers, there are usually units. Most of the time the Powers That Be(tm) care that we don't mistake our meters for our feet. A good unit conversion table is an important asset to have around. Well.. thanks!
Apr 24 2006
Dan wrote:I don't mean to rain on your parade, but I tend to think the units should be under a namespace other than the global one.Maybe. I did not even think about that, yet. Now, thinking of it, I'm not sure whether I would go for a separate namespace. Of course, they will be in a separate module, but inside that, I would rather choose full names like "meter", "liter", "second", "hour" etc. instead of abbreviations ("m", "l", "s", "h"). And with these full names, I think the namespace-pollution is not that large: only few unit names are really likely to be used for other symbols in a program (especially in a program that deals with physics)I also think the unit.d should most definitely be inlined because the few dude already wrote are a drop in the bucket compared to the full barrage of units that we use. There will be *alot* of code in that file that will be useless in any given program.Guess, the units should be grouped into sub-modules. If you want to use only SI units, you don't need the thousands of ancient units that can be found in history books.Uncompiled code size doesn't matter for anything other than the amount of time spent compiling (and scrolling). Templates aren't needed, they just make code more legible.Sorry, I don't get your point here. The templates in my proof-of-concept code are essential for the functionality and have nothing to do with legibility. Each physical dimension defines a type (like meter, squaremeter, cubicmeter, ...) the list of these possible types is infinite, so you cannot explicitely define all of them by hand. Only a limited set of types has legible names (which are defined as aliases) but also combined dimensions like m^3*s/kg^13 could occur internally and are then simply instantiated when needed. Greetings, Norbert
Apr 24 2006