Jus12
Jus12

Reputation: 18024

Can we have an array of by-name-parameter functions?

In Scala we have a by-name-parameters where we can write

def foo[T](f: => T):T = {
   f // invokes f    
}
// use as:
foo(println("hello"))

I now want to do the same with an array of methods, that is I want to use them as:

def foo[T](f:Array[ => T]):T = {     // does not work
   f(0) // invokes f(0)              // does not work
}
foo(println("hi"), println("hello")) // does not work

Is there any way to do what I want? The best I have come up with is:

def foo[T](f:() => T *):T = {
   f(0)() // invokes f(0)    
}
// use as:
foo(() => println("hi"), () => println("hello"))

or

def foo[T](f:Array[() => T]):T = { 
   f(0)() // invokes f(0)    
}
// use as: 
foo(Array(() => println("hi"), () => println("hello")))

EDIT: The proposed SIP-24 is not very useful as pointed out by Seth Tisue in a comment to this answer.

An example where this will be problematic is the following code of a utility function trycatch:

type unitToT[T] = ()=>T
def trycatch[T](list:unitToT[T] *):T = list.size match {
  case i if i > 1 => 
    try list.head()
    catch { case t:Any => trycatch(list.tail: _*) }
  case 1 => list(0)()
  case _ => throw new Exception("call list must be non-empty")
}

Here trycatch takes a list of methods of type ()=>T and applies each element successively until it succeeds or the end is reached.

Now suppose I have two methods:

def getYahooRate(currencyA:String, currencyB:String):Double = ???

and

def getGoogleRate(currencyA:String, currencyB:String):Double = ???

that convert one unit of currencyA to currencyB and output Double.

I use trycatch as:

val usdEuroRate = trycatch(() => getYahooRate("USD", "EUR"), 
                           () => getGoogleRate("USD", "EUR"))

I would have preferred:

val usdEuroRate = trycatch(getYahooRate("USD", "EUR"), 
                           getGoogleRate("USD", "EUR")) // does not work

In the example above, I would like getGoogleRate("USD", "EUR") to be invoked only if getYahooRate("USD", "EUR") throws an exception. This is not the intended behavior of SIP-24.

Upvotes: 3

Views: 261

Answers (2)

nafg
nafg

Reputation: 2534

Here is a solution, although with a few restrictions compared to direct call-by-name:

import scala.util.control.NonFatal


object Main extends App {
  implicit class Attempt[+A](f: => A) {
    def apply(): A = f
  }

  def tryCatch[T](attempts: Attempt[T]*): T = attempts.toList match {
    case a :: b :: rest =>
      try a()
      catch {
        case NonFatal(e) =>
          tryCatch(b :: rest: _*)
      }
    case a :: Nil =>
      a()
    case Nil => throw new Exception("call list must be non-empty")
  }

  def a = println("Hi")
  def b: Unit = sys.error("one")
  def c = println("bye")
  tryCatch(a, b, c)

  def d: Int = sys.error("two")
  def e = { println("here"); 45 }
  def f = println("not here")

  val result = tryCatch(d, e, f)

  println("Result is " + result)
}

The restrictions are:

  1. Using a block as an argument won't work; only the last expression of the block will be wrapped in an Attempt.
  2. If the expression is of type Nothing (e.g., if b and d weren't annotated), the conversion to Attempt is not inserted since Nothing is a subtype of every type, including Attempt. Presumably the same would apply for an expression of type Null.

Upvotes: 4

Sean Vieira
Sean Vieira

Reputation: 159865

As of Scala 2.11.7, the answer is no. However, there is SIP-24, so in some future version your f: => T* version may be possible.

Upvotes: 3

Related Questions