Cost of using repeated parameters
Asked Answered
G

3

5

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?

Gove answered 16/3, 2010 at 3:0 Comment(0)
V
4

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.
Venu answered 16/3, 2010 at 7:40 Comment(2)
"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)Reason
@Reason 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.Venu
S
3

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

Staysail answered 16/3, 2010 at 13:6 Comment(0)
L
0

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

Lysine answered 16/3, 2010 at 3:27 Comment(1)
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).Jammie

© 2022 - 2024 — McMap. All rights reserved.