Search code examples
scalaoptional-parameterscallbyname

parameter list ("*") with lazy "by-name" parameters?


I can:

scala> def foo( f: => String) = println(f)
foo: (f: => String)Unit

and I can:

scala> def foo( f: String*) = f.map(println)
foo: (f: String*)Seq[Unit]

but I can't:

scala> def foo( f: =>String* ) = f.map(println)
<console>:1: error: ')' expected but identifier found.
       def foo( f: =>String* ) = f.map(println)
                       ^

nor

scala> def foo( f: (=>String)* ) = f.map(println)
<console>:1: error: no by-name parameter type allowed here
       def foo( f: (=>String)* ) = f.map(println)
                ^

Is there some other way to do what I want? Why isn't this allowed?


Solution

  • Here the => means that the parameter is passed by name. What you have tried isn't allowed simply because =>String is not an actual type (as opposed to say ()=>String) and you cannot make an Array[=>String]. Given that a variadic parameter x: T* is under the hood handled as an array containing all the parameter's values (as in x: Array[T]), this is the reason why not being able to create a Array[=>String] (which does not make sense) also means that a variadic parameter f: (=>String)* is not possible.

    This problem can be worked around using a little wrapper class:

    implicit class ByName[T]( getValue: => T ) extends Proxy {
      def apply(): T = getValue
      def self = apply()
    }
    

    Then change the signature of your method like so:

    def foo( fs: ByName[String]* )
    

    When calling your method with multiple arguments, all the arguments will be implicitly wrapped into a ByName instance, and then you can call apply to get the actal value:

    def foo( fs: ByName[String]* ) = fs foreach { f => println( f() ) }
    

    Given that ByName extends Proxy for simple things such as calling toString or testing equality you don't even have to call apply. Thus you can simply do:

    def foo( fs: ByName[String]* ) = f foreach println