Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How could I implement an early return from outside the body of a method in Scala?

Disclaimer: Before someone says it: yes, I know it's bad style and not encouraged. I'm just doing this to play with Scala and try to learn more about how the type inference system works and how to tweak control flow. I don't intend to use this code in practice.


So: suppose I'm in a rather lengthy function, with lots of successive checks at the beginning, which, if they fail, are all supposed to cause the function to return some other value (not throw), and otherwise return the normal value. I cannot use return in the body of a Function. But can I simulate it? A bit like break is simulated in scala.util.control.Breaks?

I have come up with this:

object TestMain {

  case class EarlyReturnThrowable[T](val thrower: EarlyReturn[T], val value: T) extends ControlThrowable
  class EarlyReturn[T] {
    def earlyReturn(value: T): Nothing = throw new EarlyReturnThrowable[T](this, value)
  }

  def withEarlyReturn[U](work: EarlyReturn[U] => U): U = {
    val myThrower = new EarlyReturn[U]
    try work(myThrower)
    catch {
      case EarlyReturnThrowable(`myThrower`, value) => value.asInstanceOf[U]
    }
  }

  def main(args: Array[String]) {
    val g = withEarlyReturn[Int] { block =>
      if (!someCondition)
        block.earlyReturn(4)

      val foo = precomputeSomething
      if (!someOtherCondition(foo))
        block.earlyReturn(5)

      val bar = normalize(foo)
      if (!checkBar(bar))
        block.earlyReturn(6)

      val baz = bazify(bar)
      if (!baz.isOK)
        block.earlyReturn(7)

      // now the actual, interesting part of the computation happens here
      // and I would like to keep it non-nested as it is here
      foo + bar + baz + 42 // just a dummy here, but in practice this is longer
    }
    println(g)
  }
}

My checks here are obviously dummy, but the main point is that I'd like to avoid something like this, where the actually interesting code ends up being way too nested for my taste:

if (!someCondition) 4 else {
  val foo = precomputeSomething
  if (!someOtherCondition(foo)) 5 else {
    val bar = normalize(foo)
    if (!checkBar(bar)) 6 else {
      val baz = bazify(bar)
      if (!baz.isOK) 7 else {
        // actual computation
        foo + bar + baz + 42 
      }
    }
  }
}

My solution works fine here, and I can return early with 4 as return value if I want. Trouble is, I have to explicitly write the type parameter [Int] — which is a bit of a pain. Is there any way I can get around this?

like image 571
Jean-Philippe Pellet Avatar asked Jun 08 '11 15:06

Jean-Philippe Pellet


People also ask

What is the return type of a procedure in Scala?

If you don't specify any return type of a function, default return type is Unit which is equivalent to void in Java. = : In Scala, a user can create function with or without = (equal) operator. If the user uses it, the function will return the desired value.

How do I return multiple values from a function in Scala?

You can return multiple values by using tuple. Function does not return multiple values but you can do this with the help of tuple.

How do I return a string in Scala?

A Scala function will simply return the last expression in the function. You don't need to explicitly type 'return' except in special circumstances (that should generally be avoided.) All branches of if statement must return a String in your case. If you omit the else branch, it is by default an Unit.


1 Answers

It's a bit unrelated to your main question, but I think, a more effective approach (that doesn't require throwing an exception) to implement return would involve continuations:

def earlyReturn[T](ret: T): Any @cpsParam[Any, Any] = shift((k: Any => Any) => ret)
def withEarlyReturn[T](f: => T @cpsParam[T, T]): T = reset(f)
def cpsunit: Unit @cps[Any] = ()

def compute(bool: Boolean) = { 
    val g = withEarlyReturn {
         val a = 1
         if(bool) earlyReturn(4) else cpsunit    
         val b = 1
         earlyReturn2(4, bool)            
         val c = 1
         if(bool) earlyReturn(4) else cpsunit            
         a + b + c + 42
    }
    println(g)  
}

The only problem here, is that you have to explicitly use cpsunit.

EDIT1: Yes, earlyReturn(4, cond = !checkOK) can be implemented, but it won't be that general and elegant:

def earlyReturn2[T](ret: T, cond: => Boolean): Any @cpsParam[Any, Any] =
                            shift((k: Any => Any) => if(cond) ret else k())

k in the snippet above represents the rest of the computation. Depending on the value of cond, we either return the value, or continue the computation.

EDIT2: Any chance we might get rid of cpsunit? The problem here is that shift inside the if statement is not allowed without else. The compiler refuses to convert Unit to Unit @cps[Unit].

like image 127
Vasil Remeniuk Avatar answered Sep 20 '22 23:09

Vasil Remeniuk