groovy
  1. groovy
  2. GROOVY-5306

Add "a ?= 2" support: should be expanded to "a = a == null ? 2 : a"

    Details

    • Type: Improvement Improvement
    • Status: Open Open
    • Priority: Major Major
    • Resolution: Unresolved
    • Affects Version/s: None
    • Fix Version/s: 4.0
    • Component/s: syntax
    • Labels:
      None
    • Number of attachments :
      0

      Description

      I've suggested last week the creation of the "?:=" constructor in Groovy:

      https://jira.codehaus.org/browse/GROOVY-5291

      It was rejected and a new JIRA was requested to be created with a new operator.

      Then, I've talked to the Grails users in their mailing list to get some feedback, which can be read here:

      http://grails.1312388.n4.nabble.com/Help-improving-Groovy-syntax-tt4384137.html

      Then, Phil DeJarnett has suggested using "?=" instead of "?:=". Not only I preferred this suggestion, but I was changed my mind about its meaning too.

      It would be used as a caching/memoization operator mostly. It would be similar to Ruby's "||=", except for this specific situation:

      Ruby:

      a = nil
      a ||= false # a will be false
      a ||= true # a will be true
      

      That is why "a ||= value" is expanded to "a = a || value"

      But for caching/memoization, I'd prefer "a ?= value" to be expanded to "a = a == null ? value : a". This way we would have:

      Proposed Groovy syntax:

      def a = null
      a ?= false; assert a == false
      a ?= true; assert a == true
      
      a = null; a ?= new Object(); assert a instanceof Object
      

      I'll actually fill a new ticket on Ruby Redmine too for proposing the same syntax and semanthics

        Issue Links

          Activity

          Hide
          Rodrigo Rosenfeld Rosas added a comment -

          I've just proposed the same syntax in Ruby redmine:

          http://bugs.ruby-lang.org/issues/6023

          Show
          Rodrigo Rosenfeld Rosas added a comment - I've just proposed the same syntax in Ruby redmine: http://bugs.ruby-lang.org/issues/6023
          Hide
          Alex Anderson added a comment -

          Looks nice.

          What would be the value of a in the following?

          def a = false
          a ?= 2

          Show
          Alex Anderson added a comment - Looks nice. What would be the value of a in the following? def a = false a ?= 2
          Hide
          Phil DeJarnett added a comment -

          I think that, to fit with Groovy, it should be based on Groovy-false. So, in Alex's example, a should be equal to 2.

          A great usage example, provided by Alex on the mailing list, is:

          def beforeInterceptor = {
              params.sort = params.sort ?: 'date'
              params.order = params.order ?: 'desc'
          }
          

          This is a common usage pattern in Grails. The new operator would seriously simplify this pattern, and provide an easier-to-read, and less error-prone solution.

          def beforeInterceptor = {
              params.sort ?= 'date'
              params.order ?= 'desc'
          }
          

          I also think that it could be called the default-to operator, as in params.sort default to "date".

          Show
          Phil DeJarnett added a comment - I think that, to fit with Groovy, it should be based on Groovy-false. So, in Alex's example, a should be equal to 2 . A great usage example, provided by Alex on the mailing list, is: def beforeInterceptor = { params.sort = params.sort ?: 'date' params.order = params.order ?: 'desc' } This is a common usage pattern in Grails. The new operator would seriously simplify this pattern, and provide an easier-to-read, and less error-prone solution. def beforeInterceptor = { params.sort ?= 'date' params.order ?= 'desc' } I also think that it could be called the default-to operator, as in params.sort default to "date" .
          Hide
          Phil DeJarnett added a comment -

          Also, another note, I think it should expand to something like if(!foo) foo = "default", instead of foo = foo ?: "default".

          This would prevent evaluating foo twice or setting foo if it is already set. (Potentially useful, for example, in situations where foo is purely dynamic.) The new operator would then have another benefit over the elvis operator.

          Show
          Phil DeJarnett added a comment - Also, another note, I think it should expand to something like if(!foo) foo = "default" , instead of foo = foo ?: "default" . This would prevent evaluating foo twice or setting foo if it is already set. (Potentially useful, for example, in situations where foo is purely dynamic.) The new operator would then have another benefit over the elvis operator.
          Hide
          CÚdric Champeau added a comment -

          Formatting tags.

          Show
          CÚdric Champeau added a comment - Formatting tags.
          Hide
          Rodrigo Rosenfeld Rosas added a comment -

          @Alex and @Phil, with this propose a should be false in Alex's example. If you prefer 2 instead, please vote on this other JIRA:

          https://jira.codehaus.org/browse/GROOVY-5291

          But I'd prefer "?=" to have a different semantic, as I want it to be used for caching only.

          Suppose that in an expensive calculation you've come to the conclusion that the result should be an empty map ([:]).

          Then, I wouldn't be able to use the cache with "map ?= expensiveCalculation()" because the method would be run again. In the other side, if its initial value is null, and the proposed syntax is accepted, expensiveCalculation will only be run once.

          I think that "a ?:= 2" could be also useful, so I'd ask to reconsider the other ticket as well.

          @Phil, I don't think that it should be expanded to "if (!foo) foo = ..." or even "if (foo == null) foo = ..." exactly because I think this should be applied only to local variables or class members.

          But I guess this will have some bugs inside closures:

          https://jira.codehaus.org/browse/GROOVY-1569

          I really think that bug should be considered a serious one instead of remaining open since 2006 and target to Groovy 3.0.

          Show
          Rodrigo Rosenfeld Rosas added a comment - @Alex and @Phil, with this propose a should be false in Alex's example. If you prefer 2 instead, please vote on this other JIRA: https://jira.codehaus.org/browse/GROOVY-5291 But I'd prefer "?=" to have a different semantic, as I want it to be used for caching only. Suppose that in an expensive calculation you've come to the conclusion that the result should be an empty map ( [:] ). Then, I wouldn't be able to use the cache with "map ?= expensiveCalculation()" because the method would be run again. In the other side, if its initial value is null, and the proposed syntax is accepted, expensiveCalculation will only be run once. I think that "a ?:= 2" could be also useful, so I'd ask to reconsider the other ticket as well. @Phil, I don't think that it should be expanded to "if (!foo) foo = ..." or even "if (foo == null) foo = ..." exactly because I think this should be applied only to local variables or class members. But I guess this will have some bugs inside closures: https://jira.codehaus.org/browse/GROOVY-1569 I really think that bug should be considered a serious one instead of remaining open since 2006 and target to Groovy 3.0.
          Hide
          Phil DeJarnett added a comment -

          Rodrigo: I disagree here. I don't understand why you'd want two different operators that perform 99% the same function, but with a single, critical difference, that only differ by one, easily-missed character. This is the kind of language design choices that makes a language difficult for new developers.

          When you want to compare against null explicitly, I feel that should be spelled out in the code, using the existing language constructs. I think it's important to be a little more verbose when you want to express concrete ideas, such as "only update this value when foo is null".

          You seem to have some use cases in your mind that apply specifically to you, but you haven't really provided any actual, real-world examples on either bug report that shows where your usage is beneficial. The generic examples you provide aren't really doing much to argue for having two variants. Until I saw Alex's example, I wasn't really sold on the idea. I recommend putting together some concrete examples to provide a solid reasoning for the difference between the two operators.

          (I don't think that GROOVY-1569 is a serious issue, because it actually follows with rules of the language. Fields are only accessed directly from within the same class. Closures, technically, are a different class. To forcibly access a field directly, there's an existing operator, this.@field.)

          Show
          Phil DeJarnett added a comment - Rodrigo: I disagree here. I don't understand why you'd want two different operators that perform 99% the same function, but with a single, critical difference, that only differ by one, easily-missed character. This is the kind of language design choices that makes a language difficult for new developers. When you want to compare against null explicitly, I feel that should be spelled out in the code, using the existing language constructs. I think it's important to be a little more verbose when you want to express concrete ideas, such as "only update this value when foo is null ". You seem to have some use cases in your mind that apply specifically to you, but you haven't really provided any actual, real-world examples on either bug report that shows where your usage is beneficial. The generic examples you provide aren't really doing much to argue for having two variants. Until I saw Alex's example, I wasn't really sold on the idea. I recommend putting together some concrete examples to provide a solid reasoning for the difference between the two operators. (I don't think that GROOVY-1569 is a serious issue, because it actually follows with rules of the language. Fields are only accessed directly from within the same class. Closures, technically, are a different class. To forcibly access a field directly, there's an existing operator, this.@field .)
          Hide
          Rodrigo Rosenfeld Rosas added a comment -

          Phil, my argument is exactly the same. Let's take the params use case, for example.

          params.sort ?= 'date'
          

          First, why are you modifying params your self. You will probably pass this params.sort so some method, so it would probably look like:

          MyDomain.withCriteria {
              ...
              sort params.sort ?: 'date'
          }
          

          Usually, the '?=' will be used for caching, and for this real-word use case, null is what you use for store unprocessed/uncached values.

          I know about this, because when programming in Ruby, I have already gone into this situation and I'm pretty sure I wasn't the first one to be surprised about this:

            @skip_disabled ||= true # caching like this doesn't work for boolean members
            @skip_disabled = true if @skip_disabled.nil? # has to be written this way
          
          Show
          Rodrigo Rosenfeld Rosas added a comment - Phil, my argument is exactly the same. Let's take the params use case, for example. params.sort ?= 'date' First, why are you modifying params your self. You will probably pass this params.sort so some method, so it would probably look like: MyDomain.withCriteria { ... sort params.sort ?: 'date' } Usually, the '?=' will be used for caching, and for this real-word use case, null is what you use for store unprocessed/uncached values. I know about this, because when programming in Ruby, I have already gone into this situation and I'm pretty sure I wasn't the first one to be surprised about this: @skip_disabled ||= true # caching like this doesn't work for boolean members @skip_disabled = true if @skip_disabled.nil? # has to be written this way
          Hide
          Rodrigo Rosenfeld Rosas added a comment -

          By the way, thanks for pointing me to this.@field. I didn't know about that.

          But I still think it is a serious bug, because it is about programming concepts. Closures enable functional programming, and shouldn't change the class scope, just like JavaScript or Ruby. The fact that it is implemented as if it was the body of a different class is just an implementation detail to me, not a feature of the language itself. I don't really think this behavior is desired. It is just a side effect.

          And I wasn't the one that created that JIRA back in 2006, so I'm not alone on thinking like this.

          Show
          Rodrigo Rosenfeld Rosas added a comment - By the way, thanks for pointing me to this.@field. I didn't know about that. But I still think it is a serious bug, because it is about programming concepts. Closures enable functional programming, and shouldn't change the class scope, just like JavaScript or Ruby. The fact that it is implemented as if it was the body of a different class is just an implementation detail to me, not a feature of the language itself. I don't really think this behavior is desired . It is just a side effect. And I wasn't the one that created that JIRA back in 2006, so I'm not alone on thinking like this.

            People

            • Assignee:
              Unassigned
              Reporter:
              Rodrigo Rosenfeld Rosas
            • Votes:
              1 Vote for this issue
              Watchers:
              0 Start watching this issue

              Dates

              • Created:
                Updated: