49

I am writing a program that involves working with both polar and Cartesian coordinates.

Does it make sense to create two different structs for each kind of points, one with X and Y members and one is with R and Theta members.

Or is it too much and it is better to have just one struct with first and second as members.

What I am writing is simple and it won't change much. But I am curious what is better from a design point of view.

I am thinking the first option is better. It seems more readable and I will get the benefit of type checking.

  • 11
    I always create a new struct/class per purpose. Need a 3d vector, create a struct 3d_vector with three floats. Need a uvw representation, create a struct texture_coords with three floats. Need a position in a 3d environment, create a struct position with three floats. You get the point. It allows for far better readability than using the same thing all over. If you are bothered by defining the same thing multiple times, use a base 3-float-struct and define multiple names to be that same structure. – Kevin Nov 10 '15 at 11:47
  • If they have some common methods then maybe one. Would you ever need to compare equality of the two? – paparazzo Nov 10 '15 at 15:52
  • 8
    @Sidney Unless you absolutely need the functionality I wouldn't. You need to do a sin/arcsin operation to convert between the two representations. This is going to introduce a bit of bitrot in the least significant bits every time you do the conversion. I'm almost certain you'd end up with similar pain to what I went through trying to deal with a class that provided both an event frequency and a time between events (x and 1/x) representation of something. Tracking which representation is cannonical in the class and dealing with all the rounding headaches isn't something I'd want to do again. – Dan Neely Nov 10 '15 at 16:02
  • 3
    A good example of a datatype that can represent lots of things is the string, but "stringy typed" is an antipattern. Wrt your example. Try to implement dot product for a type that supports both coordinate systems. – Nathan Cooper Nov 10 '15 at 17:58
  • 1
    You should strive to use different types whenever applicable, to get the benefits of type safety -- this will prevent you from sending the wrong type to/from a function (using compile time correctness checking, depending on your programming language). It will ease maintenance because you can find all true uses of some type (without getting erroneous usages due to types being conflated). – Erik Eidt Nov 10 '15 at 18:05
17

I have seen both solutions, so it is definitely context dependent.

For readability, having multiple structs as you suggest is very effective. However, in some environments, you want to do common manipulations to these structs, and you find yourself duplicating code, such as matrix*vector operations. It can get frustrating when the particular operation isn't available in your flavor of vector because nobody ported it there.

The extreme solution (which we eventually gave into) is to have a CRTP templated base class with functions get<0>() get<1>() and get<2>() to get the elements in a generic manner. Those functions are then defined in the Cartesian or Polar struct that derives from this base class. It solves all of the problems, but comes at a rather silly price: having to learn template metaprogramming. However, if template metaprogramming is already fair game for your project, it might be a good match.

  • 1
    Your answer is very interesting. is it possible to provide an example ? – Moha the almighty camel Nov 11 '15 at 7:56
  • 1
    I can probide an example of what I've done: FIR filtering polars, cartesians and vectors thereof. The math was quite similar, sans angle (un)wrapping, the code had some parts duplicated anyway for performance reasons and we used templates for where it was the same. Used different names for all stuff. Cort's "extreme solution" could have saved a few duplications, but not nearly all of them. – Eugene Ryabtsev Nov 11 '15 at 10:42
  • 1
    My first reaction was that a situation like this would be better solved by casting, but it turns out to be somewhat risky. – 200_success Nov 11 '15 at 15:47
114

Yes, it makes a lot of sense.

The value of a struct is not just that it encapsulates data under a handy name. The value is that it codifies your intentions so that the compiler can help you verify that you don't violate them some day (e.g. mistake a polar coordinate set for a cartesian coordinate set).

People are bad at remembering such niggling details, but good at creating bold, inventive plans. Computers are good at niggling details and bad at creative plans. Therefore it is always a good idea to shift as much niggling-detail maintenance to the computer, leaving your mind free to work on the grand plan.

  • 6
    +1 A perfect description of using the computer to do what computers are good at doing, and letting your brain focus on your own job. – BrianH Nov 10 '15 at 15:04
  • 8
    "Programs should be written for people to read, and only incidentally for machines to execute." -- from "Structure and Interpretation of Computer Programs" by Abelson and Sussman. – hlovdal Nov 11 '15 at 15:50
18

Yes, while both Cartesian and Polar are (in their place) eminently sensible coordinate representation schemes, they should ideally never be mixed (if you have a point Cartesian{1,1}, it is a very different point from Polar{1,1}).

Depending on your needs, it may also be worth implementing a Coordinate interface, with methods like X(), Y(), Displacement() and Angle() (or possibly Radius() and Theta(), depending on).

  • It is even more important if the OP is making classes out of those structs, since the operations on cartesian and polar coordinates are different. – Mindwin Nov 10 '15 at 14:20
  • 1
    +1 for the last paragraph, that is the ideal solution. A point is space is an object; the internal representation of that point should not matter. Of course, real-world concerns (performance, rounding errors) might cause it to matter. It all depends on what this is being used for. – BlueRaja - Danny Pflughoeft Nov 10 '15 at 21:24
  • And also, for this example, it's unlikely to change, but if they were 2 other classes, nothing tells you that they might diverge at some points. – dyesdyes Nov 10 '15 at 22:43
8

In the end, the goal of programming is to toggle transistor bits to do useful work. But thinking at such a low level would lead to unmanageable craziness, which is why there are higher-level programming languages to help you hide the complexity.

If you just make one struct with members named first and second, then the names don't mean anything; you would essentially treat them as memory addresses. That defeats the purpose of the high-level programming language.

Furthermore, just because they all happen to be representable as a double doesn't mean that you can use them interchangeably. For example, θ is a dimensionless angle, whereas y has length units. Since the types are not logically substitutable, they should be two incompatible structs.

If you really need to play memory-reuse tricks — and you almost certainly shouldn't — you can use a union type in C to make your intention clear.

2

Firstly, have both explicitly, as per @Kilian-foth's entirely sound answer.

However, I'd like to add:

Ask: Do you really have operations that are generic to both when considered as pairs of double? Note this not the same as saying that you have operations that apply to both in their own terms. For example 'plot(Coord)' cares whether the Coord is Polar or Cartesian. In the other hand, persisting to file just treats the data as it is. If you do really have generic operations, consider either defining a base class, or defining a converter to a std::pair<double, double> or tuple or whatever you have in your language.

Further, one approach might be to treat one Coordinate type as more fundamental and the other as merely support for user or external interaction.

So you might ensure all fundamental operations are coded for Cartesian and then provide support for converting Polar to Cartesian. This avoids implementing different versions of many operations.

1

A possible solution, depending on the language and if you know that both classes will have similar methods and operations, would be to define the class once and use type aliases in order to explicitly name the types differently.

This also has the advantage that as long as the classes are exactly the same, you can maintain only one, but as soon as you need to change them, you don't need to modify the code using them since the types have already been used distincly.

Another option, depending again on the usage of the classes (if you need polymorphism and such) is to use public inheritance to both new types, so they have the same public interface as the common type they both represent. This also allows for separate evolution of the types.

  • The names of members in both classes are not the same. actually the names are the only difference between the two classes – Moha the almighty camel Nov 12 '15 at 9:07
  • @Mhd.Tahawi You could implement getters and setters with the appropriate names, ensuring that the class you are using is the same, but providing appropriate names for the operations you want to use. It becomes a bit more verbose, but you'll have to duplicate less code. – Svalorzen Nov 12 '15 at 9:26
0

I believe that having the same member names is a bad idea in this case, because it makes the code more error prone.

Imagine the scenario: you have a couple of cartesian points: pntA and pntB. Then you decide, for some reason, that they should better be represented in polar coordinates, and change the declaration and constructor.

Now, if all your operations were just method calls like:

double distance = pntA.distanceFrom(pntB);

then you're fine. But what if you used the members explicitly? Compare

double leftMargin = abs(pntA.x - pntB.x);
double leftMargin = abs(pntA.first - pntB.first);

In the first case, the code will not compile. You'll see the error immediately and will be able to fix it. But if you have the same member names, the error will be only on the logical level, much harder to detect.

If you write in a non-object-oriented language, then it's even easier to pass wrong struct to the function. What's to stop you from writing the following code?

double distance = calculate_distance_polar(cartesianPointA, polarPointB);

Different data types, on the other hand, would allow you to find the error during compilation.

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.