3

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.

1
  • I think there's a way to do this using, implicits, let me see... Commented Nov 30, 2016 at 3:20

2 Answers 2

4

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.
Sign up to request clarification or add additional context in comments.

1 Comment

Note: I refactored tryCatch a bit (1) instead of using .size or indexes, which are more error-prone and less idiomatic in scala, I use a standard List recursion; (2) I use the NonFatal extractor so e.g. OOM won't be swallowed (see NonFatal docs); (3) capitalized the C
3

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.

4 Comments

But SIP-24 doesn't allow you to pick and choose which items get evaluated: "all arguments are evaluated each time the parameter is referenced in the called method". So f(0) won't just print "hi". It will print both "hi" and "hello".
@SethTisue Interesting observation. That that makes such features not very useful.
@SethTisue Updated my question with an example where the behavior of SIP-24 would be undesirable. I cannot think of one example where that intended behavior will actually be desirable. Anyone care to enlighten me?
I can't, but I've added a link to this Q&A from the SIP.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.