I'm facing a tough decision on Double2D which could affect the entire
community and so I'm interested in some feedback.
I've been going through the physics2d engine and doing some cleanup
and one thing that could really help in that cleanup is to add some
functions to Double2D which are presently only in MutableDouble2D, but
to do so in such a way that Double2D's version would have somewhat
different semantics.
MutableDouble2D has four kinds of ways of "adding":
MutableDouble2D m1, m2, m3; // 2d vectors
double v; // a scalar
m1.addIn(m2); // m1 <- m1 + m2, return m1
m1.addIn(v); // m1 <- m1 + v (at all slots in m1), return m1
m1.add(m2, m3); // m1 <- m2 + m3, return m1
m1.add(m2, v); // m1 <- m2 + v (at all slots in m1), return m1
The use of "add" in this context is unfortunate, I know. It's going
to cause problems in a second. But originally my idea was to enable
stuff like this:
m1 = new MutableDouble2D().add(m2, m3).multiplyIn(v).normalize();
Which does
m1 <- normalize((m2 + m3) * v)
... but doesn't do any new allocations at all, because at each step we
just overwrite the variables inside the MutableDouble2D we created.
Okay, so that's cute. The problem comes when I want to add similar
functionality to Double2D. I can't implement an "addIn" method
because Double2D is IMMUTABLE. Instead, I'd do something like this:
Double2D d1, d2, d3;
double v;
d1 = d2.add(d3).mutiply(v).normalize();
This does the same thing but at each step a new Double2D is created.
For example,
d1.add(d2); // new d <- d1 + d2, return d
That's a good, easy to understand functional style which is much less
convoluted than the MutableDouble2D approach, BUT it allocates lots of
Double2Ds, which isn't particularly efficient, though it's not
horrible. So it's a useful functionality to have in Double2D.
The problem is that the semantics are somewhat different than
MutableDouble2D's semantics, in which the original object is
OVERWRITTEN. This is particularly obvious in the normalize() method,
which in MutableDouble2D normalizes the actual MutableDouble2D, but
for Double2D would produce a new Double2D (it's have to).
Also MutableDouble2D's add(...) method, for example, takes two
arguments and has totally different semantics than Double2D's add(...)
method would.
I'm trying to nail down what options I have. One choice I have been
mulling over is to add methods to Double2D like add(d) [note one
argument], multiply(v), etc., and then also create similar
MutableDouble2D methods with the same names. But the question is how
the MutableDouble2D methods should work. Should they (A) produce NEW
MutableDouble2D instances or (B) overwrite the existing
MutableDouble2D instance, like other MutableDouble2D methods presently
do?
(A) is more semantically consistent with the proposed new Double2D
methods.
(B) is semantically consistent with the existing MutableDouble2D
methods.
I'm trying to follow the principle of least surprise but I don't know
which of these would have less surprise. normalize() in particular
will *have* to be case (B). There's no way around it. Which has been
nudging me to wards doing (B). A third option would be to just
create Double2D methods and not create ANY equivalent MutableDouble2D
methods.
The decision made here will have a long-standing effect on use of
these classes, and they're so integral to MASON that I want to be very
very careful. Backward compatability will be retained but I am
concerned about making things weird in the future.
So I'd really appreciate some opinions on the matter.
Sean
|