Right now you can pass a Seq when the repeated parameter type is erased to Array and vice-versa, this works because a conversion gets inserted. The same could be done here.
Either that, or we could insist that the right kind of data is passed, potentially requiring an explicit conversion by the user.
That would make cross compilation a lot harder.
What I proposed was to only give new semantics to the hypothetical new syntax (def foo(...x: Int)
), the old Int*
syntax would behave as it does now, so cross-compilation wouldnât be affected.
But wouldnât the idea be to deprecate and remove the other syntax eventually? I think having both syntaxes permanently would be worse than the status quo.
Sure, of course.
Yes, itâll need to be removed eventually, but only once cross-compilation with a version that does not support the new syntax is no longer necessary.
I really do not see the problem with the 3 symbol combination being not consistent with the declaration, knowing that it makes perfect sense as a type ascription.
However, why donât we simply infer automatically the vararg type ? It would remove the problematic symbol while still allowing it when inference does not work.
def f(xs: Int*) = List(xs: _*)
f(Seq(1, 2): _*) // List(1, 2)
f(Seq(1, 2))
^^^^^^^^^
Found: Seq[Int]
Required: Int
Edit : and maybe this one should work too:
def f(xs: Int*) = List(xs: Int*)
^
')' expected, but identifier found
Edit2: and while weâre at it, maybe we should enable this as well:
def f(xs: Seq[Int]) = List(xs: _*)
f(Seq(1, 2)) // List(1, 2)
f(1, 2)
^^^^
Found: (Int, Int)
Required: Seq[Int]
Edit3 : âŚand remove Int*
notation altogether (or make it an alias for Seq[Int]
)
This tends to get a bit nasty around the edge-cases.
def f[A](xs: Seq[A]) = ???
f(Seq(1, 2)) // desugars to f(Seq(1, 2)`: _*) or to f(Seq(Seq(1, 2)): _*)?
You can make some choices that makes this always work, but then parametric works different than concrete, so is irregular there.
The tension between convenience and regularity doesnât have an obvious resolution.
I think if we consider doing something special only if thereâs more than one parameter, we should be ok, no ?
Java has that. It can automatically spread an array as varargs. Iâve stumbled upon that a few times (when I was Java programmer) and I didnât really like it. I wished there was something explicit so that I have a control whether varargs are spread or wrapped instead of wondering what will happen here and there.
Then the Scala behavior will (unpleasantly) surprise Java programmers. That makes it even more relevant to improve it. However, in Java the converse mechanism does not work, so let us forget about my Edits 2 and 3 above.
public class Test {
public static void main(final String args[]) {
new Test().fn("hello", "world");
}
void fn(final String as[]) {
for (final String a : as) {
System.out.println(a);
}
}
}
error: method fn in class Test cannot be applied to given types;
new Test().fn("hello", "world");
^
required: String[]
found: String,String
reason: actual and formal argument lists differ in length
Java (like Scala) needs a way of passing varargs from one method to another and such adaptation is one way to do it. Scala doesnât need such implicit conversions as you can explicitly spread a Seq over varargs using : _*
(Java doesnât have analogous syntax). If we want changes then I would strongly prefer ...
notation over automatic Java-like adaptation. Regular syntax has probably higher priority in Scala than being Java-like.
I like the idea to view it as implicit conversion. Scala developers are used to it, so it wonât be shocking.
import scala.language.implicitConversions
case class *[A](value: A*)
given [A] as Conversion[Seq[A], *[A]] = *(_: _*)
def f[A](xs: *[A]) = xs.value
f(Seq(1, 2)) // List(1, 2)
Just want to highlight that @joshlemer introduced another idea, possibly orthogonal to the main syntax choice debate, of allowing multiple spread operators to be used in a single context.
This looks appealing, as it would help in a pretty common scenario: building up a collection from a mix of individual items and sub-collections. That can be a bit awkward to do in Scala ATM.
Heres a sample use case:
val CatsDeps = Seq(
"org.typelevel" %% "cats-core" % "2.6.0",
"org.typelevel" %% "alleycats-core" % "2.6.0",
)
//etc
libraryDependencies ++= Seq(
CatsDeps:_*,
CatsEffectDep,
FS2Dep,
MunitDeps:_*
),
I wonât be the one to push this through.
(There are a couple of recent posts about flexible varargs.)