In scala can I pass repeated parameters to other methods? - scala

Here is something I can do in java, take the results of a repeated parameter and pass it to another method:
public void foo(String ... args){bar(args);}
public void bar(String ... args){System.out.println("count="+args.length);}
In scala it would look like this:
def foo(args:String*) = bar(args)
def bar(args:String*) = println("count="+args.length)
But this won't compile, the bar signature expects a series of individual strings, and the args passed in is some non-string structure.
For now I'm just passing around arrays. It would be very nice to use starred parameters. Is there some way to do it?

Java makes an assumption that you want to automatically convert the Array args to varargs, but this can be problematic with methods that accept varargs of type Object.
Scala requires that you be explicit, by ascribing the argument with : _*.
scala> def bar(args:String*) = println("count="+args.length)
bar: (args: String*)Unit
scala> def foo(args:String*) = bar(args: _*)
foo: (args: String*)Unit
scala> foo("1", "2")
count=2
You can use : _* on any subtype of Seq, or on anything implicitly convertable to a Seq, notably Array.
scala> def foo(is: Int*) = is.length
foo: (is: Int*)Int
scala> foo(1, 2, 3)
res0: Int = 3
scala> foo(Seq(1, 2, 3): _*)
res1: Int = 3
scala> foo(List(1, 2, 3): _*)
res2: Int = 3
scala> foo(Array(1, 2, 3): _*)
res3: Int = 3
scala> import collection.JavaConversions._
import collection.JavaConversions._
scala> foo(java.util.Arrays.asList(1, 2, 3): _*)
res6: Int = 3
It is explained well with examples in the Language Reference, in section 4.6.2.
Note that these examples are made with Scala 2.8.0.RC2.

Related

Represent T* as concrete type

(T,T)* resolves to Seq[(T,T)] after erasure, but how to represent (T,T)* itself as a type?
The reason I ask is there's an API I'm using that defines a:
def foo(bar: (String,String)*) = ...
but fails when I try to pass in a Seq[(String,String)].
My pull request to add in:
def foo(bar: Seq[(String,String)]) = ...
blows up due to the 2 methods having the same type after erasure.
Are star projections able to be represented as a concrete type?
You can pass the Seq if you follow it with :_* like this:
val s:Seq[(String, String)] = Seq( ("a", "b"), ("c", "d"), ... )
foo(s:_*)
So you shouldn't need both signatures.
To disambiguate the erased signatures:
scala> class X { def f(is: Int*) = is.sum }
defined class X
scala> class Y extends X { def f(is: Seq[Int])(implicit d: DummyImplicit): Int = f(is: _*) }
defined class Y
scala> new Y().f(1 to 10)
res3: Int = 55
or this is better, the signatures in collections always look like this to mean "two or more":
scala> class X {
| def f(i: Int): Int = i
| def f(is: Seq[Int]): Int = is.sum
| def f(i: Int, j: Int, rest: Int *): Int = i + j + rest.sum
| }
defined class X
scala> new X().f(3)
res9: Int = 3
scala> new X().f(3,4)
res10: Int = 7
scala> new X().f(3,4,5)
res11: Int = 12
scala> new X().f(1 to 10)
res12: Int = 55
You can't refer to a repeated parameter type, just as you can't refer to a by-name parameter type as such. So you can't convert to it. However, you can detect it reflectively, by name:
scala> import reflect.runtime.universe._
import reflect.runtime.universe._
scala> typeOf[X].member(TermName("f")).asMethod.paramss.flatten.head.asTerm.typeSignature.typeSymbol.name
warning: there were 1 deprecation warning(s); re-run with -deprecation for details
res4: reflect.runtime.universe.Symbol#NameType = <repeated>
There is internal API, definitions.isRepeated(sym), if you want to cast for it.

Passing a individual arguments AND a Seq to a var-arg function

I know it's possible to pass individual arguments to a vararg function and it's possible to pass a seq using :_* but is it possible to pass both?
for example:
scala> object X { def y(s: String*) = println(s) }
defined module X
scala> X.y("a", "b", "c")
WrappedArray(a, b, c)
scala> X.y(Seq("a", "b", "c"):_*)
List(a, b, c)
scala> X.y("a", Seq("b", "c"):_*)
<console>:9: error: no `: _*' annotation allowed here
(such annotations are only allowed in arguments to *-parameters)
X.y("a", Seq("b", "c"):_*)
^
Edit: In Scala 2.10 (in case that matters)
Hacky but this should work well:
X.y(Seq("a") ++ Seq("b", "c"):_*)
If you look around in the Scala standard library you'll find this sort of pattern in places:
def doIt(arg: Thing)
def doIt(arg1: Thing, arg2: Thing, moreArgs: Thing*)
You can see this, e.g., in Set.+(...). It allows you to have any number of arguments without ambiguity in the overloads.
Addendum
Proof of concept:
scala> class DI { def doIt(i: Int) = 1; def doIt(i1: Int, i2: Int, iMore: Int*) = 2 + iMore.length }
defined class DI
scala> val di1 = new DI
di1: DI = DI#16ac0be1
scala> di1.doIt(0)
res1: Int = 1
scala> di1.doIt(1, 2)
res2: Int = 2
scala> di1.doIt(1, 2, 3)
res3: Int = 3
scala> di1.doIt(1, 2, List(3, 4, 5): _*)
res4: Int = 5

What is the type of a variable-length argument list in Scala?

Suppose that I declare a function as follows:
def test(args: String*) = args mkString
What is the type of args?
This is called a variable number of arguments or in short varargs. It's static type is Seq[T] where T represents T*. Because Seq[T] is an interface it can't be used as an implementation, which is in this case scala.collection.mutable.WrappedArray[T]. To find out such things it can be useful to use the REPL:
// static type
scala> def test(args: String*) = args
test: (args: String*)Seq[String]
// runtime type
scala> def test(args: String*) = args.getClass.getName
test: (args: String*)String
scala> test("")
res2: String = scala.collection.mutable.WrappedArray$ofRef
Varargs are often used in combination with the _* symbol, which is a hint to the compiler to pass the elements of a Seq[T] to a function instead of the sequence itself:
scala> def test[T](seq: T*) = seq
test: [T](seq: T*)Seq[T]
// result contains the sequence
scala> test(Seq(1,2,3))
res3: Seq[Seq[Int]] = WrappedArray(List(1, 2, 3))
// result contains elements of the sequence
scala> test(Seq(1,2,3): _*)
res4: Seq[Int] = List(1, 2, 3)

Can scala splat be used for anything that isn't a varargs?

given e.g:
scala> def pipes(strings:String*) = strings.toList.mkString("|")
which I can call normally:
scala> pipes("foo", "bar")
res1: String = foo|bar
or with a splat:
scala> val args = List("a","b","c")
scala> pipes(args:_*)
res2: String = a|b|c
But can I use a splat to provide arguments for anything but a varargs parameter? e.g I would like to do something like:
scala> def pipeItAfterIncrementing(i:Int, s:String) = (i + 1) + "|" + s
scala> val args:Tuple2[Int, String] = (1, "two")
scala> pipeItAfterIncrementing(args:_*)
That doesn't work, but is there any way to achieve the same effect of providing multiple arguments from a single object, whether it be a tuple or something else? Is there any reason this couldn't be implemented for tuples, given that both their length and types are known at compile-time?
You can use Function.tupled to do exactly this: turn a function that takes n arguments into a function that takes a single tuple argument of arity n. As can be expected, Function.untupled does the reverse job.
The special type ascription : _* is only applicable for repeated parameter (a.k.a. varargs).
scala> def pipeItAfterIncrementing(i:Int, s:String) = (i + 1) + "|" + s
pipeItAfterIncrementing: (i: Int,s: String)java.lang.String
scala> def tupledPipeItAfterIncrementing = Function.tupled(pipeItAfterIncrementing _)
tupledPipeItAfterIncrementing: ((Int, String)) => java.lang.String
scala> val args:Tuple2[Int, String] = (1, "two")
args: (Int, String) = (1,two)
scala> tupledPipeItAfterIncrementing(args)
res0: java.lang.String = 2|two
Well kind of ...
scala> def pipeItAfterIncrementing(i:Int, s:String) = (i + 1) + "|" + s
scala> val args:Tuple2[Int, String] = (1, "two")
scala> (pipeItAfterIncrementing _).tupled(args)
will give you the desired 2|two.

How do I easily convert from one collection type to another during a filter, map, flatMap in Scala?

Suppose I have a List[Int], and I want to call toString on each element, and get back the result as a Vector[String].
What are the various ways to do this in Scala? Is there a solution with a minimal amount of explicit typing? — i.e., I want to specify that I want a Vector rather than a List, but I'd like the String argument to be inferred from the filter function.
Or should I explicitly pass a CanBuildFrom instance? Where do I get these from — for Seqs, Sets and Maps?
Using breakOut
Use breakOut as the CanBuildFrom and let the typer know what you want your result type to be (unfortunately you need to specify String here)
scala> import collection.breakOut
import collection.breakOut
scala> List(1, 2, 3)
res0: List[Int] = List(1, 2, 3)
scala> res0.map(_.toString)(breakOut) : Vector[String]
res2: Vector[String] = Vector(1, 2, 3)
Using to[Collection] (starting with Scala 2.10.0)
Scala 2.10.0 introduced an easy way to convert a collection to another collection:
scala> List(1, 2, 3).map(_.toString).to[Vector]
res0: Vector[String] = Vector(1, 2, 3)
Using toIndexedSeq
Alternatively ask for an IndexedSeq explicitly:
scala> res0.map(_.toString).toIndexedSeq
res4: scala.collection.immutable.IndexedSeq[String] = Vector(1, 2, 3)
If you want to do this without creating the intermediate List, then:
scala> res0.view.map(_.toString).toIndexedSeq
res5: scala.collection.immutable.IndexedSeq[String] = Vector(1, 2, 3)
Using Natural Transformations
You could do it (awkwardly, but more generally) using natural transformations
scala> trait Trans[F[_], G[_]] {
| def f2g[A](f : F[A]) : G[A]
| }
defined trait Trans
Now provide a typeclass instance from the List ~> Vector transformation:
scala> implicit val List2Vector = new Trans[List, collection.immutable.Vector] {
| def f2g[A](l : List[A]) : Vector[A] = l.map(identity[A])(collection.breakOut)
| }
List2Vector: java.lang.Object with Trans[List,scala.collection.immutable.Vector] = $anon$1#56329755
Define a wrapper and an implicit conversion:
scala> class Clever[M[_], A](ma : M[A]) { def to[N[_]](implicit t : Trans[M, N]) : N[A] = t.f2g(ma) }
defined class Clever
scala> implicit def ma2clever[M[_], A](ma : M[A]) = new Clever[M, A](ma)
ma2clever: [M[_],A](ma: M[A])Clever[M,A]
Then:
scala> List(1, 2, 3).map(_.toString).to[Vector]
res4: Vector[java.lang.String] = Vector(1, 2, 3)