[Miles's message didn't appear to get through]
Thanks Miles. A couple points:
- I gather from this that you would find "functional" (non-modifying)
style to be surprising. This is interesting because it's not what I
would intuitively expect would be the hope of most coders.
- Deprecation is always an option. If I went with option (A), I'd
probably consider deprecating certain methods in MutableDouble2D. But
I've always tried to be very slow and conservative on MASON so as to
avoid having to deprecate much.
- You mentioned polymorphism, so I thought I'd mention that
interestingly Double2D and MutableDouble2D do not derive from one
another; and likewise similar other pairs in MASON (Int2D, Double3D,
Int3D) The reason for this is straightforward: in both cases I want
users to be able to access the instance variables directly rather than
be required to use methods like getX() and setX(), which would *not*
be inlinable (indeed, non-inlinability s a major problem for the
java.awt.Point family as a result). The problem is that Double2D
needs to have those variables final, while MutableDouble2D can't have
them final. There's no way in Java to derive a class and change the
finality of certain instance variables. It's plausible to make them
subclasses of the same interface I suppose.
Sean
On Oct 29, 2009, at 6:41 PM, Miles Parker wrote:
>
> I'm not sure this msg got through..?
>
> Begin forwarded message:
>
>> From: Miles Parker <[log in to unmask]>
>> Date: October 28, 2009 12:30:36 PM PDT
>> To: MASON Multiagent Simulation Toolkit <[log in to unmask]
>> >
>> Subject: Re: Double2D and MutableDouble2D Conundrum
>>
>>
>> Hey Sean,
>>
>> Sort of from the peanut gallery here, but I would recommend a third
>> more annoying approach. Perhaps the best thing to do in this API
>> evolution pattern ;) is to create a new set of methods that has the
>> ideal consistent semantics and deprecate anything from old API. I
>> think that replacement vs. modification of new values is one of the
>> easiest ways to be surprised and the most prone to those kinds of
>> strange outcomes that befuddle API users. Personally, I'm never
>> quite happy with APIs that do things that "read" like they're
>> modifying a value but really don't, though they are really common.
>> For me, if I call:
>>
>> d1.add(d3)
>>
>> I'm a bit surprised if I don't see d1 modified even though as I say
>> that kind of usage is very common. I recently ran into this with a
>> 3D library which is why I mention it. As much as I dislike patterns
>> that involve statics esp. since Java method dispatch is so brain-
>> dead I think there is enough precedence with Math libs and such to
>> do something like..
>>
>> public static Math2D.add(m1, m2) -> m1 + m2
>>
>> There is an issue with polymorphism here to in that from my reading
>> of below this would be the case in Mutable, so that if I change the
>> type of d1 somewhere else in the code, it will break something at a
>> completely different line which causes extra confusion. But that's
>> implicit in what you say below.
>>
>> cheers,
>>
>> Miles
>>
>>
>>
>> On Oct 27, 2009, at 11:41 AM, Sean Luke wrote:
>>
>>> 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
>>
>>
>
> Miles T. Parker
> President and Chief Software Architect
> Metascape, LLC
>
> tel: 509-643-4441
> skype: milestravisparker
> mailto:[log in to unmask]
> http://metascapeabm.com
> http://www.linkedin.com/in/milestparker
> http://milesparker.blogspot.com
>
|