Hi Paul,

thank you for the link (https://github.com/apache/groovy/pull/1343 )

As far as I could see the NV macros you propose (https://github.com/apache/groovy/pull/1343/commits/0a3ba697511335a82ec7487998976aacdc8667de) all create "x0=${x0.foo()}, x1=${x1.foo()}, ..." GString|s, with foo in [toString, inspect, dump]:

NV ... "x0=$x0, x1=$x1, ..."-GString // toString() called on values; same as my NVS proposal NVI  ... "x0=${x0.inspect()}, x1=${x1.inspect()}, ..."-GString // inspect called on values NVD  ... "x0=${x0.dump()}, x1=${x1.dump()}, ..."-GString // dump called on values


However, the NameAndValue instance creation makes a) the concept very flexible, and b) elevates it beyond just logging (all examples dry-coded):

a) Examples:
println "path: ${NVL(x, y, z).join('/')}"  // arbitrary join
final weirdRegex = NVL(id,parentFiledReferenceIdentifier,name,humanReadableName).collect { "($it.name:$it.val)" }.join('|')  // arbitrary name-value-string + arbitrary join final xml = "<properties>${NVL(id,parentFiledReferenceIdentifier,name,humanReadableName).collect { "<$it.name>$it.val</$it.name>" }.join('')}</properties>" // another arbitrary name-value-string + arbitrary join (just a quick&dirty example - please stream properly escaped XML at all times :-) )

b) Examples: I use it for instance to define property collections in Pojo classes, which other methods then use to drive e.g. the output of the Pojo in a GUI table (or to create some SQL), e.g.:
List<NameAndValue> getImportantProperties() { NVL(id, name, children) }
List<NameAndValue<String>> getTextProperties() { NVL(firstName, lastName, description) } String toXmlString() { "<foo ${importantProperties.collect { NameAndValue nv -> $/${xmlAttributeName(nv.name)}="${xmlAttributeVal(nv.val)}"/$ } }>insert text here...</foo>" }


The NameAndValue<T> class is of course indepent of the NV macro family, and can also be created explicitly, e.g. (assuming @Newify pattern):

final x = 100
final y = 123.456
final list = [ NameAndValue('first',x), NameAndValue('second',y) ] + NVL(pos.x, pos.y)

Keeping that use in mind, it could make sense to come up with a shorter class name for the NameAndValue class...


I would therefore suggest we support both:

// NameAndValue based
NV ... as my NVL before
NVP ... as NV, but first argument is treated as parameter for NameAndValue instance creation; e.g. NVP(NameAndValue.DUMP, a, b, c) // create NameAndValueExt instance, which will use dump method in its  toString() method

// GString based
GV or NVS ... your NVI; GV = GStringed-variable(s)
GVI or NVSI ... your NVI
GVD or NVSD ... your NVD

Since "inspect" falls back to "toString", we could also just make GV/NVS use inspect, and only support GVD/NVSD besides that - then it would make sense to use "inspect" instead of "toString" in NameAndValue#toString also...

Cheers,
mg



On 07/08/2020 12:53, Paul King wrote:
I created a starting set of NV, NVI and NVD macros similar (but slightly different) to what mg has described previously. I see that as a starting point for discussion.

Something like 'returnIf' wouldn't be hard to add but I'd personally prefer to explore enhancing our switch statement first since I think that would cover many use cases where I'd be tempted to try 'returnIf'.

Just on switch statements we have Java 13/14 enhanced switch we could explore (switch expressions, yields/no breaks) and destructuring like python's latest proposal[1] but obviously with our own syntax.


Cheers, Paul.
[1] https://www.python.org/dev/peps/pep-0622/

On Wed, Aug 5, 2020 at 7:53 AM MG <mg...@arscreat.com <mailto:mg...@arscreat.com>> wrote:

    Hi Eric,

    yea,  I got that, that's why I said "In that case a global setting
    might /also/ be useful".

    But I doubt that the majority of Groovy users out there who want
    to quickly check if it is macros that make their code break in
    Groovy 4 would know that to do so they just need to "add the macro
    transform class to the disallowed list in CompilerConfiguration"; 
    to be able to do so would mean one would a) need to know the macro
    transform class exists and what its purpose and exact name is, b)
    how the disallowed list in CompilerConfiguration works (that's the
    easy part), as well as last but not least c) to be sure that doing
    so will not just break part or all of Groovy... ;-)

    I think you grossly underestimate the amount of Groovy (internal)
    knowledge you have :-)

    Cheers,
    mg


    On 04/08/2020 18:27, Milles, Eric (TR Technology) wrote:

    In terms of globally disabling macro methods, you can just add
    the macro transform class to the disallowed list in
    CompilerConfiguration.  I think Paul is describing a mechanism
    where an individual macro method is taken out of service.

    *From:* MG <mg...@arscreat.com> <mailto:mg...@arscreat.com>
    *Sent:* Tuesday, August 4, 2020 9:53 AM
    *To:* dev@groovy.apache.org <mailto:dev@groovy.apache.org>; Paul
    King <pa...@asert.com.au> <mailto:pa...@asert.com.au>
    *Subject:* Re: [PROPOSAL]Support conditional return

    Hi Paul,

    thanks for clearing that up :-)

    @unforeseen implications: In that case a global
    -Dgroovy.macro.enable=false
    might also be useful, to do a quick check if it is macros that
    are causing the problem (if we do not have that already).

    Btw: Do we have a way to hide the macro definitions from e.g.
    IntelliJ Intellisense, and only show the stub implementation ? I
    use the NV macros* extensively by now in my code, and what I
    found is, that always having to select and import the stub class,
    and not the macro class is a (small) hassle.

    Cheers,
    mg

    *In practice it turns out the NV variety I use the most is NVL,
    which returns a list of NV instances, so is good for logging
    multiple variables. At some point in the future there will need
    to be a discussion what varieties we want to support; my
    suggestion would be:
    NV(x) ... single NameAndValue class instance
    NVL(x0,x1,...) ... list of NameAndValue instances
    NVS(x0,x1,...) ... "x0=$x0, x1=$x1, ..."-GString
    (we could also have NVS return an (efficiently built) String, and
    NVGS return the GString, but I am not sure whether that it is
    worth it)

    On 04/08/2020 08:17, Paul King wrote:

        Hi mg,

        Just on supplying our own macros, we should do this for
        Groovy 4. We have been reluctant so far because we have been
        conservative about unforeseen implications. However, unless
        we start using them more widely, we aren't going to learn
        those implications.

        I'd suggest having them (to start with) in their own optional
        incubating module (e.g. groovy-macro-samples) and we should
        come up with a way to disable any one of them, e.g.
        -Dgroovy.macro.enable.returnIf=false
        -Dgroovy.macro.enable.NV=true (or whatever).

        Cheers, Paul.

        On Wed, Jul 29, 2020 at 10:07 AM MG <mg...@arscreat.com
        <mailto:mg...@arscreat.com>> wrote:

            I like that idea :-)
            (unless someone has a really convincing argument why not,
            100% for
            sticking with "it" instead of "_"/"$")

            That would also allow for more flexibility with e.g.
            regards to the
            number of methods that are being evaluated, without
            getting into the
            problematic area of whether/ how to support this syntax-wise.

            If there is nothing blocking this, the question is if
            Groovy should
            supply a basic version of such a macro (if Groovy is ever
            planning to
            supply macros of its own) ?

            Cheers,
            mg


            On 28/07/2020 16:08, Milles, Eric (TR Technology) 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
            <mailto:sun...@apache.org>>
            > Sent: Sunday, July 26, 2020 6:23 PM
            > To: dev@groovy.apache.org <mailto: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&amp;data=02%7C01%7Ceric.milles%40thomsonreuters.com%7C411c66fda05844d7429908d831bacc9d%7C62ccb8646a1a4b5d8e1c397dec1a8258%7C0%7C0%7C637314025668554080&amp;sdata=vNa3dz0H%2BJAegS9Zb8HW2by0ueceqCKI6qDVFpBpbc4%3D&amp;reserved=0
            
<https://nam02.safelinks.protection.outlook.com/?url=https%3A%2F%2Ftwitter.com%2Fbsideup%2Fstatus%2F1287477595643289601%3Fs%3D20&data=02%7C01%7Ceric.milles%40thomsonreuters.com%7Cd22419dbc45b40d96bf608d8388618f6%7C62ccb8646a1a4b5d8e1c397dec1a8258%7C0%7C0%7C637321496617678018&sdata=NBAn2FtRigVd8UFrZpPt7GdDLdI7kMVh%2BxTl2vNAtK0%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
            <mailto: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
            <mailto: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 <mailto: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&amp;data=02%7C01%7Ceric.milles%40thomsonreuters.com%7C411c66fda05844d7429908d831bacc9d%7C62ccb8646a1a4b5d8e1c397dec1a8258%7C0%7C0%7C637314025668554080&amp;sdata=5m%2B5ejCWEicseaUp5wK0UDjHwpfMFht5ptjglZ9IWS4%3D&amp;reserved=0
            
<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%7Cd22419dbc45b40d96bf608d8388618f6%7C62ccb8646a1a4b5d8e1c397dec1a8258%7C0%7C0%7C637321496617688010&sdata=vHsd4PrZTGJxhfAvtPxKLEfAvxiidhOAVvqFthIDHTU%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
            
<https://nam02.safelinks.protection.outlook.com/?url=https%3A%2F%252>
            >>>>>
            Fmedium.com%2Fncr-edinburgh%2Fearly-exit-c86d5f0698ba&amp;data=02
            >>>>> %7C01%7Ceric.milles%40thomsonreuters.com
            
<https://nam02.safelinks.protection.outlook.com/?url=http%3A%2F%2F40thomsonreuters.com%2F&data=02%7C01%7Ceric.milles%40thomsonreuters.com%7Cd22419dbc45b40d96bf608d8388618f6%7C62ccb8646a1a4b5d8e1c397dec1a8258%7C0%7C0%7C637321496617688010&sdata=dVEeA8WhNRImv11c7OIRUDcIyrjq58kF0o%2F5R%2BK6WlU%3D&reserved=0>%7C411c66fda05844d7429908
            >>>>>
            d831bacc9d%7C62ccb8646a1a4b5d8e1c397dec1a8258%7C0%7C0%7C637314025
            >>>>>
            668554080&amp;sdata=q8VrgoQDeH85232oyMgQT8WwljNqoUjIc4cS7GGqH5I%3
            >>>>> D&amp;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
            <mailto: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 <mailto: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