www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Extending typedefs

reply Deewiant <deewiant.doesnotlike.spam gmail.com> writes:
With strong typedefs, we can do type checking, like in the following canonical
example:

---
typedef int Mass, Volume;

Mass x = 3;
Volume y = 4;

x = y; // error, cannot implicitly convert from Volume to Mass
---

But what we cannot do is the following:

---
Mass x;
Volume y;
Density z;

z = x / y;
---

Since x / y is an expression of type Mass (I believe), which can't be implicitly
converted to Density. Thinking about this from physics's point of view, though,
it should work: mass divided by volume is the definition of density.

Of course, this can be done with sufficient casting. But as we all know, casts
are clumsy and make code get ugly very quickly.

Plus, things get really ugly when typedefing classes. See the following simple
example, for instance:

---
class Vector {
	real x, y;
	this(real a, real b) { x = a; y = b; }
	Vector opMul(real n) { return new Vector(n * x, n * y); }
}
typedef Vector Velocity;

Velocity vel;
vel = cast(Velocity)(cast(Vector)vel * 2));
---

The inner cast is needed since otherwise DMD complains that "this for opMul
needs to be type Vector not type Velocity". The outer cast, of, course, is to
make the now-Vector (vel * 2) back into a Velocity. I wasn't even aware of this
before starting to write this post. Even worse:

cast(Vector)vel *= 2;

Makes sense, right? Not to me, at least. This is one of the few instances were
an lvalue can be a cast expression. Surely the "this" pointer in the class
should be typedefed along with the class itself!

But even though I'd label the above as a bug (language or compiler, beats me), I
digress.

I'm proposing a syntax which would allow, for example, expressions of type (Mass
/ Volume) to be implicitly converted to Density. What I'm thinking of is along
these lines:

typedef int Mass, Volume, Density;
typedef (Mass / Volume) Density;

So now, Density behaves like a normal typedef of an int until an expression of
type (Mass / Volume) comes along, at which point a cast is no longer needed to
convert the expression to type Density.

At best, the compiler could infer the other forms from the above, so that this
is not needed:

typedef (Mass / Volume) Density;
typedef (Mass / Density) Volume;
typedef (Density * Volume) Mass;
typedef (Volume * Density) Mass;

But it's enough for me if the above works, whether it needs one line or four.

Comments on this or the "this" pointer casting?
Jan 26 2006
next sibling parent reply "Carlos Smith" <carlos.smith sympatico.ca> writes:
"Deewiant" <deewiant.doesnotlike.spam gmail.com> wrote in message 
news:dra43i$oec$1 digitaldaemon.com...
 With strong typedefs, we can do type checking, like in the following 
 canonical
 example:

 ---
 typedef int Mass, Volume;

 Mass x = 3;
 Volume y = 4;

 x = y; // error, cannot implicitly convert from Volume to Mass
 ---

 But what we cannot do is the following:

 ---
 Mass x;
 Volume y;
 Density z;

 z = x / y;
 ---

 Since x / y is an expression of type Mass (I believe), which can't be 
 implicitly
 converted to Density. Thinking about this from physics's point of view, 
 though,
 it should work: mass divided by volume is the definition of density.

 Of course, this can be done with sufficient casting. But as we all know, 
 casts
 are clumsy and make code get ugly very quickly.

 Plus, things get really ugly when typedefing classes. See the following 
 simple
 example, for instance:

 ---
 class Vector {
 real x, y;
 this(real a, real b) { x = a; y = b; }
 Vector opMul(real n) { return new Vector(n * x, n * y); }
 }
 typedef Vector Velocity;

 Velocity vel;
 vel = cast(Velocity)(cast(Vector)vel * 2));
 ---

 The inner cast is needed since otherwise DMD complains that "this for 
 opMul
 needs to be type Vector not type Velocity". The outer cast, of, course, is 
 to
 make the now-Vector (vel * 2) back into a Velocity. I wasn't even aware of 
 this
 before starting to write this post. Even worse:

 cast(Vector)vel *= 2;

 Makes sense, right? Not to me, at least. This is one of the few instances 
 were
 an lvalue can be a cast expression. Surely the "this" pointer in the class
 should be typedefed along with the class itself!

 But even though I'd label the above as a bug (language or compiler, beats 
 me), I
 digress.

 I'm proposing a syntax which would allow, for example, expressions of type 
 (Mass
 / Volume) to be implicitly converted to Density. What I'm thinking of is 
 along
 these lines:

 typedef int Mass, Volume, Density;
 typedef (Mass / Volume) Density;

 So now, Density behaves like a normal typedef of an int until an 
 expression of
 type (Mass / Volume) comes along, at which point a cast is no longer 
 needed to
 convert the expression to type Density.

This is an interesting ideas. But it needs some work to make it practical. Does it means you would have to add a zillion typedef'initions to D ? There are a lot of concepts in math, physics, ... defined in terms of numbers who represent quantities. Also, will you have to define: typedef (Mass / unsigned int ) Mass; typedef (Mass / negative int ) NegativeMass; ... And, how will you define the different types for Surface. typedef ( ... ) Surface; // for a circle. typedef ( ... ) Surface; // for a square. typedef ( ... ) Surface; // for a cube.
 At best, the compiler could infer the other forms from the above, so that 
 this
 is not needed:

 typedef (Mass / Volume) Density;
 typedef (Mass / Density) Volume;
 typedef (Density * Volume) Mass;
 typedef (Volume * Density) Mass;

 But it's enough for me if the above works, whether it needs one line or 
 four.

May be, it is better to have a rule, that says, that new typedef, based on Basic Types of the language, inherit the properties of these Basic Types. And that means Mass can be divided by Volume because those are 2 integers. And the result can be assigned to Density, because this is also an integer
 Comments on this or the "this" pointer casting? 

Jan 26 2006
parent reply Deewiant <deewiant.doesnotlike.spam gmail.com> writes:
Carlos Smith wrote:
 Does it means you would have to add a zillion typedef'initions to D ?
 There are a lot of concepts in math, physics, ... defined in terms of
 numbers who represent quantities.

You would only have to add them if you wanted to make a strongly typed complete mathematics and physics library. <g> I'm not saying there have to be unique typedefs for every single thing, just that the feature could be useful.
 Also, will you have to define:
    typedef (Mass / unsigned int ) Mass;
    typedef (Mass / negative int ) NegativeMass;
    ...

No, not necessarily. Only if I want to be really picky about the distinction between a mass and a negative mass. I'd say the only sane thing is just to do a normal "typedef int Mass" and then deal with negatives as necessary. There is apparently such a thing as too strong typing. <g>
 And, how will you define the different types for Surface.
    typedef ( ... ) Surface;     // for a circle.
    typedef ( ... ) Surface;    // for a square.
    typedef ( ... ) Surface;    // for a cube.
 

typedef Surface Circle; typedef Surface Square; typedef Surface Cube; They're completely independent of each other. _Maybe_ something like "typedef (Square * Square * Square) Cube" in addition to the above, but it seems to me that that would require some strange functionality in Surface.opMul(), so probably not. It also seems to me that that should be done with classes and inheritance: circles and cubes are quite different things. Make a class (or interface) out of Surface and inherit as needed. I'm not trying to replace the class mechanism here or anything. It's just that while it is also possible in my Mass/Volume/Density example to just make classes of the three and make them interoperate as they should, there are reasons why I'd much rather not: * D doesn't allow overloading of =, so they would be pretty clumsy compared to aliased integers (which is what I'd use without my suggested typedefs). * I would basically be making an "abstract class Integer" which behaves just like a normal int (but see above point), except that it can be subclassed. * D isn't Ruby so I can't subclass int, which would deal with the above.
 At best, the compiler could infer the other forms from the above, so
 that this
 is not needed:

 typedef (Mass / Volume) Density;
 typedef (Mass / Density) Volume;
 typedef (Density * Volume) Mass;
 typedef (Volume * Density) Mass;

 But it's enough for me if the above works, whether it needs one line
 or four.

May be, it is better to have a rule, that says, that new typedef, based on Basic Types of the language, inherit the properties of these Basic Types. And that means Mass can be divided by Volume because those are 2 integers. And the result can be assigned to Density, because this is also an integer

And the result can be assigned to Speed, because that's also an integer. But it makes no physical sense to apply Volume to Speed. Did I misunderstand or did you just describe the alias keyword?
Jan 26 2006
parent reply Ben Phillips <Ben_member pathlink.com> writes:
In article <drabgr$uae$1 digitaldaemon.com>, Deewiant says...
And the result can be assigned to Speed, because that's also an integer. But it
makes no physical sense to apply Volume to Speed. Did I misunderstand or did you
just describe the alias keyword?

imho you should just use a plain old "int". I see no reason to have a billion different typedefs all for different units. Yes its OO, but to me it just looks like unnecessary abstraction. I mean the chances of messing up density = mass/volume are too slim in my opinion to be worth the extra effort (I'm having trouble thinking up more complex examples that are really so desparately in need of compile time arithmetic checking that its worth creating a huge hierarchy of variants on int)
Jan 26 2006
parent "Craig Black" <cblack ara.com> writes:
"Ben Phillips" <Ben_member pathlink.com> wrote in message 
news:drc0v4$la$1 digitaldaemon.com...
 In article <drabgr$uae$1 digitaldaemon.com>, Deewiant says...
And the result can be assigned to Speed, because that's also an integer. 
But it
makes no physical sense to apply Volume to Speed. Did I misunderstand or 
did you
just describe the alias keyword?

imho you should just use a plain old "int". I see no reason to have a billion different typedefs all for different units. Yes its OO, but to me it just looks like unnecessary abstraction. I mean the chances of messing up density = mass/volume are too slim in my opinion to be worth the extra effort (I'm having trouble thinking up more complex examples that are really so desparately in need of compile time arithmetic checking that its worth creating a huge hierarchy of variants on int)

My friend, you have no idea how many problems are caused by units miscalculations. All the smart people at NASA can't even get it right! That's why features that provide safety for the programmer such as bounds checking and garbage collection are so popular. If we can provide more safe units conversion utilities it will definitely be worth the effort. However, I do believe that the approach that he proposes is not the right approach. Again, for a good example of a C++ library that already tackles this problem, see SIUnits. -Craig
Jan 27 2006
prev sibling parent reply "Craig Black" <cblack ara.com> writes:
I don't think typedefs are the solution to the problem that you are trying 
to solve.  C++ has libraries that have the capabilities that you propose 
(The SIUnits Library).  They use templates and operator overloading to 
enforce correctness when dealing with units.  Perhaps we can solve this 
problem without burdening Walter with another feature request.

-Craig

"Deewiant" <deewiant.doesnotlike.spam gmail.com> wrote in message 
news:dra43i$oec$1 digitaldaemon.com...
 With strong typedefs, we can do type checking, like in the following 
 canonical
 example:

 ---
 typedef int Mass, Volume;

 Mass x = 3;
 Volume y = 4;

 x = y; // error, cannot implicitly convert from Volume to Mass
 ---

 But what we cannot do is the following:

 ---
 Mass x;
 Volume y;
 Density z;

 z = x / y;
 ---

 Since x / y is an expression of type Mass (I believe), which can't be 
 implicitly
 converted to Density. Thinking about this from physics's point of view, 
 though,
 it should work: mass divided by volume is the definition of density.

 Of course, this can be done with sufficient casting. But as we all know, 
 casts
 are clumsy and make code get ugly very quickly.

 Plus, things get really ugly when typedefing classes. See the following 
 simple
 example, for instance:

 ---
 class Vector {
 real x, y;
 this(real a, real b) { x = a; y = b; }
 Vector opMul(real n) { return new Vector(n * x, n * y); }
 }
 typedef Vector Velocity;

 Velocity vel;
 vel = cast(Velocity)(cast(Vector)vel * 2));
 ---

 The inner cast is needed since otherwise DMD complains that "this for 
 opMul
 needs to be type Vector not type Velocity". The outer cast, of, course, is 
 to
 make the now-Vector (vel * 2) back into a Velocity. I wasn't even aware of 
 this
 before starting to write this post. Even worse:

 cast(Vector)vel *= 2;

 Makes sense, right? Not to me, at least. This is one of the few instances 
 were
 an lvalue can be a cast expression. Surely the "this" pointer in the class
 should be typedefed along with the class itself!

 But even though I'd label the above as a bug (language or compiler, beats 
 me), I
 digress.

 I'm proposing a syntax which would allow, for example, expressions of type 
 (Mass
 / Volume) to be implicitly converted to Density. What I'm thinking of is 
 along
 these lines:

 typedef int Mass, Volume, Density;
 typedef (Mass / Volume) Density;

 So now, Density behaves like a normal typedef of an int until an 
 expression of
 type (Mass / Volume) comes along, at which point a cast is no longer 
 needed to
 convert the expression to type Density.

 At best, the compiler could infer the other forms from the above, so that 
 this
 is not needed:

 typedef (Mass / Volume) Density;
 typedef (Mass / Density) Volume;
 typedef (Density * Volume) Mass;
 typedef (Volume * Density) Mass;

 But it's enough for me if the above works, whether it needs one line or 
 four.

 Comments on this or the "this" pointer casting? 

Jan 26 2006
parent reply Oskar Linde <oskar.lindeREM OVEgmail.com> writes:
In article <dratih$1gmj$1 digitaldaemon.com>, Craig Black says...
I don't think typedefs are the solution to the problem that you are trying 
to solve.  C++ has libraries that have the capabilities that you propose 
(The SIUnits Library).  They use templates and operator overloading to 
enforce correctness when dealing with units.  Perhaps we can solve this 
problem without burdening Walter with another feature request.

The feature requests needed for a SIUnits clone are: - implicit function template instantiation - assignment operator overloading (only for structs is enough) /Oskar
Jan 26 2006
parent reply "Craig Black" <cblack ara.com> writes:
 The feature requests needed for a SIUnits clone are:
 - implicit function template instantiation
 - assignment operator overloading (only for structs is enough)

 /Oskar

I know Walter wants to eventually add implicit function template instantiation, but as for assignment operator overloading, I don't know. Do you think new typedef features would be easier to add to the compiler? Do you think that they would facilitate a library with the capability of SIUnits? -Craig
Jan 26 2006
parent reply Deewiant <deewiant.doesnotlike.spam gmail.com> writes:
Craig Black wrote:
 The feature requests needed for a SIUnits clone are:
 - implicit function template instantiation
 - assignment operator overloading (only for structs is enough)

 /Oskar

I know Walter wants to eventually add implicit function template instantiation, but as for assignment operator overloading, I don't know. Do you think new typedef features would be easier to add to the compiler? Do you think that they would facilitate a library with the capability of SIUnits? -Craig

If it requires quite a lot of template "magic" or some such, while such a library would be useful it doesn't exactly solve the problem, since it'd be difficult to add new units. Of course, if it provides its own mechanism for doing just that, that's fine. Unfortunately, porting such a library is probably a lot of work, even if we had the features that are, according to Oskar, required. My typedef suggestion just struck me as the simplest solution. I am not a compiler writer, so I don't know whether that's correct or not, but in my opinion it would be simpler for the D programmers, at least, than messing around with templates. And, of course, different syntax, unless assignment operator overloading arrives.
Jan 26 2006
parent reply "Craig Black" <cblack ara.com> writes:
 If it requires quite a lot of template "magic" or some such, while such a
 library would be useful it doesn't exactly solve the problem, since it'd 
 be
 difficult to add new units. Of course, if it provides its own mechanism 
 for
 doing just that, that's fine.

It does. SIUnits is much more involved than what you propose. It is based on the seven base units of the SI system: length, mass, time, electric current, temperature, amount of substance, and luminous intensity. All other SI units are merely combinations of one of these seven. The templates have the smarts to know when you multiply length times length, you get length squared, or area. If you divide length by time, you get velocity, etc. There are an infinite number of combinations of these units and therefore an infinite number of unit types. I believe such a system would be quite difficult to reproduce with typedefs as you propose.
 Unfortunately, porting such a library is probably a lot of work, even if 
 we had
 the features that are, according to Oskar, required.

I am less concerned with the difficulty of such a task and more concerned with eventually attaining the most complete and elegant solution.
 My typedef suggestion just struck me as the simplest solution. I am not a
 compiler writer, so I don't know whether that's correct or not, but in my
 opinion it would be simpler for the D programmers, at least, than messing 
 around
 with templates. And, of course, different syntax, unless assignment 
 operator
 overloading arrives.

If someone else writes the template, and you merely have to use it, is that so bad? SIUnits was probably a challenge to write, but using it is not that hard. However, perhaps you are right. But you are going to have to flesh out your idea more if it is to be on par with the capability of SIUnits. -Craig
Jan 26 2006
parent reply BCS <BCS_member pathlink.com> writes:
I have sketched some template code to see what it would take to do prity mutch 
exactly what you all are talking about. I thin that all that would be needed is 
implicet specilization of a function and allowing the return type of sutch a 
function to be determoned by the type of it's aruments.

Something like this:

template Unit(int L, int T, int M, int I, int K)
{
struct Unit
{
real v

Unit(L,T,M,I,K) opAdd(Unit(L,T,M,I,K) v2)
{
	Unit(L,T,M,I,K) ret;
	ret.v = v+v2.v;
	return ret;
}

// multiply this by an instance of Unit specialized for any values
// L,T,M,I&K come from this, l,t,m,i&k come from v2
Unit(L+l,T+t,M+m,I+i,K+k) opMul(Unit(l,t,m,i,k) v2)
{
	Unit(L+l,T+t,M+m,I+i,K+k) ret;
	ret.v = v*v2.v;
	return ret;
}
}
}

this has been brought up a number of times and I would rely like to see this 
kind of capacity added to D.


Craig Black wrote:
If it requires quite a lot of template "magic" or some such, while such a
library would be useful it doesn't exactly solve the problem, since it'd 
be
difficult to add new units. Of course, if it provides its own mechanism 
for
doing just that, that's fine.

It does. SIUnits is much more involved than what you propose. It is based on the seven base units of the SI system: length, mass, time, electric current, temperature, amount of substance, and luminous intensity. All other SI units are merely combinations of one of these seven. The templates

IIRC it is possible to get away with 5 dimensions of units if you allow the Mole to be just a count and the candela to be power per area.
 have the smarts to know when you multiply length times length, you get 
 length squared, or area.  If you divide length by time, you get velocity, 
 etc.  There are an infinite number of combinations of these units and 
 therefore an infinite number of unit types.  I believe such a system would 
 be quite difficult to reproduce with typedefs as you propose.
 
 
Unfortunately, porting such a library is probably a lot of work, even if 
we had
the features that are, according to Oskar, required.

I am less concerned with the difficulty of such a task and more concerned with eventually attaining the most complete and elegant solution.
My typedef suggestion just struck me as the simplest solution. I am not a
compiler writer, so I don't know whether that's correct or not, but in my
opinion it would be simpler for the D programmers, at least, than messing 
around
with templates. And, of course, different syntax, unless assignment 
operator
overloading arrives.

If someone else writes the template, and you merely have to use it, is that so bad? SIUnits was probably a challenge to write, but using it is not that hard. However, perhaps you are right. But you are going to have to flesh out your idea more if it is to be on par with the capability of SIUnits. -Craig

Jan 26 2006
parent reply "Craig Black" <cblack ara.com> writes:
"BCS" <BCS_member pathlink.com> wrote in message 
news:drbgor$21oo$1 digitaldaemon.com...
I have sketched some template code to see what it would take to do prity 
mutch exactly what you all are talking about. I thin that all that would be 
needed is implicet specilization of a function and allowing the return type 
of sutch a function to be determoned by the type of it's aruments.

 Something like this:

 template Unit(int L, int T, int M, int I, int K)
 {
 struct Unit
 {
 real v

 Unit(L,T,M,I,K) opAdd(Unit(L,T,M,I,K) v2)
 {
 Unit(L,T,M,I,K) ret;
 ret.v = v+v2.v;
 return ret;
 }

 // multiply this by an instance of Unit specialized for any values
 // L,T,M,I&K come from this, l,t,m,i&k come from v2
 Unit(L+l,T+t,M+m,I+i,K+k) opMul(Unit(l,t,m,i,k) v2)
 {
 Unit(L+l,T+t,M+m,I+i,K+k) ret;
 ret.v = v*v2.v;
 return ret;
 }
 }
 }

 this has been brought up a number of times and I would rely like to see 
 this kind of capacity added to D.

I agree. This would be a worthwhile addition to D's template capabilities. Why do you think Oskar suggested that assignment operator overloading would be required? -Craig
Jan 26 2006
parent Oskar Linde <oskar.lindeREM OVEgmail.com> writes:
Craig Black wrote:
 "BCS" <BCS_member pathlink.com> wrote in message 
 news:drbgor$21oo$1 digitaldaemon.com...
 I have sketched some template code to see what it would take to do prity 
 mutch exactly what you all are talking about. I thin that all that would be 
 needed is implicet specilization of a function and allowing the return type 
 of sutch a function to be determoned by the type of it's aruments.


<snip>
 this has been brought up a number of times and I would rely like to see 
 this kind of capacity added to D.

I agree. This would be a worthwhile addition to D's template capabilities. Why do you think Oskar suggested that assignment operator overloading would be required?

I apologize for that statement. It was made too hasty. Assignment operator overloading would not be necessary for a pure dimensional library that only does compile time checking of dimensional correctness. Assignment overloading would only affect a runtime behavior that in this case is meant to remain unchanged. For a library that deals with units on the other hand, assignment overloading would probably be necessary to handle conversions between different units / unit prefixes. --- Off Topic, regarding assignment overloading: I do not propose that assignment overloading should be implemented for classes. Classes are after all reference types. Structs, on the other hand, are value based and assigning one struct to another means today that a memcpy is being made. Assignment of structs is already a potentially expensive operation. Making this user definable makes sense. This would among other things make it possible to create an efficient BigInt-like type that would behave as a primitive type. Ideally, structs should also have the possibility of defining a destructor that gets called when it goes out of scope. This would allow the implementation of ref-counted data and more. I.e. the following should work: T a,b; a = 17; b = a; a++; assert(a != b); for a custom type T, without the implementation of a++ needing to do defensive .dup-ing of the contained data. (For a BigInt, a++ is on average a very inexpensive operation but is unfortunately impossible to implement like that if the data may be shared and you have no way of knowing.) /Oskar
Jan 30 2006