I’ve asked this same question several times as I’ve been learning Scala. Each time I feel I have a better handle on the situation.
There are several iteration facilities in Scala which traverse a collection and generate some result. Sometime the program logic determines that it is finished and has computed the desired result. Thereafter, it is a challenge to return that value.
Here is something I’ve sort of converged on using
def block[A](body:(A=>Nothing)=>A):A = {
// CL-like block/return-from-block, the name of the return() function is provided
// by the caller.
// Usage: block{ ret => ... ret(someValue) ...}
// extending Exception with NoStackTrace prevents throwing the
// exception from computing the stacktrace and storing the information.
// We don't need a stacktrace because the purpose of this exception
// is simply to perform a non-local exit.
import scala.util.control.NoStackTrace
class NonLocalExit(val data:A,val ident:(A=>Nothing)=>A) extends Exception with NoStackTrace {}
def ret(data:A):Nothing = {
throw new NonLocalExit(data,body)
}
try{
body(ret)
}
catch{
case nonLocalExit: NonLocalExit if nonLocalExit.ident eq body => nonLocalExit.data
}
}
The challenge is to make it reentrant, so that escaping from a block
always escapes from the correct block, regardless of the state of recursion of various functions.
Recently I looked into the implementation of find
in the scala library, and discovered there ALREADY IS a class scala.util.control.Breaks
to implement this behavior. But I don’t know if it is safe to use.
def find(p: A => Boolean): Option[A] = {
var result: Option[A] = None
breakable {
for (x <- this)
if (p(x)) { result = Some(x); break }
}
result
}
I like it because it is already build-in-- I could theoretically just use it.
However, I don’t like the fact that the break
keyword is fixed and not a function, and that using it seems imperative, I can only envision using it for side effect or at least with mutable variables.
In my implementation, an argument of block
is a function to call with the value you want returned from block
. This makes block
nestable and any nested level can return from any block
, and its never ambiguous.
block{
break_1 =>
block{
break_2 =>
... some code...
... if something break_1(my_return_value_1)
... some code
... if something break_2(my_return_value_2)
... some code
}
}
Thus the find
implementation above would be simplfied:
def find(p: A => Boolean): Option[A] = {
block{ found =>
for (x <- this)
if (p(x)) { found(Some(x) }
None
}
}