www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Suggestion: wrapping up value type objects

reply Kristian <kjkilpi gmail.com> writes:
I found it strange that D has no copy/clone operator, lets say 'opClone'=
.

Before you say that there is a reason for it and D does not handle objec=
ts  =

as value types (like C++ does), I have to point out that D already handl=
es  =

(or should handle) objects as value types in some situations. I'm talkin=
g  =

about the 'opXxx' operators ('opAdd', 'opSub', etc).

First I have to say that I'm not against using objects as reference type=
s.  =

But, sometimes it's necessary to create value types (out of classes), an=
d  =

using them could be easier than it's now.


If a class has an 'opXxx' operator, then it normally returns a temporary=
  =

object. Also the following should be true: 'a <op>=3D b' <-> 'a =3D a <o=
p> b'.  =

(Otherwise it would be a very rare case.)

Dublication is needed when creating a temporary object (in 'opXxx'). So,=
  =

why don't add the 'opClone' operator to the language? I mean, there  =

already are 'opXxx' functions. Why don't standardize the way of creating=
  =

copies/clones?


Then you could get rid of all the 'opXxx' and 'opXxx_r' functions: they =
 =

can be expressed by using 'opClone' and 'opXxxAssign' only. That would  =

also remove any unnecessary temporary objects from statements making  =

calculations very efficient (as stated earlier). For example:

     a =3D (b * c) + d;
->
     a =3D b.opClone().opMulAssign(c).opAddAssign(d);

And as mentioned in some earlier posts, you could also have binary and  =

unary clone operators. E.g. (I use '<-' here):

     a <- b;
->
     a =3D b.opClone();


The 'opClone()' function can also have a default implementation (done by=
  =

the compiler). All the member variables are dublicated by default. For  =

example:

     class Foo {
         int a;
         Bar b;
     }

The default 'opClone' would then be (generated by the compiler):

     Foo opClone() {
         Foo ret =3D new Foo;
         ret.a =3D a;
         ret.b =3D b.opClone();
         return(ret);
     }

If that's not wanted, or possible, then you can always implement the  =

function by yourself, of course.


Finally, there could also be easier way to create local objects (they to=
o  =

are discussed earlier in various posts). For example:

     Foo obj();  //a local object, already constructed


Benefits:
- There is a standard way to dublicate objects, making programs more  =

consistent, easier to read/write, and (thus) bug free.
- The number of operator functions are reduced almost to one third! (No =
 =

need to implement 'opXxx's and 'opXxx_r's.)
- No more unnecessary temporaries.
- Value type objects are indeed part of the language. Note that they don=
't  =

take anything away from normal reference types.
Oct 25 2006
parent reply Don Clugston <dac nospam.com.au> writes:
Kristian wrote:
 
 I found it strange that D has no copy/clone operator, lets say 'opClone'.
 
 Before you say that there is a reason for it and D does not handle 
 objects as value types (like C++ does), I have to point out that D 
 already handles (or should handle) objects as value types in some 
 situations. I'm talking about the 'opXxx' operators ('opAdd', 'opSub', 
 etc).
 
 First I have to say that I'm not against using objects as reference 
 types. But, sometimes it's necessary to create value types (out of 
 classes), and using them could be easier than it's now.
 
 
 If a class has an 'opXxx' operator, then it normally returns a temporary 
 object. Also the following should be true: 'a <op>= b' <-> 'a = a <op> 
 b'. (Otherwise it would be a very rare case.)
 
 Dublication is needed when creating a temporary object (in 'opXxx'). So, 
 why don't add the 'opClone' operator to the language? I mean, there 
 already are 'opXxx' functions. Why don't standardize the way of creating 
 copies/clones?
 
 
 Then you could get rid of all the 'opXxx' and 'opXxx_r' functions: they 
 can be expressed by using 'opClone' and 'opXxxAssign' only.

Not necessarily. Consider Vector and Matrix. Matrix a; Matrix b; Vector c; a = b * c; a = b.opClone().opMulAssign(c); // OK. But a = c * b; a = c.opClone().opMulAssign(b); // Oops -- we cloned the vector, should have cloned the matrix instead. In fact, in general you cannot even assume that (typeof(a*b) == typeof(a)) | (typeof(a*b) == typeof(b)) which means that cloning would sometimes create useless temporary objects. That would
 also remove any unnecessary temporary objects from statements making 
 calculations very efficient (as stated earlier). For example:
 
     a = (b * c) + d;
 ->
     a = b.opClone().opMulAssign(c).opAddAssign(d);
 
 And as mentioned in some earlier posts, you could also have binary and 
 unary clone operators. E.g. (I use '<-' here):
 
     a <- b;
 ->
     a = b.opClone();

int a = 2; int b = -3; if (a<-b) { ... } That particular one doesn't work. But := might be ok.
 The 'opClone()' function can also have a default implementation (done by 
 the compiler). All the member variables are dublicated by default. For 
 example:
 
     class Foo {
         int a;
         Bar b;
     }
 
 The default 'opClone' would then be (generated by the compiler):
 
     Foo opClone() {
         Foo ret = new Foo;
         ret.a = a;
         ret.b = b.opClone();
         return(ret);
     }
 
 If that's not wanted, or possible, then you can always implement the 
 function by yourself, of course.
 
 
 Finally, there could also be easier way to create local objects (they 
 too are discussed earlier in various posts). For example:
 
     Foo obj();  //a local object, already constructed

Currently, that compiles, and is equivalent to: Foo function() obj;
Oct 25 2006
parent reply Kristian <kjkilpi gmail.com> writes:
On Wed, 25 Oct 2006 15:24:07 +0300, Don Clugston <dac nospam.com.au> wro=
te:
 Kristian wrote:

 So, why don't add the 'opClone' operator to the language? I mean, the=


 already are 'opXxx' functions. Why don't standardize the way of  =


 creating copies/clones?
   Then you could get rid of all the 'opXxx' and 'opXxx_r' functions: =


 they can be expressed by using 'opClone' and 'opXxxAssign' only.

Not necessarily. Consider Vector and Matrix. Matrix a; Matrix b; Vector c; a =3D b * c; a =3D b.opClone().opMulAssign(c); // OK. But a =3D c * b; a =3D c.opClone().opMulAssign(b); // Oops -- we cloned the vector, should have cloned the matrix instead=

 In fact, in general you cannot even assume that
 (typeof(a*b) =3D=3D typeof(a)) |  (typeof(a*b) =3D=3D typeof(b))

 which means that cloning would sometimes create useless temporary  =

 objects.

Well, in you example (of course, I see your point though) 'Vector' shoul= d = not have the 'opMulAssign(Matrix)' function (because it makes no sense),= = so "a =3D c * b" would then be read as: a =3D b.opClone().opMulAssign(c); //ok Lets have two classes, 'A' and 'B'. 'a' is type of 'A' and 'b' of 'B'. Now if we have: typeof(a * b) =3D=3D typeof(a) typeof(b * a) =3D=3D typeof(b) then 'mathematics will be broken' (or how should I put it). When this = should be possible? In the example above "c * b" should produce a Matrix, not a Vector. *But* if it would be ok to produce Vector instead, then a =3D c.opClone().opMulAssign(b); would also be correct (assuming that you can assign a 'Vector' in 'a'), = = and no useless temporary objects are created. And if we have: typeof(a * b) =3D=3D typeof(b * a) =3D=3D typeof(a) then only 'A' will contain the corresponding operators (i.e. = 'opMulAssign(B)'). Thus there will be no problem to decide which object = to = clone. So we can use cloning, and the 'opXxx' and 'opXxx_r' functions are not = needed.
Oct 25 2006
parent reply Don Clugston <dac nospam.com.au> writes:
Kristian wrote:
 On Wed, 25 Oct 2006 15:24:07 +0300, Don Clugston <dac nospam.com.au> wrote:
 Kristian wrote:

 So, why don't add the 'opClone' operator to the language? I mean, 
 there already are 'opXxx' functions. Why don't standardize the way of 
 creating copies/clones?
   Then you could get rid of all the 'opXxx' and 'opXxx_r' functions: 
 they can be expressed by using 'opClone' and 'opXxxAssign' only.

Not necessarily. Consider Vector and Matrix. Matrix a; Matrix b; Vector c; a = b * c; a = b.opClone().opMulAssign(c); // OK. But a = c * b; a = c.opClone().opMulAssign(b); // Oops -- we cloned the vector, should have cloned the matrix instead. In fact, in general you cannot even assume that (typeof(a*b) == typeof(a)) | (typeof(a*b) == typeof(b)) which means that cloning would sometimes create useless temporary objects.

Well, in you example (of course, I see your point though) 'Vector' should not have the 'opMulAssign(Matrix)' function (because it makes no sense), so "a = c * b" would then be read as: a = b.opClone().opMulAssign(c); //ok

But how does the compiler know that this is a legal transformation?
 Lets have two classes, 'A' and 'B'.
 'a' is type of 'A' and 'b' of 'B'.
 Now if we have:
 
     typeof(a * b) == typeof(a)
 
     typeof(b * a) == typeof(b)
 
 then 'mathematics will be broken' (or how should I put it). When this 
 should be possible?

You've misunderstood me. My point was that it might be a third type. There's no guarantee that an opXXX function returns the same type as either of its operands. One can define for example Vector * TransposeVector = Matrix. opAdd() is a more general operation than opAddAssign(); it can't always be reduced to opClone() and opAddAssign().
Oct 25 2006
parent reply Kristian <kjkilpi gmail.com> writes:
On Wed, 25 Oct 2006 18:15:09 +0300, Don Clugston <dac nospam.com.au> wro=
te:

 Kristian wrote:
 On Wed, 25 Oct 2006 15:24:07 +0300, Don Clugston <dac nospam.com.au> =


 wrote:
 Kristian wrote:

 So, why don't add the 'opClone' operator to the language? I mean,  =




 there already are 'opXxx' functions. Why don't standardize the way =




 creating copies/clones?
   Then you could get rid of all the 'opXxx' and 'opXxx_r' functions=




 they can be expressed by using 'opClone' and 'opXxxAssign' only.

Not necessarily. Consider Vector and Matrix. Matrix a; Matrix b; Vector c; a =3D b * c; a =3D b.opClone().opMulAssign(c); // OK. But a =3D c * b; a =3D c.opClone().opMulAssign(b); // Oops -- we cloned the vector, should have cloned the matrix inste=



 In fact, in general you cannot even assume that
 (typeof(a*b) =3D=3D typeof(a)) |  (typeof(a*b) =3D=3D typeof(b))

 which means that cloning would sometimes create useless temporary  =



 objects.



 should not have the 'opMulAssign(Matrix)' function (because it makes =


 sense), so "a =3D c * b" would then be read as:
      a =3D b.opClone().opMulAssign(c);  //ok

But how does the compiler know that this is a legal transformation?

1) 'Vector' has 'opMul(Matrix)': a =3D c * b; -> a =3D c.opMul(b); Note that 'Vector.opMul(Matrix)' should not return 'Matrix'. If that wou= ld = be the case, then the function will belong to 'Matrix', i.e. = 'Matrix.opMulAssign(Vector)', and we could use cloning. That is, there is no point in: Vector { Matrix opMul(Matrix m) { Matrix ret =3D new Matrix; ret =3D m.opClone(); ret +=3D this; return(ret); } } 2) 'Vector' has 'opMulAssign(Matrix)': a =3D c * b; -> a =3D c.opClone().opMulAssign(b); We can use cloning obviously. But 'Vector.opMulAssign(Matrix)' returns = 'Vector', so this case is not valid here (we have defined that "b * c" a= nd = "c * b" returns 'Matrix'). 3) 'Matrix' has 'opMulAssign(Vector)': a =3D c * b; -> a =3D b.opClone().opMulAssign(c); After the cases 1 and 2 are checked, this is a legal transformation.
 Lets have two classes, 'A' and 'B'.
 'a' is type of 'A' and 'b' of 'B'.
 Now if we have:
      typeof(a * b) =3D=3D typeof(a)
      typeof(b * a) =3D=3D typeof(b)
  then 'mathematics will be broken' (or how should I put it). When thi=


 should be possible?

You've misunderstood me. My point was that it might be a third type. =

 There's no guarantee that an opXXX function returns the same type as  =

 either of its operands.

 One can define for example Vector * TransposeVector =3D Matrix.

 opAdd() is a more general operation than opAddAssign(); it can't alway=

 be reduced to opClone() and opAddAssign().

Ah, I see. You're right of course. Then that means that if a class has (or could have) a 'opXyzAssign' = function, then there is no need to implement the 'opXyz' and 'opXyz_r' = functions for it. But when there is no corresponding 'opXxxAssign' function for = 'opXxx'/'opXxx_r', then the compiler will use 'opXxx'/'opXxx_r' instead = of = 'opClone'. For example: A a; B b; C c; //typeof(b + c) =3D=3D 'A' //typeof('A' * a) =3D=3D 'A' a =3D (b + c) * a; -> a =3D b.opAdd(c).opMulAssign(a); And: //typeof(b * b) =3D=3D 'B' //typeof('B' + c) =3D=3D 'A' a =3D b * b + c; -> a =3D b.opClone().opMulAssign(b).opAdd(c); Two temporaties are always needed because 'typeof('B' + c)' is not 'B' o= r = 'C' (i.e. there is no 'A.opAddAssign(B)' and 'B.opAddAssign(A)' function= s). In either case (there is 'opXxxAssign', or there can not be), there is n= o = need to implement redundant functions, i.e. 'opXxxAssign' *and* = 'opXxx'/'opXxx_r'. Hence using 'opClone' reduces the number of needed operator functions by= = two thirds or one third! (Usually there can be a 'opXxxAssign' function for an operator, so the = number of needed operator functions will be reduced by two thirds = normally!) Now, if one tries to write an operator function 'A.opXyz(B)' that return= s = 'A' or 'B', the compiler will throw an error saying "define = 'A.opXyzAssing(B)' or 'B.opXyzAssing(A)' function instead".
Oct 25 2006
parent Kristian <kjkilpi gmail.com> writes:
On Wed, 25 Oct 2006 20:01:50 +0300, Kristian <kjkilpi gmail.com> wrote:
[snip]
 Now, if one tries to write an operator function 'A.opXyz(B)' that  =

 returns 'A' or 'B', the compiler will throw an error saying "define  =

 'A.opXyzAssign(B)' or 'B.opXyzAssign(A)' function instead".

Of course, the error message is not wanted when you cannot access the = source code of a class. For example: class A; //imported from a library class B { A opAdd(B); //should be allowed } Even if "A opAdd(B)" logically should be logically defined in 'A' (as "A= = opAddAssign(B)"), one cannot do it because the source code of 'A' is = inaccessible. One solution could be using a new keyword. For example: class B { extend A opAdd(B); } Ok, 'extend' isn't probably a good one, I just quickly invented it becau= se = "A opAdd(B);" should be in 'A' so it kind of extends 'A', and "extend A = = opAdd(B);" can be read as "extend A" + "opAdd(B);". Ok, that would allow one to write: A a; B b; a =3D a + b; But not: a +=3D b; So, the following could be possible instead: class B { extend A opAddAssign(B); } Now it really extends 'A' because 'B' should/cannot have such an operato= r = (it returns 'A' instead of 'B'). This would allow both the cases (with t= he = help of 'opClone' as described earlier): a =3D a + b; a +=3D b; You can think of "A opAddAssign(B);" actually being defined in 'A' inste= ad = of 'B', whenever 'B' is used with 'A' that is. (I am assuming here that 'B' can access all the necessary methods of 'A'= = to create such operators.)
Oct 26 2006