0

Is it possible to combine variable arguments and default parameters in a Scala method function definition? Specifically, I'm trying to write a patch method with the following signature:

def patch(body: String, contentType: ContentType = ContentType.APPLICATION_JSON, customHeaders: (String, String)*)

I get the error Parameter section with *-parameter cannot have default arguments. So I assume it was a matter of ordering the parameters. However, I'm required to place *-parameters last.

My questions are:

  • Why does *-parameter have to be last? Is it so the compiler can easily parse the arguments?

  • Why can't *-parameter come after the default arguments? I imagine the same argument applies where it's easier for the compiler to parse arguments because default arguments and variable arguments are both optional.

OfLettersAndNumbers
  • 822
  • 1
  • 12
  • 22

1 Answers1

3

Why does *-parameter have to be last?

Technically, even if the *-parameter isn't restricted to be the last, the compiler should be able to figure it out. This SO link offers some insight (albeit unofficial) into possible rationale.

Why can't *-parameter come after the default arguments?

If *-parameter after a default argument were allowed, there would be ambiguity in which argument variables should be assigned the supplied parameters in some cases. For example:

def foo(a: String = "hi", bs: String*) = a + " " + bs.mkString(" ")

foo("hello", "world")  // Should "hello" go to `a` or be a part of `bs`?

Note that technically this restriction could also be lifted, say, by requiring explicit argument variable assignment in cases when ambiguity arises (e.g. foo(a="hello", "world")).

To circumvent the restriction, you can resort to currying (which allows you to have a *-parameter per argument list):

def bar(s: String, i: Int = 1, ts: (String, String)*) =
  ts.map(t => (t._1 + s*i, t._1 + s*i))
// <console>:23: error: a parameter section with a `*'-parameter is not allowed to have default arguments
//        def bar(s: String, i: Int = 1, ts: (String, String)*) = {

def bar(s: String, i: Int = 1)(ts: (String, String)*) =
  ts.map(t => (t._1 + s*i, t._2 + s*i))

bar("!", 2)(("a", "b"), ("c", "d"))
// res1: Seq[(String, String)] = ArrayBuffer((a!!,b!!), (c!!,d!!))
Leo C
  • 22,006
  • 3
  • 26
  • 39
  • "If *-parameter after a default argument were allowed, there would be ambiguity in which argument variables should be assigned the supplied parameters in some cases." – Not really. It is pretty simple to come up with easy-to-understand rules to disambiguate. It is simply a design choice. – Jörg W Mittag Oct 27 '18 at 16:34
  • @Jörg W Mittag, I think you're right. Among other reasons, the design choice could be based on that the decision of "*-parameter must be the last" was made and the fact that default arguments are more practically useful to be placed last. I've added a relevant note in the answer. – Leo C Oct 27 '18 at 20:32