groovy-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From "Jochen Theodorou (JIRA)" <j...@apache.org>
Subject [jira] [Commented] (GROOVY-8301) break/return/continue support in "Appended Block Closures"
Date Fri, 23 Mar 2018 19:28:00 GMT

    [ https://issues.apache.org/jira/browse/GROOVY-8301?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=16411930#comment-16411930
] 

Jochen Theodorou commented on GROOVY-8301:
------------------------------------------

Now knowing a bit more about inlining there are a few things to say by me here. What inline
does is basically to copy bytecode from the to be inlined function to the callsite. So given

@Inline
 def foo()\{1}
 def x = foo()

the outcome would basically be:
 def x = 1

Appended blocks would then be of course:
 @Inline
 def foo(x)\{x()+1}

def y = foo {return 2}

would be
 def y = (return 2)+1

since this is not valid, this version cannot work. In general I find that in such situations
you are required to have a real block (like a void return type). With this comes along, that
we have to disable the implicit return mechanic for the block then. This means so far 2 challenges:
 # work around implicit return for inlined blocks
 # actually getting the bytecode of the to be inlined function
 # to callsite must be compiled to be able to do a direct method call
 # obviously we would also not use the closure shared mechanism for variable scoping

The point 2 is a problem for any scripting environment. In those cases the bytecode will not
be there, thus no inlining can happen. Additionally, if foo is part of the some compilation
unit you may not have yet the bytecode of foo when you generate the callsite for the call
to foo. For me that means we will have to post-process the bytecode as additional step. The
point 1 imho means that almost all methods from DGM cannot be used in their current form

Next let us talk about variables:
 @Inline
 def foo(x)\{x(1)+3}
 def y = foo \{2+it}

should become 
 def y = 2+1+3

This means we will have to resolve parameter variables of the block when inlining
 @Inline
 def foo(x)\{def y=x(1);y+3}
 def y = foo

{2+it}

should become
 def y = def y = 2+1; y+3

which cannot be expression in normal code, but it is allowed in bytecode to have a local variable
shadowing another or even having two at the same time. A bigger problem is that in the bytecode
y will have the slot 2, but that slot is most likely already in use and not supposed to be
used once we inline. Which means the bytecode post processor has to use the free available
slots and translate the slot usage from the to be inlined function to the scope of the callsite,

Translation for bytecode labels

@Inline
 def foo(x,y){
 for (i in x) \{y(i)}
 }
loop: for (int i=0; i<100; i++) {
  loop:foo([1,2,3]){break loop}
}

would be compiled to:
loop: for (int i=0; i<100; i++) {
  loop': for (i´ in [1,2,3]) {
   break loop
  }
}

Which means the break statement suddenly gets a different meaning here. And especially it
must not point to the loop from the function that got inlined




> break/return/continue support in "Appended Block Closures"
> ----------------------------------------------------------
>
>                 Key: GROOVY-8301
>                 URL: https://issues.apache.org/jira/browse/GROOVY-8301
>             Project: Groovy
>          Issue Type: New Feature
>          Components: Compiler
>            Reporter: mgroovy
>            Priority: Major
>              Labels: break, closure, continue, inline, return
>
> This proposal revisits the idea to change the semantics of the break, return, and continue
keyword for closures that are used in a way that mimics Java block constructs such as if,
for, or while.
> Example 1:
> {code}
> // Iterate over all PERSON rows in the result set
> sqe.forEachRow("select * from PERSON") { final row ->
>   if(row.TAG == 0) { break } // Leave forEachRow loop
>   if(row.TAG == 1) { return } // return from the method enclosing the forEachRow loop
>   if(row.TAG == 2) { continue } // Move to next iteration in the forEachRow loop
> }
> {code}
> Example 2:
> {code}
> // Encapsulate with statically imported helper method that catches exceptions and ignores
them, if the passed parameter is true
> execWithOptionalIgnoreErrors(ignoreErrorsQ) {
>   final x = dangerousOperation() 
>   if(x == 1) { return } // return from the enclosing method
>   // Note: break & continue are not allowed here
> }
> {code}
> Support for continue can trivially be mapped to a return statement inside the closure,
so what one needs to look at is how to support leaving the iteration ("break" semantics) and
returning from the enclosing method ("return" semantics). With current Groovy closures there
are two known potential approaches to achieve this behavior:
> # Use try-catch to simulate a non-local goto to "jump" to the right spot.
> ** Described e.g. here http://blackdragsview.blogspot.co.at/2006/09/non-local-transfers-in-groovy-90.html
by [~blackdrag].
> ** Drawbacks of the approach:
> ### If the user wraps a try-catch-everything (i.e. catching Exception or Throwable base
class) around the code, the mechanism will break in an unexpected way.
> ### Exceptions incur a certain performance overhead.
> # Use special return values that indicate whether to break or return.
> ** This evidently works for all cases which have no return value (e.g. forEach)
> ** For all other cases one could make the closure return an Object, which would again
allow to discern regular return values from break/return-semantics return values, if specific
classes (e.g. GroovyClosureBreak) are returned
> *** Since casting is required in this case, as small overhead is again incurred
> ** A big drawback is that for both cases for "return" semantics all surrounding methods
would need to have / made to have Object as return type.
> ** This approach could be used to supply "continue" and "break" semantics only.
> *** Note that having more than one return in a method is also often considered bad style,
so one could argue that "continue" and "break" semantics are the more important of the three
to support.
> A different approach would be to introduce "inline closures" to Groovy. These closures
would effectively be treated as inline block constructs, the same as they already exist for
e.g. regular for loops. This feature would make contine/break/return support for "closures"
trivial, since the keywords would just automatically work as expected if the closure code
is inlined.
> * This approach is used e.g. by the Kotlin programming language https://kotlinlang.org/docs/reference/inline-functions.html
> * Kotlin uses a seperate "inline" keyword for this. I feel like supplying an annotatio
in Groovy to indicate inlining would be the more Groovy/flexible (can be combined with other
annotations) approach (Kotlin to me generally seems to suffer from an overabundance of keywords)
> * Allowing inlining is also a feature that could also be applied for general methods.
> * Leaving implementation effort aside, this approach to me looks like the best way forward,
it
> ## never breaks
> ## never incurs any performance overhead
> ## is elegant
> ## potentially can be used for applications that go beyond the topic of this issue
> * Note: The general version of this feature has already been suggested in GROOVY-6880
with regards to performance improvements. I agree that potential performance improvements
are not a strong enough argument to introduce this, automatic break/contine/return support
for inlined closures to me is.



--
This message was sent by Atlassian JIRA
(v7.6.3#76005)

Mime
View raw message