views:

955

answers:

12

The codebase where I work has an object called Pair where A and B are the types of the first and second values in the Pair. I find this object to be offensive, because it gets used instead of an object with clearly named members. So I find this:

List<Pair<Integer, Integer>> productIds = blah();
// snip many lines and method calls

void doSomething(Pair<Integer, Integer> id) {
  Integer productId = id.first();
  Integer quantity = id.second();
}

Instead of

class ProductsOrdered {
  int productId;
  int quantityOrdered;
  // accessor methods, etc
}

List<ProductsOrderded> productsOrdered = blah();

Many other uses of the Pair in the codebase are similarly bad-smelling.

I Googled tuples and they seem to be often misunderstood or used in dubious ways. Is there a convincing argument for or against their use? I can appreciate not wanting to create huge class hierarchies but are there realistic codebases where the class hierarchy would explode if tuples weren't used?

+20  A: 

First of all, a tuple is quick and easy: instead of writing a class for every time you want to put 2 things together, there's a template that does it for you.

Second of all, they're generic. For example, in C++ the std::map uses an std::pair of key and value. Thus ANY pair can be used, instead of having to make some kind of wrapper class with accessor methods for every permutation of two types.

Finally, they're useful for returning multiple values. There's really no reason to make a class for specifically for a function's multiple return values, and they shouldn't be treated as one object if they're unrelated.

To be fair, the code you pasted is a bad use of a pair.

rlbond
Just playing devil's advocate here, but are there really a lot of cases where you need a method that returns multiple unrelated values? The one scenario I see pretty often is the `TryParse` method for the built-in data types in C#. Here, you need to return a `bool` (whether the parse was successful) and, say, an `int` (the value if the parse was successful). To allow a second return value, the `out` keyword is used. I guess a tuple might be nice here instead, but I'm not sure whether it's better than declaring a type with a `bool` named `ParseSuccessful` and a generic `T` named `Value`.
DanM
@DanThMan: yes, though they're not unrelated. For example, the C++ algorithm `equal_range` returns a pair of iterators. Maps in c++ are equivalent to sets of `pair<key, value>`. There are other cases, but I can't think of them right now.
rlbond
DanThMan: Coming from the opposite direction (a language that supports multiple return values), one could ask the same: do you ever need "out" parameters if you've got multiple return values? You want to *return* 2 things, so why does one look like I'm passing it in? :-)
Ken
Also, most cases where I see "out" params are instances of "try to do this, if OK then save the result here, else tell me it's no good". Very often there's a parallel method that throws an exception on failure. So perhaps the *syntax* of exception catching (in languages like C#/Java) is the real problem here: if the try-catch construct was an expression, would you ever need out params? I'm not sure I would. Common Lisp has catch-as-expression (and conditions rather than just exceptions), and while you could do something like out-params, I've never once used that strategy in Lisp.
Ken
A: 

This is just prototype-quality code that likely was smashed together and has never been refactored. Not fixing it is just laziness.

The real use for tuples is for generic functionality that really doesn't care what the component parts are, but just operates at the tuple level.

Eclipse
Josh: What code will you write to fix this?
shahkalpesh
Exactly what he suggested would work. Without seeing more context I can't comment really on that.
Eclipse
A: 

This code clearly smells

The only reason I use tuples is for map. It is convenient to retrieve an object via another object.

Eric
+11  A: 

Tuples are used all the time in Python where they are integrated into the language and very useful (they allow multiple return values for starters).

Sometimes, you really just need to pair things and creating a real, honest to god, class is overkill. One the other hand, using tuples when you should really be using a class is just as bad an idea as the reverse.

Aaron Maenpaa
+1 returning multiple values is the big reason for me. A Pair class is probably a good compromise between returning Object[] or a whole new type.
Outlaw Programmer
+1  A: 

The obvious example is a coordinate pair (or triple). The labels are irrelevant; using X and Y (and Z) is just a convention. Making them uniform makes it clear that they can be treated in the same way.

Daniel Earwicker
+6  A: 

For what its worth, the code in the OP is a mess not because it uses tuples, but because the values in the tuple are too weakly typed. Compare the following:

List<Pair<Integer, Integer>> products_weak = blah1();
List<Pair<Product, Integer>> products_strong = blah2();

I'd get upset too if my dev team were passing around IDs rather than class instances around, because an integer can represent anything.


With that being said, tuples are extremely useful when you use them right:

  • Tuples exist to group ad hoc values together. They are certainly better than creating excessive numbers of wrapper classes.
  • Useful alternative to out/ref parameters when you need to return more than one value from a function.

However, tuples in C# make my eyes water. Many languages like OCaml, Python, Haskell, F#, and so on have a special, concise syntax for defining tuples. For example, in F#, the Map module defines a constructor as follows:

val of_list : ('key * 'a) list -> Map<'key,'a>

I can create an instance of a map using:

(* val values : (int * string) list *)
let values = 
    [1, "US";
     2, "Canada";
     3, "UK";
     4, "Australia";
     5, "Slovenia"]

(* val dict : Map<int, string> *)
let dict = Map.of_list values

The equilvalent code in C# is ridicuous:

var values = new Tuple<int, string>[] {
     new Tuple<int, string>(1, "US"),
     new Tuple<int, string>(2, "Canada"),
     new Tuple<int, string>(3, "UK"),
     new Tuple<int, string>(4, "Australia"),
     new Tuple<int, string>(5, "Slovenia")
     }

 var dict = new Dictionary<int, string>(values);

I don't believe there is anything wrong with tuples in principle, but C#'s syntax is too cumbersome to get the most use out of them.

Juliet
You can clean it up a bit though. How about this helper class?static class Tuple { static Tuple<T0, T1> Build<T0, T1>(T0 t0, T1 t1); }Then you can create a tuple like so: Tuple.Build(1, "US");Exploit that generic types are inferred for functions. :)
jalf
Still not as clean as in Python or ML/F#, but at least you avoid having to specify the generic parameters
jalf
Your tuple code here could be done with a dictionary using dictionary initializers. (new Dictionary<int, string>() { {1, "US"} {2, "Canada" } }).ToTuple() ?
Paul Stovell
Re: passing around IDs: IDs are actually convenient to pass around, especially when you are going from one tier to another tier. But in this case you have to reverse engineer the code to figure out that the first integer is an ID.
Mr. Shiny and New
A: 

Many things have already been mentioned, but I think one should also mention, that there are some programming styles, that differ from OOP and for those tuples are quite useful.

Functional programming languages like Haskell for example don't have classes at all.

Georg
+3  A: 

The code example has a few different smells:

  • Reinventing the wheel

There is already a tuple available in the framework; the KeyValuePair structure. This is used by the Dictionary class to store pairs, but you can use it anywhere it fits. (Not saying that it fits in this case...)

  • Making a square wheel

If you have a list of pairs it's better to use the KeyValuePair structure than a class with the same purpose, as it results in less memory allocations.

  • Hiding the intention

A class with properties clearly shows what the values mean, while a Pair<int,int> class doesn't tell you anything about what the values represent (only that they are probably related somehow). To make the code reasonably self explanatory with a list like that you would have to give the list a very desciptive name, like productIdAndQuantityPairs...

Guffa
+3  A: 

It's code reuse. Rather than writing Yet Another Class With Exactly The Same Structure As The Last 5 Tuple-Like Classes We Made, you make... a Tuple class, and use that whenever you need a tuple.

If the only significance of the class is "to store a pair of values", then I'd say using tuples is an obvious idea. I'd say it was a code smell (as much as I hate the term) if you started implementing multiple identical classes just so that you could rename the two members.

jalf
A: 

also do not ignore the overhead charged in creation of a full fledge class vs a tuple which is translated to roughly a C struct, the cost is very much non zero especially if you are creating many such objects.

Uh, what language? In C++, std::pair<int,int> should compile to the same runtime data structure as class Foo { int x; int y; } with the one potential exception of the virtual table pointer, if your compiler does not optimize out vtables on classes which do not use them.
Not Sure
A: 

If you're doing a Schwartzian transform to sort by a particular key (which is expensive to compute repeatedly), or something like that, a class seems to be a bit overkill:

val transformed = data map {x => (x.expensiveOperation, x)}
val sortedTransformed = transformed sort {(x, y) => x._1 < y._1}
val sorted = sortedTransformed map {case (_, x) => x}

Having a class DataAndKey or whatever seems a bit superfluous here.

Your example was not a good example of a tuple, though, I agree.

Andy Morris
+1  A: 

Scala has tuple-valued types, all the way from 2-tuples (Pairs) up to tuples with 20+ elements. See First Steps to Scala (step 9):

val pair = (99, "Luftballons")
println(pair._1)
println(pair._2)

Tuples are useful if you need to bundle together values for some relatively ad hoc purpose. For example, if you have a function that needs to return two fairly unrelated objects, rather than create a new class to hold the two objects, you return a Pair from the function.

I completely agree with other posters that tuples can be misused. If a tuple has any sort of semantics important to your application, you should use a proper class instead.

Jim Ferrans