Scala: Constructor taking either Seq or varargs

13,741

Solution 1

A method taking varargs is also always taking a sequence, so there is no need to define an auxiliary constructor or overloaded method.

Given

class Api(api_url: String, params: (String, String)*)

you can call it like this

new Api("url", ("a", "b"), ("c", "d"))

or

val seq = Seq(("a", "b"), ("c", "d"))
new Api("url", seq:_*)

Also, in your question, you are calling method seq on the params parameter. This probably does not do what you intended. seq is used to ensure that operations on the resulting collection are executed sequentially instead of in parallel. The method was introduced with the parallel collections in version 2.9.0 of Scala.

What you probably wanted to use was toSeq, which returns the collection it is used on converted to a Seq (or itself if it is already a Seq). But as varargs parameters are already typed as Seq, that is a no-op anyway.

Solution 2

No: actually, Any* is actually almost identical to Seq[Any], not to Array[Any].

To disambiguate between the two, you can use the technique to add a dummy implicit parameter to make the signature different:

class Api(api_url: String, params: Seq[(String, String)]) {
  def this(api_url: String, params: (String, String)*)(implicit d: DummyImplicit) =
    this(api_url, params)
}

Solution 3

I suppose that you would like to make the method calls prettier and so explicit calling with _* is not an option. In that case you may solve the problem with method overloading.

class Api(api_url: String, params: Seq[(String, String)]) {
  def this(api_url: String, param : (String, String), params: (String, String)*)
    = this(api_url, param +: params)
  def this(api_url: String)
    = this(api_url, Seq())
}
Share:
13,741
wen
Author by

wen

Updated on June 03, 2022

Comments

  • wen
    wen almost 2 years

    I am guessing that, for compatibility reasons, the type of vararg parameters Any* is Array[Any] - please correct this if I'm wrong. However, this does not explain the following error:

    class Api(api_url: String, params: Seq[(String, String)]) {
      def this(api_url: String, params: (String, String)*)
        = this(api_url, params.seq)
    }
    

    This code does not compile, but gives the warning:

    double definition: constructor Api:(api_url: String, params: (String, String)*)Api and constructor Api:(api_url: String, params: Seq[(String, String)])Api at line 13 have same type after erasure: (api_url: java.lang.String, params: Seq)Api

    So how do I define a constructor taking either varargs or a sequence?

  • wen
    wen over 12 years
    ...isn't this ...really disgusting?
  • Jean-Philippe Pellet
    Jean-Philippe Pellet over 12 years
    Well, feel free to provide a less “disgusting” solution!
  • wen
    wen over 12 years
    When I figure one out, I will. For now, I'll just scratch the varargs constructor. ^^
  • Derek Wyatt
    Derek Wyatt over 12 years
    Why not keep the varargs constructor and let the caller do the seq:_* thing if he wants?
  • wen
    wen over 12 years
    @Derek: because I didn't know seq:_* existed. :)
  • aaddesso
    aaddesso almost 9 years
    May I point out that the type annotation :_* is what really makes the constructor call using the sequence argument compatible to the varargs constructor. More details: stackoverflow.com/questions/6051302/…
  • Ivan Balashov
    Ivan Balashov almost 8 years
    Designing new interfaces should one prefer vargargs over Seq?
  • Eduardo
    Eduardo over 4 years
    This is the best solution as far as I'm concerned. It allows you to do Api("url"), Api("url", "a" -> "b"), Api("url", Seq("a" -> "b")) without sacrifiycing readability with the :_*