You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2023/04/28 14:43:12 UTC

[groovy-website] branch asf-site updated: add sequenced collections blog

This is an automated email from the ASF dual-hosted git repository.

paulk pushed a commit to branch asf-site
in repository https://gitbox.apache.org/repos/asf/groovy-website.git


The following commit(s) were added to refs/heads/asf-site by this push:
     new 0ea48d2  add sequenced collections blog
0ea48d2 is described below

commit 0ea48d26b762a0ffc8c67c94fd336820bf860319
Author: Paul King <pa...@asert.com.au>
AuthorDate: Sat Apr 29 00:43:00 2023 +1000

    add sequenced collections blog
---
 .../site/blog/groovy-sequenced-collections.adoc    | 299 +++++++++++++++++++++
 1 file changed, 299 insertions(+)

diff --git a/site/src/site/blog/groovy-sequenced-collections.adoc b/site/src/site/blog/groovy-sequenced-collections.adoc
new file mode 100644
index 0000000..092a364
--- /dev/null
+++ b/site/src/site/blog/groovy-sequenced-collections.adoc
@@ -0,0 +1,299 @@
+= Groovy and Sequenced Collections (JEP-431)
+Paul King
+:revdate: 2023-04-29T09:00:00+00:00
+:keywords: groovy, jep431, collections
+:description: This post looks at Groovy support for sequenced collections.
+
+An exciting feature coming in JDK21 is
+https://openjdk.org/jeps/431[Sequenced Collections]
+which improve processing for collections which have
+a defined encounter order. Additional details about the new
+functionality can be found in the <<Additional References>> section later.
+
+Since Groovy is designed to work very closely with the JDK libraries,
+once JDK21 is released, Groovy will get the new functionality "for free".
+Groovy however has its own solutions to some of the problems which JEP-431
+addresses, so this post looks at the existing functionality (which you can use with older JDKs)
+and the new functionality you can use once JDK21 is generally available,
+and you upgrade to that JDK version.
+
+The examples in this post use JDK21ea Build 20 (2023/4/27) and Groovy 4.0.11.
+While EA builds come with numerous disclaimers warning that changes or removal
+of functionality might occur before general release,
+we'd expect the examples to work for subsequent releases.
+We'll post an update if anything changes.
+
+== Sequenced Collections Summary
+
+Sequenced Collections adds three new interfaces: `SequencedSet`,
+`SequencedCollection`, and `SequencedMap`. Each interface adds
+some new methods which we'll encounter in later examples.
+In the rest of this post, we'll explain the new sequenced collections
+functionality by looking at various scenarios you might face when processing
+various collections.
+
+== Accessing the first and last element
+
+=== JDK before JEP-431
+
+Something as simple as accessing the first and last elements
+for various collection types isn't consistent or as easy as might be expected (hence JEP-431).
+Here are some examples:
+
+|===
+|Collection Type |First element |Last element
+
+|`List`
+|`list.get(0)`
+|`list.get(list.size()-1)`
+
+|`Deque`
+|`deque.getFirst()`
+|`deque.getLast()`
+
+|`Set`
+|`set.iterator().next()` or +
+`set.stream().findFirst().get()`
+| requires iterating through the set
+
+|`SortedSet`
+|`set.first()`
+|`set.last()`
+|===
+
+=== JDK after JEP-431
+
+After JEP-431, this improves greatly:
+
+|===
+|Collection Type |First element |Last element
+
+|`List`, `Deque`, `Set`
+|`collection.getFirst()`
+|`collection.getLast()`
+|===
+
+=== Groovy before JEP-431
+
+Groovy provides extension methods, `first()` and `last()`, for arrays and any `Iterable`, with
+various optimised versions when it makes sense. The _subscript_ operator
+is provided for any class having a `getAt` method. There are built-in implementations
+for collections, arrays and numerous other classes.
+
+|===
+|Aggregate Type |First element |Last element
+
+|`List`, `Deque`, `Set`, array
+|`aggregate[0]` or `aggregate.first()`
+|`aggregate[-1]` or `aggregate.last()`
+|===
+
+Groovy also provides _take_ extension methods which could be used here. You could use
+`list.take(1)` to get a list containing just the first element of the original list,
+and `takeRight(1)` for a list containing just the last element. These work across
+all the different aggregate types too.
+
+=== Groovy after JEP-431
+
+No special support is yet added for JEP-431.
+So Groovy functionality will be existing functionality
+plus the new JDK functionality.
+
+|===
+|Aggregate Type |First element |Last element
+
+|array
+|`array[0]` +
+`array.first()` +
+|`array[-1]` +
+`array.last()`
+
+|`List`, `Deque`, `Set`
+|`collection[0]` +
+`collection.first()` +
+`collection.getFirst()` +
+`collection.first`
+|`collection[-1]` +
+`collection.last()` +
+`collection.getLast()` +
+`collection.last`
+|===
+
+For now, Groovy's approach provides uniformity also across arrays (and potentially other classes).
+Folks should not feel any urgent pressure to use JEP-431 functionality for this scenario
+with an important caveat. Over time, additional collection types may emerge which might
+provide more efficient implementations for `getFirst()` or `getLast()` in which case it would
+be beneficial to use those methods.
+
+Future Groovy versions may provide specialised sequenced collections support.
+The `first()` and `last()` extension methods may be implemented in terms
+of `getFirst()` and `getLast()`.
+We might also extend sequenced collection methods to arrays (and maybe other classes),
+though it isn't a high priority for now.
+
+=== Examples
+
+[source,groovy]
+----
+List list = [1, 2, 3]
+assert list.get(0) == 1
+assert list[0] == 1
+assert list.first() == 1
+assert list.getFirst() == 1                                // NEW
+assert list.first == 1                                     // NEW
+
+assert list.get(list.size() - 1) == 3
+assert list[-1] == 3
+assert list.last() == 3
+assert list.getLast() == 3                                 // NEW
+assert list.last == 3                                      // NEW
+
+LinkedList deque = [1, 2, 3]
+assert deque.getFirst() == 1
+assert deque[0] == 1
+assert deque.first() == 1
+assert deque.first == 1
+
+assert deque[-1] == 3
+assert deque.last() == 3
+assert deque.getLast() == 3                                // NEW
+assert deque.last == 3                                     // NEW
+
+LinkedHashSet set = [1, 2, 3]
+assert set.iterator().next() == 1
+assert set[0] == 1
+assert set.first() == 1
+assert set.getFirst() == 1                                 // NEW
+assert set.first == 1                                      // NEW
+
+assert set[-1] == 3
+assert list.last() == 3
+assert set.getLast() == 3                                  // NEW
+assert set.last == 3                                       // NEW
+
+TreeSet sortedSet = [2, 4, 1, 3]
+assert sortedSet[0] == 1
+assert sortedSet.first() == 1
+assert sortedSet.getFirst() == 1                           // NEW
+assert sortedSet.first == 1                                // NEW
+
+assert sortedSet[-1] == 4
+assert sortedSet.last() == 4
+assert sortedSet.getLast() == 4                            // NEW
+assert sortedSet.last == 4                                 // NEW
+
+Integer[] array = [1, 2, 3]
+assert array[0] == 1
+assert array.first() == 1
+assert array[-1] == 3
+assert array.last() == 3
+----
+
+== Removing first or last elements
+
+If you need to mutate a collection, removing the first or last element,
+Groovy doesn't offer consistent extension methods across all the aggregate types.
+You can use the JDK `remove(0)` method from `List` to remove the first element from the list (and Groovy also provides a nice `removeAt(0)` alias).
+Groovy also provides `removeLast()` for lists.
+Given this, the `removeFirst()` and `removeLast()`
+methods from `SequencedCollection` are a nice addition.
+
+If you want to create a new aggregate which is the same as the original
+but with the first (or last) element removed, Groovy provides
+`tail()` and `drop(1)` (or `init()` and `dropRight(1)`).
+
+== Adding elements to the front/end
+
+If you need to mutate a collection, adding elements at the front or end,
+Groovy doesn't offer consistent extension methods across all the aggregate types.
+You'd normally use `add(element)` or `add(0, element)` for lists.
+So the `addFirst()` and `addLast()`
+methods from `SequencedCollection` are a nice addition.
+Groovy does offer the `leftShift` operator (`<<`) as another way to append to the end of a list.
+
+== Working with reversed collections
+
+Another area tackled by JEP-431 is improved consistency for
+working with a collection in reverse order.
+Groovy already offers some enhancements for this scenario
+with `reverseEach` and `asReversed` extension methods.
+These methods aren't available for all sets, e.g. not for `LinkedHashSet`
+but only `NavigableSet` instances. The `asReversed` method
+creates a new collection rather than a view that is provided by
+JEP-431s `reversed()` method. There are times when the latter might be preferred.
+So, all in all, this functionality provided by JEP-431 is most welcome.
+
+|===
+|Collection Type |Before JEP-431 |After JEP-431 |Groovy
+
+|`List`
+|use `list.listIterator(list.size()).previous()`
+| `list.reversed()`
+| `list.reverseEach` +
+`list.asReversed()`
+
+|`Deque`
+|use `deque.descendingIterator()`
+|`deque.reversed()`
+| `deque.reverseEach` +
+`deque.asReversed()`
+
+|`NavigableSet`
+|use `set.descendingSet()`
+|`set.reversed()`
+| `set.reverseEach` +
+`set.asReversed()`
+
+|`Set`
+|N/A
+|`set.reversed()`
+|N/A
+|===
+
+=== Examples
+
+[source,groovy]
+----
+var result = []
+list.reverseEach { result << it }
+assert result == [3, 2, 1]
+assert list.asReversed() == [3, 2, 1]
+assert list.reversed() == [3, 2, 1]                        // NEW
+
+result = []
+deque.reverseEach { result << it }
+assert result == [3, 2, 1]
+assert deque.asReversed() == [3, 2, 1]
+assert deque.reversed() == [3, 2, 1]                       // NEW
+
+result = []
+assert set.reversed() == [3, 2, 1] as Set                  // NEW
+
+result = []
+sortedSet.reverseEach { result << it }
+assert result == [4, 3, 2, 1]
+assert sortedSet.asReversed() == [4, 3, 2, 1] as Set
+assert sortedSet.reversed() == [4, 3, 2, 1] as Set         // NEW
+
+var map = [a: 1, b: 2]
+result = []
+map.reverseEach { k, v -> result << [k, v] }
+assert result == [['b', 2], ['a', 1]]
+assert map.reversed() == [b:2, a:1]                        // NEW
+----
+
+
+== Additional References
+
+* https://openjdk.org/jeps/431[JEP-431 Proposal]
+* https://www.infoworld.com/article/3689880/jdk-21-the-new-features-in-java-21.html[Summary of features coming in JDK21] (Paul Krill on Infoworld)
+* https://www.youtube.com/watch?v=9G_0el3RWPE[Inside Java Newscast #45] (with Nicolai)
+* https://inside.java/2023/04/25/podcast-031/[Inside Java Podcast Episode 31] (Ana-Maria Mihalceanu with Stuart Marks)
+* https://www.infoq.com/news/2023/03/collections-framework-makeover/[] (A N M Bazlur Rahman on InfoQ)
+* https://groovy.apache.org/blog/groovy-list-processing-cheat-sheet[Groovy list processing cheat sheet]
+
+== Conclusion
+
+We have had a quick look at using JEP-431 functionality with Groovy.
+While Groovy already offers some of the functionality which JEP-431 provides,
+it certainly looks like a nice addition to the JDK.