views:

265

answers:

2

What is wrong is the following method?

def someMethod(funcs: => Option[String]*) = {
 ...
}
+3  A: 

Evidently repeated arguments are not available for by-name parameters.

Randall Schulz
But, just for kicks, try this: `def f(oi: Option[Int]*) = oi` in the REPL. Interesting, eh?
Rex Kerr
@Rex_Kerr: I guess. What interesting thing are you referring to?
Randall Schulz
@Randall: You can return Option[Int]* from a method, and you can use (f _) to make it a function. But try to find syntax that allows you to represent the type. So it's unclear to me whether repeated arguments are not available for by-name parameters, or whether the syntax doesn't allow you to express the type that you want (or both).
Rex Kerr
+3  A: 

That actually "works" under 2.7.7 if you add parens:

scala> def someMethod(funcs: => (Option[String]*)) = funcs
someMethod: (=> Option[String]*)Option[String]*

except it doesn't actually work at runtime:

scala> someMethod(Some("Fish"),None)
    scala.MatchError: Some(Fish)
at scala.runtime.ScalaRunTime$.boxArray(ScalaRunTime.scala:136)
at .someMethod(<console>:4)
at .<init>(<console>:6)
at .<clinit>(<console>) ...

In 2.8 it refuses to let you specify X* as the output of any function or by-name parameter, even though you can specify it as an input (this is r21230, post-Beta 1):

scala> var f: (Option[Int]*) => Int = _
f: (Option[Int]*) => Int = null

scala> var f: (Option[Int]*) => (Option[Int]*) = _
<console>:1: error: no * parameter type allowed here
       var f: (Option[Int]*) => (Option[Int]*) = _

But if you try to convert from a method, it works:

scala> def m(oi: Option[Int]*) = oi
m: (oi: Option[Int]*)Option[Int]*

scala> var f = (m _)
f: (Option[Int]*) => Option[Int]* = <function1>

scala> f(Some(1),None)
res0: Option[Int]* = WrappedArray(Some(1), None)

So it's not entirely consistent.

In any case, you can possibly achieve what you want by passing in an Array and then sending that array to something that takes repeated arguments:

scala> def aMethod(os: Option[String]*) { os.foreach(println) }
aMethod: (os: Option[String]*)Unit

scala> def someMethod(funcs: => Array[Option[String]]) { aMethod(funcs:_*) }
someMethod: (funcs: => Array[Option[String]])Unit

scala> someMethod(Array(Some("Hello"),Some("there"),None))
Some(Hello)
Some(there)
None

If you really want to (easily) pass a bunch of lazily evaluated arguments, then you need a little bit of infrastructure that as far as I know doesn't nicely exist in the library (this is code for 2.8; view it as inspiration for a similar strategy in 2.7):

class Lazy[+T](t: () => T, lt: Lazy[T]) {
  val params: List[() => T] = (if (lt eq null) Nil else t :: lt.params)
  def ~[S >: T](s: => S) = new Lazy[S](s _,this)
}
object Lz extends Lazy[Nothing](null,null) {
  implicit def lazy2params[T : Manifest](lz: Lazy[T]) = lz.params.reverse.toArray
}

Now you can easily create a bunch of parameters that are lazily evaluated:

scala> import Lz._  // To get implicit def
import Lz._

scala> def lazyAdder(ff: Array[()=>Int]) = {
     |   println("I'm adding now!");
     |   (0 /: ff){(n,f) => n+f()}
     | }
lazyAdder: (ff: Array[() => Int])Int

scala> def yelp = { println("You evaluated me!"); 5 }
yelp: Int

scala> val a = 3
a: Int = 3

scala> var b = 7
b: Int = 7

scala> lazyAdder( Lz ~ yelp ~ (a+b) )
I'm adding now!
You evaluated me!
res0: Int = 15

scala> val plist = Lz ~ yelp ~ (a+b)
plist: Lazy[Int] = Lazy@1ee1775

scala> b = 1
b: Int = 1

scala> lazyAdder(plist)
I'm adding now!
You evaluated me!
res1: Int = 9
Rex Kerr