score:1

Maybe if the order doesn't matter. So unlike in your firstName/lastName example, where there is a distinction.

There is still no information available, what the values are, but in a generic case, there might be no information.

score:4

IMHO, as a general rule:

  • By default, you should prefer case class: it doesn't cost more and it is much clearer
  • Also, case class provides lots of practical functionalities, such as apply, unapply and copy (from comment: tuples also have those)
  • And tuples of length > 3 should be avoided in general

But:

  • For methods that have several outputs, which are typically split right-away. Then use tuple
  • In several algorithms, especially using functional map/flatMap/etc..., you have lot's of intermediate "one-line" results for which you don't need to define a case class.
  • Finally, as stated by andrey-tyukin, if you're coding generic-type algorithms you could prefer tuples. But then, you can actually do nice generic case-class code using shapeless magic

A last comment, concerning type alias: I use those mainly to improve readability when I don't want to create classes. This happens in two cases:

  • the aliased type is a primitive, such as Int or Double, that is used in critical code and I want to avoid boxing them (note that this requires to respect other constraints to be effective)
  • when a single type has many meanings but creating a case class would be over-design. Ex. say several APIs use param: Map[String, Double]. Map is a good type and you don't want to wrap it in a class. Then it may be more readable to have ApiParams in your code where type ApiParams = Map[String, Double]

score:14

You should use tuples when it is the most appropriate level of abstraction.

For example, if you are writing a framework where everything is completely generic, and the most concrete things that you can get are "functions", "cartesian products", "semigroupals", or something like "closed monoidal categories", then you probably will have no other choice but using Tuple. Here is one good example: Semigroupal with method:

 product[A, B](fa: F[A], fb: F[B]): F[(A, B)]

Here, the usage of the tuple type is completely justified, because the interface is sufficiently abstract.

Another example: in the entire Scala collection library, you will never encounter anything as concrete as Person(name: String, surname: String), but instead you will see a huge lot of methods that work on tuples of generic types (A, B).

However, if the most abstract thing in your code is

object Fred {
  val name = "Fred"
  val surname =  "Bloggs"
}

and then you suddenly find out that you also need another similar object for John Doe, it would be much more appropriate to define

case class Person(name: String, surname: String)

and then maybe even to use it like this:

val j = Person(name = "John", surname = "Doe")

Of course, there is great value in accurately modeling the domain. These two goals -- accurately modeling a concrete domain vs. writing generic frameworks that work in as many cases as possible -- are complementary.

To summarize:

  • Are you writing a sufficiently abstract completely generic framework? Use tuples.
  • Are you trying to model some concrete domain as closely as possible? Use appropriately named case classes.

This here:

type Person = (String, String) //firstName, lastName

looks like a mismatch between the domain and the used abstraction. You can see it simply from the fact that the compiler does not prevent you from accidentally swapping the two components. In contrast, if you worked at a different abstraction level with generic A and B types, the compiler would emit an error message if you accidentally swapped (B, A) where an (A, B) was expected.


Related Query

More Query from same tag