views:

169

answers:

3

I consider refactoring few method signatures that currently take parameter of type List or Set of concrete classes --List[Foo]-- to use repeated parameters instead: Foo*.

Update: Following reasoning is flawed, move along...
This would allow me to use the same method name and overload it based on the parameter type. This was not possible using List or Set, because List[Foo] and List[Bar] have same type after erasure: List[Object].

In my case the refactored methods work fine with scala.Seq[Foo] that results from the repeated parameter. I would have to change all the invocations and add a sequence argument type annotation to all collection parameters: baz.doStuffWith(foos:_*).

Given that switching from collection parameter to repeated parameter is semantically equivalent, does this change have some performance impact that I should be aware of?

Is the answer same for scala 2.7._ and 2.8?

A: 

In the simplest terms, all the arguments that correspond to a repeated formal parameters, regardless of their origin, must be copied to a sequential collection of some sort for presentation to the method. The details of exactly what kind of sequence is used vary with Scala version and possibly with the source of the arguments. But regardless of those details, it is an O(n) operation, though the cost per item is pretty low. There will be at least one and sometimes more instance allocations for the sequence itself.

Randall Schulz

Randall Schulz
This is incorrect. In 2.8 at least, if you already have a Seq, if you pass it as varargs using :_* then the collection is not copied, so it's O(1), not O(n).
Seth Tisue
+4  A: 

When Scala is calling a Scala varargs method, the method will receive an object that extends Seq. When the call is made with : _*, the object will be passed as is*, without copying. Here are examples of this:

scala> object T {
     |   class X(val self: List[Int]) extends SeqProxy[Int]  {
     |     private val serial = X.newSerial
     |     override def toString = serial.toString+":"+super.toString
     |   }
     |   object X {
     |     def apply(l: List[Int]) = new X(l)
     |     private var serial = 0
     |     def newSerial = {
     |       serial += 1
     |       serial
     |     }
     |   }
     | }
defined module T

scala> new T.X(List(1,2,3))
res0: T.X = 1:List(1, 2, 3)

scala> new T.X(List(1,2,3))
res1: T.X = 2:List(1, 2, 3)

scala> def f(xs: Int*) = xs.toString
f: (Int*)String

scala> f(res0: _*)
res3: String = 1:List(1, 2, 3)

scala> f(res1: _*)
res4: String = 2:List(1, 2, 3)

scala> def f(xs: Int*): Seq[Int] = xs
f: (Int*)Seq[Int]

scala> def f(xs: Int*) = xs match {
     |   case ys: List[_] => println("List")
     |   case _ => println("Something else")
     | }
f: (Int*)Unit

scala> f(List(1,2,3): _*)
List

scala> f(res0: _*)
Something else

scala> import scala.collection.mutable.ArrayBuffer
import scala.collection.mutable.ArrayBuffer

scala> def f(xs: Int*) = xs match {
     |   case ys: List[_] => println("List")
     |   case zs: ArrayBuffer[_] => zs.asInstanceOf[ArrayBuffer[Int]] += 4; println("Array Buffer")
     |   case _ => println("Something else")
     | }
f: (Int*)Unit

scala> val ab = new ArrayBuffer[Int]()
ab: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer()

scala> ab + 1
res11: scala.collection.mutable.Buffer[Int] = ArrayBuffer(1)

scala> ab + 2
res12: scala.collection.mutable.Buffer[Int] = ArrayBuffer(1, 2)

scala> ab + 3
res13: scala.collection.mutable.Buffer[Int] = ArrayBuffer(1, 2, 3)

scala> f(ab: _*)
Array Buffer

scala> ab
res15: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 2, 3, 4)

Note

  • An Array is passed as a WrappedArray. There's no copying of elements involved, however, and changes to the WrappedArray will be reflected in the Array.
Daniel
"When the call is made with : _*, the object will be passed as is "It is not true when an Array[T] be passed.scala> f(Array(1,2,3,4) :_*)res1: String = WrappedArray(1, 2, 3, 4)
Eastsun
@Eastsun Ah, yes, the `Array`. But changes made to the `WrappedArray` will be reflected in the `Array` as well, so there's still no copying of elements.
Daniel
+3  A: 

Your reason for replacing List[T] with T* is flawed: Scala will not allow overloading like

class Foo
{
   def t1(x : Int*) = println("Ints")
   def t1(x : Strings*) = println("Strings")
}

This will result in the same compiler error as using List[Int]/List[String] here.

Although a bit clumsy you could use

class Foo
{
   def t1(x0 : Int,x : Int*) = println("Ints")
   def t1(x0 : String,x : Strings*) = println("Strings")
}

but that requires special treatment of the first parameter versus the rest.

Gr. Silvio

Silvio Bierman