Hi OC,

regarding return statements working "as expected" inside loop/if statement body like Closures:

1. As I said I already proposed Groovy support this here a few years back.
2. It never came to fruition, with all of the problems (besides someone
   needing to do the actual coding) revolving around the simple fact
   that Groovy uses Closures, not actual code blocks for all of its
   non-Java style, stream-like loop/filter/collect/... support.
3. We have been using the ReturnException concept you mention in your
   mail since 2016 in our code to support breaking from e.g. our own
   SQL-Executor class loop constructs
    1. e.g.:
         * sqe.each("select $t.NICKNAME, $t.HEIGHT, $t.INFO from $p
           where ${p.ID.isInSql(candidateIds)}") { GroovyRowResult row ->
             o if(p.HEIGHT.getNumber(row) >
               minimalHeightDependingOnName(p.NICKNAME.getString(row))
               && scanClobForRelevantTraits(p.INFO .getClob(row))) {
                 + GroovyLoopHelper.breakFromLoop()  // found linebacker
             o }
         * }
    2. I have listed the code we use below, the basic principle is just
       that we use Throwable|s, not Exception|s (so it passes by most
       try-catch blocks), and we require all of our code that catches
       Throwable|s to silently rethrow if the caught type extends
       GroovyMandatorySilentlyRethrowThrowable.
    3. And, yes, using try-catch here is of course "slow", but that is
       irrelevant in any but the most time sensitive application.
        1. It certainly does not matter when you are e.g. processing
           the result of a database query that has been sent over a
           network connection ;-)
    4. We use return for loop-continue (ugly).
    5. We currently don't support a return statement like construct.
4. I think to break the deadlock with the "but what if you pass around
   the closure ?" argument Groovy would need to introduce something
   that differentiates closures that are supposed to simulate block
   constructs, by e.g. marking them as "block closures", or using a
   BlockClosure subclass.
    1. These block closures would need to be treated in a way by
       Groovy, that ensures that e.g. a return statement inside them
       actually returns from the enclosing method.
    2. /And here comes the kicker:/ Since that cannot be done by using
       actual closures unless one uses exceptions, which we cannot use
       as a general approach, since they are "slow", that would only
       work if we supported inlining of methods/closures !
    3. Inlining of methods and closures is of course not trivial, but
       more importantly some people here have in the past expressed
       concerns that it is actually a dangerous thing to support, that
       people could over- or abuse.
    4. And from my memory these are the reasons this never moved forward...

Cheers,
mg


class GroovyMandatorySilentlyRethrowThrowable extends Throwable {}

class BreakFromGroovyLoopThrowable extends GroovyMandatorySilentlyRethrowThrowable { @Lazy static final /BREAK_FROM_GROOVY_LOOP /= new BreakFromGroovyLoopThrowable()
}

class GroovyLoopHelper {
static void breakFromLoop() {
//throw BreakFromGroovyLoopThrowable./BREAK_FROM_GROOVY_LOOP
/}

static void handleGroovyMandatorySilentlyRethrowThrowable(Throwable throwable) {
if(throwable instanceof GroovyMandatorySilentlyRethrowThrowable) {
throw throwable
}
 }

static void each(iterable, Closure closure) {
try {
iterable.each(closure)
  }
catch(BreakFromGroovyLoopThrowable ignored) {}
 }

static void eachWithIndex(iterable, Closure closure) {
try {
iterable.eachWithIndex(closure)
  }
catch(BreakFromGroovyLoopThrowable ignored) {}
 }
}

/// Example of code that supports breakFromLoop: /
try {
/// do dangerous stuff that might throw Throwable|s .../
}
catch(Throwable throwable) {
  GroovyLoopHelper./handleGroovyMandatorySilentlyRethrowThrowable/(throwable) /// handle GroovyMandatorySilentlyRethrowThrowable /
/// Regular Throwable handling/
}



On 04/12/2024 18:03, OCsite wrote:
MG,

On 4. 12. 2024, at 16:11, MG <mg...@arscreat.com> wrote:

     1. e.g. using a for(foo : foos) { ... } loop instead of
        canonical Groovy foos.each { foo -> ... }, to be able to
        easily return from the for body from multiple places using
        return statements.

For one, I would argue that the native and groovier (since more logical and intuitive and intention-revealing for anyone who can read English completely regardless whether he knows Java or not) variant should be the /for/in/ loop, like /for (foo *in* foos)/. That weird and unintuitive colon thing should, in my personal opinion, remain limited to code copy/pasted from Java (exactly like /var/ :))

That would not be worth an extra email though. I wonder, would it be perhaps worth the effort to extend the language by adding a support for method-returning from a closure?

A trivial (and most probably very very wrong and problems-inducing!) approach might perhaps be an ASTT which would convert code like

def foo() {
  bar.each { if (it) methodreturn it }
}

to something like

def foo() {
  try {
    bar.each { if (it) throw new MethodReturnException(value:it) }
  } catch (MethodReturnException e) {
    return e.value
  }
}

Would it be perhaps worth the effort to add such an ASTT to Groovy? Not sure at all... but it might help (a) to stick with the canonical /foo.each/ instead of enforcing /for/ins/, (b) also, in many cases like /foo.find/, /foo.allResults/, et cetera, which are even more ugly to replace with plain ole loops.

All the best,
OC

Reply via email to