The author of that blog post seems to be ignoring that most languages with generic types and subtypings have some way of expressing variance in their generic parameters. They write:
So here's the issue: if we have a function that accepts arguments of type Tuple, and we want to pass it a tuple of arguments of type Tuple, we can only do this when A = X, and B = Y. If X is a subtype of A or Y is a subtype of B, we will get a type error. Subsumption is broken. Suddenly, you can't pass a String as an argument to a function that accepts Objects.
But actually, a tuple can be defined as something like:
case class Tuple2[+T1, +T2]
in Scala and other similar languages (C# or Java) even if it isn't always defined so, which gives it the proper variance.
Even though this is not a problem there are some tradeoffs between the two:
Pro multiple arguments:
- Makes optional, named and variadic arguments quite straightforward.
- Slight possible performance benefit (no tuples have to be created).
- Can have as many arguments as you want, without the language having to provide up to
Tuple20 say.
Pro tuples:
- Tuples are themselves values that can be passed around and transformed.
- Requires the language to support a nice tuple syntax to be usable.
As David Arno has pointed out, both tuples as single arguments and multiple arguments have the same disadvantage against fully curried arguments: they cannot be partially applied.
Edit:
So I might have misread the question, though I think it's vague on this point. For clarity, there are three ways a function might "take multiple arguments". The first is by actually taking multiple arguments, which is what the majority of programming languages do. For example, in Scala,
def concatenateMulti(left: String, right: String): String = left + right
the second is to take a single argument that is a tuple or record, e.g.
def concatenateTuple(args: Tuple2[String, String]): String = args._1 + args._2
and the third is to take a function that returns a partially applied function
def concatenateSingle(left: String): (String => String) = (right: String) => left + right
I assumed the author was asking after the first versus the second whereas it seems they were more interested in the second versus the third