GROOVY-11964: Additional multi-assignment forms#2489
GROOVY-11964: Additional multi-assignment forms#2489paulk-asert wants to merge 1 commit intoapache:masterfrom
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #2489 +/- ##
==================================================
+ Coverage 67.1114% 67.1650% +0.0536%
- Complexity 31606 31708 +102
==================================================
Files 1451 1453 +2
Lines 122556 122878 +322
Branches 22006 22097 +91
==================================================
+ Hits 82249 82531 +282
- Misses 33224 33232 +8
- Partials 7083 7115 +32
🚀 New features to boost your workflow:
|
| assertScript '''import java.util.stream.Stream | ||
| def (first, *rest) = Stream.of('a', 'b', 'c') | ||
| assert first == 'a' | ||
| assert rest instanceof Iterator |
There was a problem hiding this comment.
I have the feeling it would be better if rest where a stream. to be specific:
´´´
def s = Stream.of('a', 'b', 'c')
def (first, *rest) = s
assert first == 'a'
assert rest instanceof Stream
assert rest == s
´´´
There was a problem hiding this comment.
I was going for a simple mental model: RHS supports getAt(IntRange) or fallback to iterator(), and a Stream is only a .stream() call away.
But I agree, Stream is important, maybe it is worth have a third alternative in the model. I'll see if any complications arise.
There was a problem hiding this comment.
In the GEP, we show these lowerings:
def (h, *t) = list
becomes:
// Path A — RHS supports getAt(IntRange):
def __rhs = rhs
def h = __rhs.getAt(0)
def t = __rhs.getAt(1..-1)
// Path B — RHS iterable but not range-indexable:
def __it = rhs.iterator()
def h = __it.hasNext() ? __it.next() : null
def t = __it
For the Stream case, the simplest implementation would just detect instanceof Stream and produce a lowering something like:
// Path C — RHS is a Stream:
def __rhs = rhs
def __it = __rhs.iterator()
def h = __it.hasNext() ? __it.next() : null
def t = StreamSupport.stream(
Spliterators.spliteratorUnknownSize(__it, 0),
false
).onClose { __rhs.close() }
And this has some simplifications:
- The wrapped tail is sequential and reports no spliterator characteristics (no SIZED, ORDERED, IMMUTABLE, etc.). Capturing the original characteristics requires reading __rhs.spliterator() instead of __rhs.iterator() — but they're mutually exclusive terminal-class ops, so head extraction would have to switch to Spliterator.tryAdvance, which is implementable but adds machinery that doesn't change semantics for the typical destructuring use case.
- Primitive streams (IntStream, LongStream, DoubleStream) fall back to the iterator() path unless you call .boxed().
I do think the Stream use case probably still justifies this but it increases the complexity of the mental model and the education costs in describing the limitations. The lowering above is in some sense an implementation detail. I will look at changing to above and we can over time handle more cases if demand warrants it.
There was a problem hiding this comment.
The PR now incorporates the above suggestion.
There was a problem hiding this comment.
To explain a bit more my way of thinking... What if the stream is "endless" or just "big" (data does not fit in memory)? For example a generator of some kind, or a stream representing a giant file, or... We could do an iterator that wraps the stream and then works one take(1) base I guess, but in a,*b we never return the iterator, but but a list or something alike for b. Of course I am aware that everything falls apart for a,*b,c Here b cannot be the original stream, for this to work it cannot be an endless stream either. Or even a stream that produces more data, that fits in memory.
There was a problem hiding this comment.
These work if that is what you are getting at:
def (hs, *ts) = Stream.iterate(0, n -> n + 2)
assert hs == 0 && ts.limit(4).toList() == [2, 4, 6, 8]
def (hi, *ti) = Iterators.iterate(0, n -> n + 2)
assert hi == 0 && ti.take(4).toList() == [2, 4, 6, 8]
8605c9f to
8f46a0c
Compare
8f46a0c to
838d780
Compare
838d780 to
a41a7a8
Compare
No description provided.