11

How to pass by-name repeated parameters in Scala?

The following code fails to work:

scala> def foo(s: (=> String)*) = {
<console>:1: error: no by-name parameter type allowed here
       def foo(s: (=> String)*) = {
                   ^

Is there any other way I could pass a variable number of by name parameters to the method?

Green Hyena
  • 531
  • 5
  • 11

6 Answers6

11

Repeated by-name parameters are not currently supported.

Randall Schulz
  • 26,420
  • 4
  • 61
  • 81
5

This isn't very pretty but it allows you to pass byname parameters varargs style

def printAndReturn(s: String) = {
  println(s)
  s
}

def foo(s: (Unit => String)*) {
  println("\nIn foo")
  s foreach {_()}  // Or whatever you want ...
}

foo()

foo((Unit) => printAndReturn("f1"),
    (Unit) => printAndReturn("f2"))

This produces

In foo

In foo f1 f2

Don Mackenzie
  • 7,953
  • 7
  • 31
  • 32
  • 4
    It's certainly not pleasant to look at nor to write. And I continue to encourage people to distinguish by-name parameters (that are *implemented* using a thunk but are otherwise logically distinct) and function values used as arguments. Likewise for methods and functions. – Randall Schulz Apr 26 '10 at 14:00
  • 2
    I think Randall's comments are fair, the above could be perhaps improved with a helper function to wrap / pre-process the arguments, for example: def deferred(block: => String) = () => block with foo altered to: def foo(s: (() => String)*) giving, for example: foo(deferred {val a = "apples"; "Green "+apples}, deferred {"Pears"}) – Don Mackenzie May 02 '10 at 22:54
2

If struggling for nice syntax the goal can be achieved using implicits.

implicit def arg2func(arg: ⇒ Byname): (() => Byname) = () ⇒ arg

def foo(params: (() ⇒ Byname)*): Unit = {
    println("foo: not yet evaluated.")
    params.foreach(_())
}

def boo(params: Byname*): Unit = {
    println("boo: already evaluated")
}

foo(Byname(0), Byname(1), Byname(2))
println()
boo(Byname(3), Byname(4), Byname(5))

case class Byname(n: Int) {
    println(n)
}

That prints:

foo: not yet evaluated.

0 1 2

3 4 5

boo: already evaluated

Community
  • 1
  • 1
Markus Marvell
  • 1,916
  • 1
  • 21
  • 26
0

You can write () => String instead of Unit (it's the same thing anyway)

Vlad Patryshev
  • 1,379
  • 1
  • 10
  • 16
  • It really isn't "the same thing." The semantics and the syntax are different both for calling and for using them in the method. – Randall Schulz Mar 31 '13 at 15:28
0

Thanks Randall Schulz on the good one-line answer.

I was looking for this possibility in order to make an INVARIANT tool that would run multiple asserts together. The solution I then came up with is to simply have 1..5 apply methods, since the number of varargs needed here is finite.

object INVARIANT {
  def apply = {}
  def apply( conds: => Boolean * ) = {    // DOES NOT COMPILE
    conds.foreach( assert(_) )
  }
}

object TestX extends App {

  class A {
    println("A body")
    INVARIANT( true )
  }

  class B extends A {
    println("B body")
    INVARIANT( true, false )  
  }

  new B
}

I posted this to show what I believe is a valid use case for varargs on 'by-name' variables. If there is a better name, please leave a comment. Thanks.

akauppi
  • 17,018
  • 15
  • 95
  • 120
0

It works in Scala 3:

scala> def f(i: => Int*): Int = i.sum
def f(i: => Int*): Int

scala> f(1, 2, 3)
val res0: Int = 6
  • However, all elements seem to be forced the moment the collection is referenced: ``` def fs(x: =>String*): String = x.head fs({ println("1"); "1"}, {println("2"); "2"}) // gives: 1 2 res1: String = "1" ``` – adamw May 02 '23 at 08:03