1

Perhaps the question might be tied to a theoritical or mathematical forum, but since it is for programming purpose, i ask here first:

In a computer vision context, i write a couple of interfaces intended to be the "read-only" part of vectors. So i define "IVector2R" and "IVector3R" that only contain getters. The question is: does IVector2R extends IVector3R (and the "y getter" always returns 0), or is it the opposite: IVector3R extends IVector2R?

I would like a conception as close as possible to the mathematic/sets theory...

Thank you for your attention

  • 3
    I suspect that both of these scenarios would be a violation of Liskov Substitution Principle. – Mr Cochese Nov 28 '13 at 18:24
  • Do not mess with inheritance/interfaces for Vector2/Vector3. If possible give the types overloaded operators and value type semantics, that's the only thing that matters. akrzemi1.wordpress.com/2012/02/03/value-semantics – jdv-Jan de Vaan Dec 18 '13 at 13:14
  • To elaborate on my comment: As a graphics programmer, Vector3 is a type that is about as fundamental as an int. Have you ever felt the need to derive int from byte or vice versa? – jdv-Jan de Vaan Dec 18 '13 at 13:24
5

I would like a conception as close as possible to the mathematic/sets theory…

When you work with vector coordinates, it means that you work in some vector space where you chose a base. Asking if there is a natural relation between 2-dimensional vectors and 3-dimensional vectors is the same thing as as asking if there is a natural relation between a 2-dimensional vector space equipped with a base and a 3-dimensional vector space equipped with a base. In general, there is not.

However, it seems you are working with a euclidean space with a basis (e1, e2, e3) so that the vector of coordinates (X,Y,Z) is Xe1 + Ye2 + Ze3. If you assume that your 2-dimensional space has the basis (e1,e2) then you have two natural maps between 2-dimensional vectors and 3-dimensional vectors:

- The projection (X,Y,Z) -> (X,Y)
- The embedding (X,Y) -> (X,Y,0)

Therefore, from a mathematical point of view, there is no natural way to express a relation between 2-dimensional vectors and 3-dimensional vectors that you could express through inheritance. There is however two natural transformations that you could implement as regular functions.

3

In my opinion, neither nor: Both are n-tuples with different n. The problem with this is that most languages don't allow types to be parameterized by a value.

When deciding a certain inheritance, you should consider matrix multiplication.

/1 0\   /2\ ?  /1 0 0\   /2\
\0 1/ · |3| =  |0 1 0| · \3/
        \4/    \0 0 0/

You are implicitly arguing that one or both multiplications work (and should possibly be equivalent). At this point every mathematician is having a heart attack, because you violated certain rules about the required dimensionality.

I think it is obvious that Vector3 can't be a subtype of Vector2, and not the other way round: You cannot generally use one in place of the other. But both have common properties. I would probably define an interface Vector with methods like component(i) which gives the i-th component (instead of getX, getY, …), size() which gives the dimensionality, and norm() which calculates the length.

If you are hell-bent on having one type inheriting from another, consider these test cases:

// failure with Vector3 <: Vector2 -- vec.z can be != 0
if (vec instanceof Vector2)
  assert vec.z == 0;

// failure with Vector3 <: Vector2 -- vec.z can be != 0
if (vec instanceof Vector2)
  assert vec.norm() == sqrt(vec.x^2 + vec.y^2); // euclidean norm - invalid in non-cartesian systems

// failure with Vector2 <: Vector3 if they are mutable (Circle-Ellipse Problem)
if (vec instanceof Vector2) {
  vec.z = 42;
  assert vec.z == 0;
}

// failures concerning dimensionality with *any* inheritance relation
// when considering matrix multiplication

This means that I have to grudgingly admit that having Vector2 inherit from Vector3 would work in most cases, if they are immutable and you aren't doing anything more fancy than addition or scalar multiplication.

Note On Coordinates

If you don't want to do vector algebra but just want to represent coordinates, then this answer would be different, because 2D coordinates can be viewed as a projection of 3D coordinates into a plane (or any other surface parameterizable by two numbers). In this case, every 2D coordinate would also have a 3D coordinate, but the two coordinates are from two different coordinate systems. Projection into the z = 0-plane is one very special case of this where it is possible to view the 2D-coordinate as a kind of 3D-coordinate. This is not generally the case in all 2D coordinate systems.

  • I think even in the vector algebra case, if the multiply function were smart enough to take the minimum dimensions, then your cute little ASCII diagram would work. Maybe you'd want two methods: multiplyStrict() and multiplyLiberal(). – user949300 Nov 28 '13 at 19:53
  • @user949300 Is there a use case for this? Most likely not, as the underlying math has no analogue for the "liberal" multiplication AFAIK. – user7043 Nov 28 '13 at 20:36
2

Interesting.

From the Liskov Substitution Principle, one could argue that a Vector3R "is a" Vector2R, but just with more stuff (a Z axis if these are dimensions, a "y getter" in your vision example). I think from a "purist" math/sets POV this works best. The drawback is that your class hierarchy might get messy, with eventual Vector4R, SomeSpecialVector3R, ImmutableVector3R, etc...

So, arguing that a Vector2R is a Vector3R but always returns 0 (or null, etc.) makes a bit of sense too. And it might make your class hierarchy much less messy. For example, you don't even need a Vector2R class. Perhaps just an additional method, whatIsMyN().

Option#2 is how Java handles it's Collections - instead of a gazillion classes in a dense hierarchy, they are allowed to say "no" by returning 0 (actually, in their case, they do even worse, throw an exception). Some programmers like it, and some hate it. Drawback: It requires that your code be a bit smarter: either ask ahead of time if this "Vector3" has a y getter, or be prepared for and ready to deal with 0s coming back.

I've coded both ways. Depends how "reasonable" it is, in your domain, for 0s to be coming back.

  • The first option is the only reasonable one. – Benjamin Gruenbaum Dec 18 '13 at 13:39
0

Please define what methods or operations Vector2R and Vector3R have in common and the use case where you'd want to treat them the same without needing to know which is which. Those would be reasons to come up with a common interface. Failing that, there is no useful answer to your question.

I'm presuming matrix multiplication is your use case? "Vector" and "Array" are loaded terms in some languages. In Java they are the same, but in Scala, Vector=shallow tree. In JavaScript, Array=Map/Dictionary. Even in math and physics, "Vector" can mean many things.

Otherwise, "Favor Composition Over Inheritance." But I'll give you some background information.

Most languages have multidimensional arrays built in:

Matrix Multiplication can be done in most languages as well:

So most languages side-step your issues or provide no support for abstracting over n dimensions where n can change. This is why your use case is critical for designing a meaningful abstraction for treating arrays of different dimensions as the same.

0

So finally, I simplify by only declaring a IMatrixR interface. I let the implementation doing more "optimized" stuff with specific classes such as Vector3 or Vector4, both implementing the IMatrixR interface.

But at a generic level, the UML diagram becomes a little bit more tricky to draw since there is more constraints to indicate and a set of exceptions (or error code to return) to specify on each methods; those implying conditions that i would rather automatically avoid by the type checking process during compilation.

As an example, the mul(IMatrixR,IMatrixR) method has to raise an exception when the dimensions are not compatible. But the overloading method mul(Matrix4,Vector4) will never throw it! Whereas the standard implementation of mul(IMatrixR,IMatrixR) does. The overloading method will be more efficient and does not require to be surrounded by try/catch...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.