case class Person(firstName: String, lastName: String)
vs
type Person = (String, String) //firstName, lastName
case classes are obviously more readable. However, I have seen people use the latter. When would it make sense to use a tuple instead?
As a follow up
Are there performance difference? In some langauges using a Tuple2 would be just like using two primitives, while creating a class would have an overhead. But is Scala is seems like Tuple is implemented as class. How about a situation where I want to zip two sequances, but would prefer the output to be a case class for all the reasons mentioned bellow.
For example
`case class UserScores(userId: Long, score: Double)`
users.zip(scores).map{(id ,score) => UserScores(id,score)}
The above example requires an extra iteration over the collection, plus extra object creation. One could use a view instead
users.zip(scores).view.map{(id ,score) => UserScores(id,score)}.force
But I am not quite sure it will have the intended effect
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:
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.
IMHO, as a general rule:
But:
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:
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]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With