Hi,

just to throw it out there. As far as I understand this, we want to be
able to return based on the value of computed by a function without
having to assign the value to a variable first.

Another potentially more powerful alternative would be if we could
return the method from inside a closure.

def doSomething(int a) {
   callB().tap { if (a > 6 && it > 10) return@doSomething it }
   callC().tap { if ( a > 5 && it > 20) return@doSomething it }
   callD().tap { if ( a > 4 && it > 30) return@doSomething it }
}

While it may not be as concise as the original proposal, it fits better
in the existing syntax IMO.

The exact syntax of return@doSomething is up for debate, i'd imagine it
similar to break labels, with the method name being an implicit label.
Alternatively, we could use a special keyword for it.

On the discussion about "_", "$", or "it" I would strongly prefer "it"
as it would be consitent with other cases.


On a side note MG mentioned:

> (if we had "??=" as "assign iff RHS != null", "??:" for "?: with
non-nullness", and could throw where an expression was expected)

JavaScript just got a new set of conditional assignment operators
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators

Name                                       Shorthand operator     Meaning

Logical AND assignment       x &&= y                          x && (x = y)
Logical OR assignment          x ||= y                            x ||
(x = y)
Logical nullish assignment    x ??= y                           x ?? (x = y)

Regardless of the outcome of this proposal, it might be worthwhile to
add them to groovy.

Cheers

Leo


Am 29.07.2020 um 22:28 schrieb MG:
> Hi Daniel,
>
> good idea also :-)
>
> If the arguments are in this order however, it looks like a regular if
> with its execution block to me...
> Having the method call as the second argument would express more
> intuitively what is happening - so maybe one should flip the order of
> the arguments, and use:
>
> returnIf(a > 6 && it > 10) { goo() }
>
> Cheers,
> mg
>
>
> PS: If it = callB() were to be executed lazily, and no "it" argument
> existed inside the condition, it would not be evaluated at all if
> condition evaluates to false, making
>
> returnIf(<condition>) { goo() }
>
> equivalent to
>
> if(<condition>) { return goo() }
>
>
>
> On 29/07/2020 05:18, Daniel Sun wrote:
>> Hi Eric,
>>
>>     I like your idea too ;-)
>>
>>     We could use closure to express the condition at the tailing of
>> method call:
>> ```
>> def doSomething(int a) {
>>    returnIf(callB()) { a > 6 && it > 10 }
>>    returnIf(callC()) { a > 5 && it > 20 }
>>    returnIf(callD()) { a > 4 && it > 30 }
>> }
>> ```
>>
>> Cheers,
>> Daniel Sun
>> On 2020/07/28 14:08:45, "Milles, Eric (TR Technology)"
>> <eric.mil...@thomsonreuters.com> wrote:
>>> If switch expression or pattern match macro is insufficient, could a
>>> macro be written to cover this "conditional return"?
>>>
>>> // "it" could easily be replaced by "_" or "$" as mentioned
>>> previously as options
>>> def doSomething(int a) {
>>>    returnIf(callB(), a > 6 && it > 10)
>>>    returnIf(callC(), a > 5 && it > 20)
>>>    returnIf(callD(), a > 4 && it > 30)
>>> }
>>>
>>>    vs.
>>>
>>> def doSomething(int a) {
>>>    return callB() if (a > 6 && _ > 10)
>>>    return callC() if (a > 5 && _ > 20)
>>>    return callD() if (a > 4 && _ > 30)
>>> }
>>>
>>> -----Original Message-----
>>> From: Daniel Sun <sun...@apache.org>
>>> Sent: Sunday, July 26, 2020 6:23 PM
>>> To: dev@groovy.apache.org
>>> Subject: Re: [PROPOSAL]Support conditional return
>>>
>>> Hi Sergei,
>>>
>>> ( Copied from twitter:
>>> https://nam02.safelinks.protection.outlook.com/?url=https%3A%2F%2Ftwitter.com%2Fbsideup%2Fstatus%2F1287477595643289601%3Fs%3D20&data=02%7C01%7Ceric.milles%40thomsonreuters.com%7C411c66fda05844d7429908d831bacc9d%7C62ccb8646a1a4b5d8e1c397dec1a8258%7C0%7C0%7C637314025668554080&sdata=vNa3dz0H%2BJAegS9Zb8HW2by0ueceqCKI6qDVFpBpbc4%3D&reserved=0
>>> )
>>>> But isn't it better with pattern matching? And what is "_" here?
>>> The underscore represents the return value
>>>
>>>> Anyways:
>>>> ```
>>>> return match (_) {
>>>>      case { it < 5 }: callC();
>>>>      case { it > 10 }: callB();
>>>>      case { it != null }: callA();
>>>>      default: {
>>>>          LOG.debug "returning callD"
>>>>          return callD()
>>>>      }
>>>> }
>>>> ```
>>> pattern matching may cover some cases of Conditional Return, but it
>>> can not cover all. Actually the Conditional Return is more flexible,
>>> e.g.
>>>
>>> ```
>>> def chooseMethod(String methodName, Object[] arguments)  {
>>>     return doChooseMethod(methodName, arguments) if _ != null
>>>
>>>     for (Class type : [Character.TYPE, Integer.TYPE]) {
>>>        return doChooseMethod(methodName,
>>> adjustArguments(arguments.clone(), type)) if _ != null
>>>     }
>>>
>>>     throw new GroovyRuntimeException("$methodName not found") } ```
>>>
>>> Even we could simplify the above code with `return?` if the
>>> condition is Groovy truth:
>>> ```
>>> def chooseMethod(String methodName, Object[] arguments)  {
>>>     return? doChooseMethod(methodName, arguments)
>>>
>>>     for (Class type : [Character.TYPE, Integer.TYPE]) {
>>>        return? doChooseMethod(methodName,
>>> adjustArguments(arguments.clone(), type))
>>>     }
>>>
>>>     throw new GroovyRuntimeException("$methodName not found") } ```
>>>
>>> Cheers,
>>> Daniel Sun
>>> On 2020/07/26 18:23:41, Daniel Sun <sun...@apache.org> wrote:
>>>> Hi mg,
>>>>
>>>>> maybe you can give some real life code where you encounter this on
>>>>> a regular basis ?
>>>> Let's think about the case about choosing method by method name and
>>>> arguments:
>>>>
>>>> ```
>>>> def chooseMethod(String methodName, Object[] arguments) {
>>>>     def methodChosen = doChooseMethod(methodName, arguments)
>>>>     if (null != methodChosen) return methodChosen
>>>>
>>>>     methodChosen = doChooseMethod(methodName,
>>>> adjustArguments(arguments.clone(), Character.TYPE))
>>>>     if (null != methodChosen) return methodChosen
>>>>
>>>>     methodChosen = doChooseMethod(methodName,
>>>> adjustArguments(arguments.clone(), Integer.TYPE))
>>>>     if (null != methodChosen) return methodChosen
>>>>
>>>>     throw new GroovyRuntimeException("$methodName not found") } ```
>>>>
>>>> The above code could be simplified as:
>>>> ```
>>>> def chooseMethod(String methodName, Object[] arguments) {
>>>>     return? doChooseMethod(methodName, arguments)
>>>>
>>>>     return? doChooseMethod(methodName,
>>>> adjustArguments(arguments.clone(), Character.TYPE))
>>>>
>>>>     return? doChooseMethod(methodName,
>>>> adjustArguments(arguments.clone(), Integer.TYPE))
>>>>
>>>>     throw new GroovyRuntimeException("$methodName not found") } ```
>>>>
>>>> Or a general version:
>>>> ```
>>>> def chooseMethod(String methodName, Object[] arguments) {
>>>>     return doChooseMethod(methodName, arguments) if _ != null
>>>>
>>>>     return doChooseMethod(methodName,
>>>> adjustArguments(arguments.clone(), Character.TYPE)) if _ != null
>>>>
>>>>     return doChooseMethod(methodName,
>>>> adjustArguments(arguments.clone(), Integer.TYPE)) if _ != null
>>>>
>>>>     throw new GroovyRuntimeException("$methodName not found") } ```
>>>>
>>>>
>>>> Cheers,
>>>> Daniel Sun
>>>> On 2020/07/26 17:11:07, MG <mg...@arscreat.com> wrote:
>>>>> Hi Daniel,
>>>>>
>>>>> currently I would be +/- 0 on this.
>>>>>
>>>>> Thoughts:
>>>>>
>>>>>   1. I feel I have written this before, but I myself do not
>>>>> encounter the
>>>>>      situation where I would need to return the result of a method
>>>>> call
>>>>>      only if it meets certain conditions when programming (maybe
>>>>> you can
>>>>>      give some real life code where you encounter this on a
>>>>> regular basis ?).
>>>>>   2. If I have more than one return, it typcially is an early out,
>>>>> which
>>>>>      depends on the method's input parameters, not on the result of
>>>>>      another method call.
>>>>>   3. Since I do a lot of logging / log debugging, I typically
>>>>> assign the
>>>>>      return value to a variable, so I can debug-log it before the one
>>>>>      return of the method.
>>>>>   4. In fact I have had to refactor code written by other people from
>>>>>      multi-return methods to single return, to be able to track
>>>>> down bugs.
>>>>>
>>>>> So overall I am not sure one should enable people to make it easier
>>>>> to write non-single-return methods ;-)
>>>>>
>>>>>
>>>>> Purely syntax wise I would prefer
>>>>> return?
>>>>> for the simple case,
>>>>>
>>>>> and
>>>>>
>>>>> return <something> if <condition>
>>>>> for the more complex one*.
>>>>>
>>>>> I find
>>>>> return(<condition)  <something>
>>>>> confusing on what is actually returned.
>>>>>
>>>>> Cheers,
>>>>> mg
>>>>>
>>>>> *Though I wonder if people would not then expect this
>>>>> if-postfix-syntax to also work for e.g. assignments and method
>>>>> calls...
>>>>>
>>>>>
>>>>> On 26/07/2020 16:15, Daniel Sun wrote:
>>>>>> Hi Mario,
>>>>>>
>>>>>>       I think you have got the point of the proposal ;-)
>>>>>>
>>>>>>       If we prefer the verbose but clear syntax, I think we could
>>>>>> introduce `_` to represent the return value for concise shape:
>>>>>>
>>>>>> ```
>>>>>> return callB() if (_ != null && _ > 10)
>>>>>>
>>>>>> // The following code is like lambda expression, which is a bit
>>>>>> more verbose return callB() if (result -> result != null && result
>>>>>>> 10) ```
>>>>>>       Show the `_` usage in your example:
>>>>>> ```
>>>>>> def doSomething(int a) {
>>>>>>     return callB() if (a > 6 && _ > 10)
>>>>>>     return callC() if (a > 5 && _ > 20)
>>>>>>     return callD() if (a > 4 && _ > 30) } ```
>>>>>>
>>>>>> ```
>>>>>> // optional parentheses
>>>>>> def doSomething(int a) {
>>>>>>     return callB() if a > 6 && _ > 10
>>>>>>     return callC() if a > 5 && _ > 20
>>>>>>     return callD() if a > 4 && _ > 30 } ```
>>>>>>
>>>>>> ```
>>>>>> // one more example
>>>>>> def doSomething(int a) {
>>>>>>     return callB()                if a > 6 && _ > 10
>>>>>>     return callC() + callD() if a > 5 && _ > 50 } ```
>>>>>>
>>>>>>       BTW, the parentheses behind `if` could be optional.
>>>>>>
>>>>>> Cheers,
>>>>>> Daniel Sun
>>>>>> On 2020/07/26 11:29:39, Mario Garcia <mario.g...@gmail.com> wrote:
>>>>>>> Hi all:
>>>>>>>
>>>>>>> Very interesting topic.
>>>>>>>
>>>>>>> The first idea sprang to mind was the PMD rule in Java saying you
>>>>>>> should have more than one exit point in your methods (
>>>>>>> https://nam02.safelinks.protection.outlook.com/?url=https%3A%2F%2Fpmd.github.io%2Flatest%2Fpmd_rules_java_codestyle.html%23onlyonereturn&data=02%7C01%7Ceric.milles%40thomsonreuters.com%7C411c66fda05844d7429908d831bacc9d%7C62ccb8646a1a4b5d8e1c397dec1a8258%7C0%7C0%7C637314025668554080&sdata=5m%2B5ejCWEicseaUp5wK0UDjHwpfMFht5ptjglZ9IWS4%3D&reserved=0).
>>>>>>>
>>>>>>> But the reality is that sometimes (more often than not) we are
>>>>>>> forced to break that rule. In fact sometimes we could even argue
>>>>>>> that breaking that rule makes the code clearer (e.g
>>>>>>> https://nam02.safelinks.protection.outlook.com/?url=https%3A%2F%2
>>>>>>> Fmedium.com%2Fncr-edinburgh%2Fearly-exit-c86d5f0698ba&data=02
>>>>>>> %7C01%7Ceric.milles%40thomsonreuters.com%7C411c66fda05844d7429908
>>>>>>> d831bacc9d%7C62ccb8646a1a4b5d8e1c397dec1a8258%7C0%7C0%7C637314025
>>>>>>> 668554080&sdata=q8VrgoQDeH85232oyMgQT8WwljNqoUjIc4cS7GGqH5I%3
>>>>>>> D&reserved=0)
>>>>>>>
>>>>>>> Although my initial reaction was to be against the proposal,
>>>>>>> however after doing some coding, I've found that neither elvis
>>>>>>> nor ternary operators makes it easier nor clearer. Here's why I
>>>>>>> think so. Taking Daniel's example:
>>>>>>>
>>>>>>> ```
>>>>>>> def m() {
>>>>>>>      def a = callA()
>>>>>>>      if (null != a) return a
>>>>>>>
>>>>>>>      def b = callB()
>>>>>>>      if (b > 10) return b
>>>>>>>
>>>>>>>      def c = callC()
>>>>>>>      if (null != c && c < 10) return c
>>>>>>>
>>>>>>>      LOGGER.debug('the default value will be returned')
>>>>>>>
>>>>>>>      return defaultValue
>>>>>>> }
>>>>>>> ```
>>>>>>> The shortest elvis operator approach I could think of was:
>>>>>>> ```
>>>>>>> def m2() {
>>>>>>>      return callA()
>>>>>>>          ?: callB().with { it > 10 ? it : null }
>>>>>>>          ?: callC().with { null != it && it <10 ? it : null } }
>>>>>>> ```
>>>>>>>
>>>>>>> which to be honest, is ugly to read, whereas Daniel's proposal
>>>>>>> is just:
>>>>>>>
>>>>>>> ```
>>>>>>> def m() {
>>>>>>>      return? callA()
>>>>>>>      return(r -> r > 10) callB()
>>>>>>>      return(r -> null != r && r < 10) callC()
>>>>>>>      return defaultValue
>>>>>>> }
>>>>>>> ```
>>>>>>>
>>>>>>> Once said that, I would say this conditional return could be
>>>>>>> useful only when there are more than two exit points, otherwise
>>>>>>> ternary or elvis operators may be good enough.
>>>>>>>
>>>>>>> So, bottom line, I kinda agree to add conditional return, but I'm
>>>>>>> not sure about the final syntax:
>>>>>>>
>>>>>>> ```
>>>>>>> return(r -> r > 10) callB()
>>>>>>> return callB() [r -> r > 10]
>>>>>>> return callB() if (r -> r > 10)
>>>>>>> ```
>>>>>>>
>>>>>>> Between the three I the one that I like the most is the third one:
>>>>>>>
>>>>>>> ```
>>>>>>> return callB() if (r -> r > 10)
>>>>>>> ```
>>>>>>>
>>>>>>> You can read it in plain english as "return this if this
>>>>>>> condition happens".
>>>>>>>
>>>>>>> Apart from Daniel's use case, using this option could open the
>>>>>>> possibility to use, not only a closure or lambda expression, but
>>>>>>> also a plain expression. A nice side effect could be that
>>>>>>> something like the following code:
>>>>>>>
>>>>>>> ```
>>>>>>> def doSomething(int a) {
>>>>>>>     return callB() if a > 6
>>>>>>>     return callC() if a > 5
>>>>>>>     return callD() if a > 4
>>>>>>> }
>>>>>>> ```
>>>>>>>
>>>>>>> turns out to be a shorter (and in my opinion nicest) way of
>>>>>>> switch case (when you want every branch to return something):
>>>>>>>
>>>>>>> ```
>>>>>>> def doSomething(int a) {
>>>>>>>     switch (a) {
>>>>>>>        case { it > 6 }: return callB()
>>>>>>>        case { it > 5 }: return callC()
>>>>>>>        case { it > 4 }: return callD()
>>>>>>>     }
>>>>>>> }
>>>>>>> ```
>>>>>>>
>>>>>>> Well, bottom line, I'm +1 Daniel's proposal because I've seen
>>>>>>> some cases where this conditional return could make the code
>>>>>>> clearer.
>>>>>>>
>>>>>>> Cheers
>>>>>>> Mario
>>>>>>>
>>>>>>> El sáb., 25 jul. 2020 a las 23:55, Paolo Di Tommaso (<
>>>>>>> paolo.ditomm...@gmail.com>) escribió:
>>>>>>>
>>>>>>>> It's not much easier a conditional expression (or even the elvis
>>>>>>>> operator)?
>>>>>>>>
>>>>>>>> ```
>>>>>>>> def m() {
>>>>>>>>       def r = callSomeMethod()
>>>>>>>>       return r != null ? r : theDefaultResult } ```
>>>>>>>>
>>>>>>>>
>>>>>>>> On Sat, Jul 25, 2020 at 8:56 PM Daniel Sun <sun...@apache.org>
>>>>>>>> wrote:
>>>>>>>>
>>>>>>>>> Hi all,
>>>>>>>>>
>>>>>>>>>        We always have to check the returning value, if it match
>>>>>>>>> some condition, return it. How about simplifying it? Let's see
>>>>>>>>> an example:
>>>>>>>>>
>>>>>>>>> ```
>>>>>>>>> def m() {
>>>>>>>>>       def r = callSomeMethod()
>>>>>>>>>       if (null != r) return r
>>>>>>>>>
>>>>>>>>>       return theDefaultResult
>>>>>>>>> }
>>>>>>>>> ```
>>>>>>>>>
>>>>>>>>> How about simplifying the above code as follows:
>>>>>>>>> ```
>>>>>>>>> def m() {
>>>>>>>>>       return? callSomeMethod()
>>>>>>>>>       return theDefaultResult
>>>>>>>>> }
>>>>>>>>> ```
>>>>>>>>>
>>>>>>>>> Futhermore, we could make the conditional return more general:
>>>>>>>>> ```
>>>>>>>>> def m() {
>>>>>>>>>       return(r -> r != null) callSomeMethod() // we could do
>>>>>>>>> more checking, e.g. r > 10
>>>>>>>>>       return theDefaultResult
>>>>>>>>> }
>>>>>>>>> ```
>>>>>>>>>
>>>>>>>>>       Any thoughts?
>>>>>>>>>
>>>>>>>>> Cheers,
>>>>>>>>> Daniel Sun
>>>>>>>>>
>>>>>
>

Reply via email to