groovy
  1. groovy
  2. GROOVY-3797

Provide an index or counter for each/find/findAll/collect and friends by taking into accounts the arity of closures passed to them

    Details

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

      Description

      As per the suggestion on this thread:
      http://www.nabble.com/generic-%27withIndex%27-method-%28like-Ruby-1.9-with_index%29-ts25631548.html

      The idea is to have access to an index or counter in all the functional methods iterating over collections, by using an additional parameter in the closure passed in parameter of those methods. That way, we don't need to add as many *withIndex variant methods to the existing ones, but rather provide a convenient convention.

      Some examples with each, and similarly by replacing each with find/findAll/any/every/collect and perhaps inject:

      list.each { elem -> ... }
      list.each { elem, counter -> ... }
      
      map.each { entry -> ... }
      map.each { key, value -> ... }
      map.each { key, value, counter -> ... }
      

        Issue Links

          Activity

          Hide
          Russel Winder added a comment -

          The only isue I can see being potentially problematic here is that in:

          def flob ( adob ) {
            adob.each { x , y -> . . . }
          }
          

          will lead to new behaviour compared to previously.

          Show
          Russel Winder added a comment - The only isue I can see being potentially problematic here is that in: def flob ( adob ) { adob.each { x , y -> . . . } } will lead to new behaviour compared to previously.
          Hide
          Paul King added a comment - - edited

          There is some significant overlap between this issue and GROOVY-1182. Perhaps we can edit one to be the superset of the two issues and close the other as a duplicate.

          Show
          Paul King added a comment - - edited There is some significant overlap between this issue and GROOVY-1182 . Perhaps we can edit one to be the superset of the two issues and close the other as a duplicate.
          Hide
          blackdrag blackdrag added a comment -

          I have several alternative suggestions to the map idea:

          (1)
          We could change the language to allow method local global variables. Basically that means that changes to this variable will be kept. In that it is comparable with a field. But since you cannot declare a field in a closure this would allow field logic in closures without having to define a class. This idea would maybe also save some usages of fields.

          (2)
          We could give closures some kind of call counter (with possible reset) in case of iterating with "each" this would mean that every call increases the counter, so it is equal to an index. The bad side of this is, that we have to use a fixed name and logic like we have for owner or delegate.

          both of these would be quite generic solutions

          Show
          blackdrag blackdrag added a comment - I have several alternative suggestions to the map idea: (1) We could change the language to allow method local global variables. Basically that means that changes to this variable will be kept. In that it is comparable with a field. But since you cannot declare a field in a closure this would allow field logic in closures without having to define a class. This idea would maybe also save some usages of fields. (2) We could give closures some kind of call counter (with possible reset) in case of iterating with "each" this would mean that every call increases the counter, so it is equal to an index. The bad side of this is, that we have to use a fixed name and logic like we have for owner or delegate. both of these would be quite generic solutions
          Hide
          Demian Ferreiro added a comment -

          If this approach is taken, I would suggest that in the 2-arity-closure version of the each/find/etc methods the index to be the first parameter to the closure. Therefore, maps and lists can be treated the same way, as the first parameter of these 2-arity-closures will always be the "key" and the second the value.

          def printKeyValuePairs(col) {
              col.each { k, v ->
                  println "$k: $v"
              }
          }
          
          printKeyValuePairs [foo: "bar", baz: "wiz"]
          printKeyValuePairs ["this", "would", "be", "cool"]
          

          Would print:

          foo: bar
          baz: wiz
          0: this
          1: would
          2: be
          3: cool
          

          Another approach could be adding an enumerate method to lists (as in Python):

          >>> ["foo", "bar"].enumerate()
          [0:foo, 1:bar]
          

          And then just use the map version of the each/filter/etc methods.

          Or, maybe more groovylistic, have a type conversion from List to Map (I like this on better ^_^):

          >>> ["foo", "bar"] as Map
          [0:foo, 1:bar]
          
          Show
          Demian Ferreiro added a comment - If this approach is taken, I would suggest that in the 2-arity-closure version of the each/find/etc methods the index to be the first parameter to the closure. Therefore, maps and lists can be treated the same way, as the first parameter of these 2-arity-closures will always be the "key" and the second the value. def printKeyValuePairs(col) { col.each { k, v -> println "$k: $v" } } printKeyValuePairs [foo: "bar", baz: "wiz"] printKeyValuePairs ["this", "would", "be", "cool"] Would print: foo: bar baz: wiz 0: this 1: would 2: be 3: cool Another approach could be adding an enumerate method to lists (as in Python ): >>> ["foo", "bar"].enumerate() [0:foo, 1:bar] And then just use the map version of the each/filter/etc methods. Or, maybe more groovylistic , have a type conversion from List to Map (I like this on better ^_^): >>> ["foo", "bar"] as Map [0:foo, 1:bar]
          Hide
          Masato Nagai added a comment -

          I would suggest that in the 2-arity-closure version of the each/find/etc methods the index to be the first parameter to the closure.

          It seems a good idea. If so, what about map.each with three parameters? Will the first parameter be the index just like others?:

          map.each { index, key, value ->
          }
          
          Show
          Masato Nagai added a comment - I would suggest that in the 2-arity-closure version of the each/find/etc methods the index to be the first parameter to the closure. It seems a good idea. If so, what about map.each with three parameters? Will the first parameter be the index just like others?: map.each { index, key, value -> }
          Hide
          ronan michaux added a comment - - edited

          Maybe hide the index logic inside an helper closure "withIndex"

          list.collect(withIndex{item, index -> ...})

          This helper could be used for other methods (every, find, etc.).

          list.find(withIndex{item, index -> ...})
          Show
          ronan michaux added a comment - - edited Maybe hide the index logic inside an helper closure "withIndex" list.collect(withIndex{item, index -> ...}) This helper could be used for other methods (every, find, etc.). list.find(withIndex{item, index -> ...})

            People

            • Assignee:
              Unassigned
              Reporter:
              Guillaume Laforge
            • Votes:
              15 Vote for this issue
              Watchers:
              14 Start watching this issue

              Dates

              • Created:
                Updated: