You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@groovy.apache.org by Paul King <pa...@asert.com.au> on 2025/04/09 01:25:21 UTC

[DISCUSS] Lazy findAll, collect, collectMany

Hi folks,

[I sent this to grails dev list but meant to send it here and CC them
for feedback - anyway, it is here now, apologies if you see this
twice.]

I have been looking at the functionality in Groovy-stream[1] and
Gatherers4J[2] lately with a view to filling any gaps in Groovy's
iterator DGM methods. I'm not trying to replicate everything they
contain, just looking for the most useful functionality Groovy might
be missing.

The biggest missing pieces at this point in my mind are lazy (Iterator
return value) variants of findAll, collect, and collectMany. Groovy's
current variants are eager (return collections and lists).
Groovy-stream gets around this by adding stream-named variants:
filter, map, and flatMap.

One option is to break backwards compatibility (Groovy 5 only). So
only for the versions of those methods which take an Iterator as
input, change the return type to be Iterator. Given how widely used
those methods are, I don't think that is an option for us.

Actually, findAll currently doesn't have an Iterator variant, so we
could add that but it would still be a behavioral compatibility
breakage since the Object version is used for Iterators and it returns
a list.

So, we could give up on lazy variants for those methods, but again
given how commonly used they are, that is a pretty big gap.

So, the other option is to provide alternative names. The best to me seem:

(A) findAllLazy, collectLazy, collectManyLazy
(B) findAllThen, collectThen, collectManyThen
(C) filter, map, flatMap
(D) something else?

Option (C) is what Groovy-stream did and would be familiar to Java
Stream users but folks are going to ask, why can't I have that "alias"
for Iterables and arrays, but the intent here is just for the Iterator
variants. I think Lazy best conveys that. Use without "Lazy" for the
eager (think terminal operator) variant and with "Lazy" for the lazy
(think intermediate operator) variant. It also is easier to extend,
the fourth method in terms of gaps is collectEntries, which currently
returns a Map. An Iterator<Map.Entry> return value could be made for
collectEntriesLazy if we wanted.

Note that many of our operators are terminal in nature, find, count*,
inject, etc, so this isn't about doing this for all operators
eventually.

Thoughts?

Paul.


[1] https://github.com/timyates/groovy-stream
[2] https://github.com/tginsberg/gatherers4j

Re: [DISCUSS] Lazy findAll, collect, collectMany

Posted by Paul King <pa...@asert.com.au>.
In any case, I created a draft PR and some interesting points came up
which I think deserve more discussion.

    https://github.com/apache/groovy/pull/2182

I have marked all of the methods as @Incubating for now, in case we
want to merge and tweak later.

What's in the PR? I went with the "Lazy" naming suggestion. As
proposed, I did findAllLazy, collectLazy and collectManyLazy (these
are just for Iterator in/out variants). I also checked for other
candidates which could have this treatment. I think there are only
three. I did implementations for collectEntriesLazy and
findIndexValuesLazy but have left findResultsLazy for now (it's easy
to add).

When coding these, I experimented with the approach Groovy-stream
takes of setting the delegate in the relevant closures. We have
traditionally done this for tap/with but not generally otherwise. I'll
add some examples of the benefits in the related Jira issue:

    https://issues.apache.org/jira/browse/GROOVY-11606

For now, I have also added map, flatMap and filter (just for
Iterators) that instead of using a Closure, use the Functions and
Predicates. These don't set the delegate. For now, these let us
compare and contrast the different approaches and try out some
performance measurements. Unless they prove much faster, or we really
like the idea of them as familiar aliases, I'd expect them to be
removed before Groovy 5 goes GA. I wasn't thinking we'd provide
aliases for non-Iterator variants.

Paul.

On Wed, Apr 9, 2025 at 11:25 AM Paul King <pa...@asert.com.au> wrote:
>
> Hi folks,
>
> [I sent this to grails dev list but meant to send it here and CC them
> for feedback - anyway, it is here now, apologies if you see this
> twice.]
>
> I have been looking at the functionality in Groovy-stream[1] and
> Gatherers4J[2] lately with a view to filling any gaps in Groovy's
> iterator DGM methods. I'm not trying to replicate everything they
> contain, just looking for the most useful functionality Groovy might
> be missing.
>
> The biggest missing pieces at this point in my mind are lazy (Iterator
> return value) variants of findAll, collect, and collectMany. Groovy's
> current variants are eager (return collections and lists).
> Groovy-stream gets around this by adding stream-named variants:
> filter, map, and flatMap.
>
> One option is to break backwards compatibility (Groovy 5 only). So
> only for the versions of those methods which take an Iterator as
> input, change the return type to be Iterator. Given how widely used
> those methods are, I don't think that is an option for us.
>
> Actually, findAll currently doesn't have an Iterator variant, so we
> could add that but it would still be a behavioral compatibility
> breakage since the Object version is used for Iterators and it returns
> a list.
>
> So, we could give up on lazy variants for those methods, but again
> given how commonly used they are, that is a pretty big gap.
>
> So, the other option is to provide alternative names. The best to me seem:
>
> (A) findAllLazy, collectLazy, collectManyLazy
> (B) findAllThen, collectThen, collectManyThen
> (C) filter, map, flatMap
> (D) something else?
>
> Option (C) is what Groovy-stream did and would be familiar to Java
> Stream users but folks are going to ask, why can't I have that "alias"
> for Iterables and arrays, but the intent here is just for the Iterator
> variants. I think Lazy best conveys that. Use without "Lazy" for the
> eager (think terminal operator) variant and with "Lazy" for the lazy
> (think intermediate operator) variant. It also is easier to extend,
> the fourth method in terms of gaps is collectEntries, which currently
> returns a Map. An Iterator<Map.Entry> return value could be made for
> collectEntriesLazy if we wanted.
>
> Note that many of our operators are terminal in nature, find, count*,
> inject, etc, so this isn't about doing this for all operators
> eventually.
>
> Thoughts?
>
> Paul.
>
>
> [1] https://github.com/timyates/groovy-stream
> [2] https://github.com/tginsberg/gatherers4j

Re: [EXT] Re: [DISCUSS] Lazy findAll, collect, collectMany

Posted by Paul King <pa...@asert.com.au>.
On Thu, Apr 10, 2025 at 1:49 AM Milles, Eric (TR Technology) via dev
<de...@groovy.apache.org> wrote:
>
> My general concern is the number of overloads and variants.

Yes, maybe we break compatibility for Groovy 5 and just keep the
current names. That would keep name overloads/maintenance down. I
think with gatherers becoming more familiar and virtual threads,
having lazy options will become more commonplace. If we ever wanted to
refresh Gpars with virtual threads, having a clean slate would be a
good thing.

> collect, collectMany and findAll would probably be named something else if created anew today.  Adding to this creates confusion IMO.  Do you have a Java stream and Groovy as-is version to compare against for each proposed new method?  It helps me at least to see what it would take to do the same without the extension.

You probably need a library like Gatherers4j in combination with
normal streams to cover off all the methods. That was in fact what
kick-started the journey I have been on for the last few weeks. I have
a blog post which does just that. I stopped working on it when these
issues came up. I'll revamp and send in its current raw form, though
it's not quite ready for publication.

Paul.

> ________________________________
> From: Jochen Theodorou <bl...@gmx.org>
> Sent: Wednesday, April 9, 2025 6:21 AM
> To: dev@groovy.apache.org <de...@groovy.apache.org>
> Subject: [EXT] Re: [DISCUSS] Lazy findAll, collect, collectMany
>
> External Email: Use caution with links and attachments.
>
> On 09.04.25 03:25, Paul King wrote:
> > Hi folks,
> >
> > [I sent this to grails dev list but meant to send it here and CC them
> > for feedback - anyway, it is here now, apologies if you see this
> > twice.]
> >
> > I have been looking at the functionality in Groovy-stream[1] and
> > Gatherers4J[2] lately with a view to filling any gaps in Groovy's
> > iterator DGM methods. I'm not trying to replicate everything they
> > contain, just looking for the most useful functionality Groovy might
> > be missing.
> >
> > The biggest missing pieces at this point in my mind are lazy (Iterator
> > return value) variants of findAll, collect, and collectMany. Groovy's
> > current variants are eager (return collections and lists).
> > Groovy-stream gets around this by adding stream-named variants:
> > filter, map, and flatMap.
>
>
> does Iterator really automatically mean lazy? Is this possible then?
>
> def l = [1,2,3]
> def iterator = l.iterator().findAll{it>1}
> l[0] = 20
> assert iterator.toList() == [20,2,3]
>
> The combination of lazy and mutable can be problematic. Also, can I do
> iterator.toList() multiple times?
>
> > One option is to break backwards compatibility (Groovy 5 only). So
> > only for the versions of those methods which take an Iterator as
> > input, change the return type to be Iterator. Given how widely used
> > those methods are, I don't think that is an option for us.
>
> well... we could have lazyIt() which return a LazyIterator. that would
> make things clear.
>
> > Actually, findAll currently doesn't have an Iterator variant, so we
> > could add that but it would still be a behavioral compatibility
> > breakage since the Object version is used for Iterators and it returns
> > a list.
> >
> > So, we could give up on lazy variants for those methods, but again
> > given how commonly used they are, that is a pretty big gap.
> >
> > So, the other option is to provide alternative names. The best to me seem:
> >
> > (A) findAllLazy, collectLazy, collectManyLazy
> > (B) findAllThen, collectThen, collectManyThen
> > (C) filter, map, flatMap
> > (D) something else?
>
> (E) lazyIt()
>
> I am pretty sure it will not be considered as because it is a bit
> ugly... but frankly I am no fan of A at all for the very same reason. B
> I find more nice because it is more semantics based.
>
> hmm... what happens if you mix lazy and non-lazy... like for example
> findAllthen(...).findAll(...)
>
> > Option (C) is what Groovy-stream did and would be familiar to Java
> > Stream users but folks are going to ask, why can't I have that "alias"
> > for Iterables and arrays, but the intent here is just for the Iterator
> > variants. I think Lazy best conveys that. Use without "Lazy" for the
> > eager (think terminal operator) variant and with "Lazy" for the lazy
> > (think intermediate operator) variant. It also is easier to extend,
> > the fourth method in terms of gaps is collectEntries, which currently
> > returns a Map. An Iterator<Map.Entry> return value could be made for
> > collectEntriesLazy if we wanted.
>
> somehow does not really convince me all.
>
> > Note that many of our operators are terminal in nature, find, count*,
> > inject, etc, so this isn't about doing this for all operators
> > eventually.
>
> but findAllThen does not come over as terminal. If they are supposed to,
> then I don't find the names fitting in the cases of A and B.
>
> bye jochen
>

Re: [EXT] Re: [DISCUSS] Lazy findAll, collect, collectMany

Posted by "Milles, Eric (TR Technology) via dev" <de...@groovy.apache.org>.
My general concern is the number of overloads and variants.  collect, collectMany and findAll would probably be named something else if created anew today.  Adding to this creates confusion IMO.  Do you have a Java stream and Groovy as-is version to compare against for each proposed new method?  It helps me at least to see what it would take to do the same without the extension.

________________________________
From: Jochen Theodorou <bl...@gmx.org>
Sent: Wednesday, April 9, 2025 6:21 AM
To: dev@groovy.apache.org <de...@groovy.apache.org>
Subject: [EXT] Re: [DISCUSS] Lazy findAll, collect, collectMany

External Email: Use caution with links and attachments.

On 09.04.25 03:25, Paul King wrote:
> Hi folks,
>
> [I sent this to grails dev list but meant to send it here and CC them
> for feedback - anyway, it is here now, apologies if you see this
> twice.]
>
> I have been looking at the functionality in Groovy-stream[1] and
> Gatherers4J[2] lately with a view to filling any gaps in Groovy's
> iterator DGM methods. I'm not trying to replicate everything they
> contain, just looking for the most useful functionality Groovy might
> be missing.
>
> The biggest missing pieces at this point in my mind are lazy (Iterator
> return value) variants of findAll, collect, and collectMany. Groovy's
> current variants are eager (return collections and lists).
> Groovy-stream gets around this by adding stream-named variants:
> filter, map, and flatMap.


does Iterator really automatically mean lazy? Is this possible then?

def l = [1,2,3]
def iterator = l.iterator().findAll{it>1}
l[0] = 20
assert iterator.toList() == [20,2,3]

The combination of lazy and mutable can be problematic. Also, can I do
iterator.toList() multiple times?

> One option is to break backwards compatibility (Groovy 5 only). So
> only for the versions of those methods which take an Iterator as
> input, change the return type to be Iterator. Given how widely used
> those methods are, I don't think that is an option for us.

well... we could have lazyIt() which return a LazyIterator. that would
make things clear.

> Actually, findAll currently doesn't have an Iterator variant, so we
> could add that but it would still be a behavioral compatibility
> breakage since the Object version is used for Iterators and it returns
> a list.
>
> So, we could give up on lazy variants for those methods, but again
> given how commonly used they are, that is a pretty big gap.
>
> So, the other option is to provide alternative names. The best to me seem:
>
> (A) findAllLazy, collectLazy, collectManyLazy
> (B) findAllThen, collectThen, collectManyThen
> (C) filter, map, flatMap
> (D) something else?

(E) lazyIt()

I am pretty sure it will not be considered as because it is a bit
ugly... but frankly I am no fan of A at all for the very same reason. B
I find more nice because it is more semantics based.

hmm... what happens if you mix lazy and non-lazy... like for example
findAllthen(...).findAll(...)

> Option (C) is what Groovy-stream did and would be familiar to Java
> Stream users but folks are going to ask, why can't I have that "alias"
> for Iterables and arrays, but the intent here is just for the Iterator
> variants. I think Lazy best conveys that. Use without "Lazy" for the
> eager (think terminal operator) variant and with "Lazy" for the lazy
> (think intermediate operator) variant. It also is easier to extend,
> the fourth method in terms of gaps is collectEntries, which currently
> returns a Map. An Iterator<Map.Entry> return value could be made for
> collectEntriesLazy if we wanted.

somehow does not really convince me all.

> Note that many of our operators are terminal in nature, find, count*,
> inject, etc, so this isn't about doing this for all operators
> eventually.

but findAllThen does not come over as terminal. If they are supposed to,
then I don't find the names fitting in the cases of A and B.

bye jochen


Re: [DISCUSS] Lazy findAll, collect, collectMany

Posted by MG <mg...@arscreat.com>.
Doing a bit of thinking out loud on this:

 1. The "Lazy" postfix to me seems to indicate very clearly what trait
    these family of methods share and why they are here.
     1. I also feel that the methods would not be used that often, since
        for typical collection sizes performance differences should be
        negligible.
     2. Therefore I feel that adding 4 letters at the end would be ok here.
 2. "It" for me indicates the English word "it" (German: "es"), not a
    short form of Iterator.
     1. That also coincides with the "it" name for an implicit single
        closure argument in Groovy.
         1. (In our framework we e.g. also use "it" with similar
            semantics as the name for a singleton/root class member.)
     2. "Iter" could potentially be used to get around that, but I would
        prefer "Lazy" also in this case, since it could be overloaded to
        be used for something else than an iterator without the need to
        introduce new method names.
 3. While "Then" as a postfix implies a chain of operations, I do not
    see where it implies these operations being lazy (?).
     1. So while it would be viable, "Lazy" would also be the winner
        here in my book.
 4. filter, map, flatMap:
     1. While these are widely used in other languages, their Groovy
        counterparts are more intuitive to me (Note: I knew these before
        I encountered Groovy).
         1. (The only exception here is Groovy's "collectMany", which
            should imho be "collectFlattened" o.s. (but also not
            "collectFlat") ).
     2. So I feel they should probably continue to be confined to the
        "Java source code compatibility" corner of Groovy :-)

Cheers,
mg


On 09/04/2025 13:21, Jochen Theodorou wrote:
> On 09.04.25 03:25, Paul King wrote:
>> Hi folks,
>>
>> [I sent this to grails dev list but meant to send it here and CC them
>> for feedback - anyway, it is here now, apologies if you see this
>> twice.]
>>
>> I have been looking at the functionality in Groovy-stream[1] and
>> Gatherers4J[2] lately with a view to filling any gaps in Groovy's
>> iterator DGM methods. I'm not trying to replicate everything they
>> contain, just looking for the most useful functionality Groovy might
>> be missing.
>>
>> The biggest missing pieces at this point in my mind are lazy (Iterator
>> return value) variants of findAll, collect, and collectMany. Groovy's
>> current variants are eager (return collections and lists).
>> Groovy-stream gets around this by adding stream-named variants:
>> filter, map, and flatMap.
>
>
> does Iterator really automatically mean lazy? Is this possible then?
>
> def l = [1,2,3]
> def iterator = l.iterator().findAll{it>1}
> l[0] = 20
> assert iterator.toList() == [20,2,3]
>
> The combination of lazy and mutable can be problematic. Also, can I do
> iterator.toList() multiple times?
>
>> One option is to break backwards compatibility (Groovy 5 only). So
>> only for the versions of those methods which take an Iterator as
>> input, change the return type to be Iterator. Given how widely used
>> those methods are, I don't think that is an option for us.
>
> well... we could have lazyIt() which return a LazyIterator. that would
> make things clear.
>
>> Actually, findAll currently doesn't have an Iterator variant, so we
>> could add that but it would still be a behavioral compatibility
>> breakage since the Object version is used for Iterators and it returns
>> a list.
>>
>> So, we could give up on lazy variants for those methods, but again
>> given how commonly used they are, that is a pretty big gap.
>>
>> So, the other option is to provide alternative names. The best to me 
>> seem:
>>
>> (A) findAllLazy, collectLazy, collectManyLazy
>> (B) findAllThen, collectThen, collectManyThen
>> (C) filter, map, flatMap
>> (D) something else?
>
> (E) lazyIt()
>
> I am pretty sure it will not be considered as because it is a bit
> ugly... but frankly I am no fan of A at all for the very same reason. B
> I find more nice because it is more semantics based.
>
> hmm... what happens if you mix lazy and non-lazy... like for example
> findAllthen(...).findAll(...)
>
>> Option (C) is what Groovy-stream did and would be familiar to Java
>> Stream users but folks are going to ask, why can't I have that "alias"
>> for Iterables and arrays, but the intent here is just for the Iterator
>> variants. I think Lazy best conveys that. Use without "Lazy" for the
>> eager (think terminal operator) variant and with "Lazy" for the lazy
>> (think intermediate operator) variant. It also is easier to extend,
>> the fourth method in terms of gaps is collectEntries, which currently
>> returns a Map. An Iterator<Map.Entry> return value could be made for
>> collectEntriesLazy if we wanted.
>
> somehow does not really convince me all.
>
>> Note that many of our operators are terminal in nature, find, count*,
>> inject, etc, so this isn't about doing this for all operators
>> eventually.
>
> but findAllThen does not come over as terminal. If they are supposed to,
> then I don't find the names fitting in the cases of A and B.
>
> bye jochen
>

Re: [DISCUSS] Lazy findAll, collect, collectMany

Posted by Paul King <pa...@asert.com.au>.
Most comments inline, but just some more background. We have many
existing operators on Iterators that are "lazy". By lazy, I just mean
return an iterator. Lazy in this context is a relative term. While all
the methods return an iterator, they have varying degrees of laziness
(repeat has storage implications, sort and reverse read in the whole
source before emitting elements lazily). Here is the list:

chop, collate, drop, dropRight, dropWhile, flatten, flattenMany,
indexed, init, injectAll, interleave, plus, repeat, reverse, sort,
tail, take, takeWhile, toSorted, toUnique, unique, withIndex, zip,
zipAll

There are also a bunch of operators that are eager and are
terminal-like in nature, so there is no benefit in a lazy version:

any, average, every, find/findResult/findIndexOf (singular), count,
countBy, inject, etc.

There are 3 common and 3 less common operators that are currently
eager that would benefit from having a lazy (read Iterator) return
type:

common: collect, collectMany, findAll
less common: collectEntries, findIndexValues, findResults

One option is to break compatibility and just change the return type
to an iterator (plus provide a bridge method for pre-compiled @CS
code). I would be less concerned about doing this for the three less
common methods. We'd release not it of course. Actually, collectMany
usage is probably rare too. For collect and findAll, I suspect their
usage with Iterators is also rare but much less so than the others.

If we don't want to break compatibility, we need to keep the eager one
and provide a different name to indicate the "lazy/non-terminal" one.
"Lazy" and "Then" suffixes were meant to do that.

Other comments in line.

On Wed, Apr 9, 2025 at 9:22 PM Jochen Theodorou <bl...@gmx.org> wrote:
>
> On 09.04.25 03:25, Paul King wrote:
> > Hi folks,
> >
> > [I sent this to grails dev list but meant to send it here and CC them
> > for feedback - anyway, it is here now, apologies if you see this
> > twice.]
> >
> > I have been looking at the functionality in Groovy-stream[1] and
> > Gatherers4J[2] lately with a view to filling any gaps in Groovy's
> > iterator DGM methods. I'm not trying to replicate everything they
> > contain, just looking for the most useful functionality Groovy might
> > be missing.
> >
> > The biggest missing pieces at this point in my mind are lazy (Iterator
> > return value) variants of findAll, collect, and collectMany. Groovy's
> > current variants are eager (return collections and lists).
> > Groovy-stream gets around this by adding stream-named variants:
> > filter, map, and flatMap.
>
>
> does Iterator really automatically mean lazy? Is this possible then?
>
> def l = [1,2,3]
> def iterator = l.iterator().findAll{it>1}
> l[0] = 20
> assert iterator.toList() == [20,2,3]

When I say lazy, we just mean a normal Java iterator. It is lazy in
the sense that it won't return elements until you call next(). The
findAllLazy method does prime itself by finding the first element so
it knows what to return for hasNext(). That means we can change (if we
don't mind potential chaos) the underlying collection backing the
iterator. In our case, 2 is the first element, satisfying the
predicate so we can change anything after that:

def source = [1,2,3]
def list = source.iterator().findAll{ it>1 } // eager
source[0] = 20
assert list == [2,3]

source = [1,2,3]
def iterator = source.iterator().findAllLazy{ it>1 }
source[0] = 20 // too late, primed past here already
assert iterator.toList() == [2,3]

source = [3,2,1]
iterator = source.iterator().findAllLazy{ it>1 }
source[2] = 20
assert iterator.toList() == [3,2,20]


> The combination of lazy and mutable can be problematic. Also, can I do
> iterator.toList() multiple times?

These are just normal Java iterators, calling an exhausted iterator
throws a no such element exception.

> > One option is to break backwards compatibility (Groovy 5 only). So
> > only for the versions of those methods which take an Iterator as
> > input, change the return type to be Iterator. Given how widely used
> > those methods are, I don't think that is an option for us.
>
> well... we could have lazyIt() which return a LazyIterator. that would
> make things clear.

Yes, we could do something like create a wrapped (Iterable/Array) and
have an unchanged method name on the wrapper class. We don't do that
for the other 24 existing Iterator methods listed at the start of this
message.

> > Actually, findAll currently doesn't have an Iterator variant, so we
> > could add that but it would still be a behavioral compatibility
> > breakage since the Object version is used for Iterators and it returns
> > a list.
> >
> > So, we could give up on lazy variants for those methods, but again
> > given how commonly used they are, that is a pretty big gap.
> >
> > So, the other option is to provide alternative names. The best to me seem:
> >
> > (A) findAllLazy, collectLazy, collectManyLazy
> > (B) findAllThen, collectThen, collectManyThen
> > (C) filter, map, flatMap
> > (D) something else?
>
> (E) lazyIt()

See above.

> I am pretty sure it will not be considered as because it is a bit
> ugly... but frankly I am no fan of A at all for the very same reason. B
> I find more nice because it is more semantics based.
>
> hmm... what happens if you mix lazy and non-lazy... like for example
> findAllthen(...).findAll(...)

We don't do anything special here it will be normal Java semantics:

Iterator + eager = eager.

> > Option (C) is what Groovy-stream did and would be familiar to Java
> > Stream users but folks are going to ask, why can't I have that "alias"
> > for Iterables and arrays, but the intent here is just for the Iterator
> > variants. I think Lazy best conveys that. Use without "Lazy" for the
> > eager (think terminal operator) variant and with "Lazy" for the lazy
> > (think intermediate operator) variant. It also is easier to extend,
> > the fourth method in terms of gaps is collectEntries, which currently
> > returns a Map. An Iterator<Map.Entry> return value could be made for
> > collectEntriesLazy if we wanted.
>
> somehow does not really convince me all.
>
> > Note that many of our operators are terminal in nature, find, count*,
> > inject, etc, so this isn't about doing this for all operators
> > eventually.
>
> but findAllThen does not come over as terminal. If they are supposed to,
> then I don't find the names fitting in the cases of A and B.

findAllThen isn't  meant to hint at being terminal, it was meant to
hint at laziness since with lazy methods you'd typically chain the
next method.

> bye jochen
>

Re: [DISCUSS] Lazy findAll, collect, collectMany

Posted by Jochen Theodorou <bl...@gmx.org>.
On 09.04.25 03:25, Paul King wrote:
> Hi folks,
>
> [I sent this to grails dev list but meant to send it here and CC them
> for feedback - anyway, it is here now, apologies if you see this
> twice.]
>
> I have been looking at the functionality in Groovy-stream[1] and
> Gatherers4J[2] lately with a view to filling any gaps in Groovy's
> iterator DGM methods. I'm not trying to replicate everything they
> contain, just looking for the most useful functionality Groovy might
> be missing.
>
> The biggest missing pieces at this point in my mind are lazy (Iterator
> return value) variants of findAll, collect, and collectMany. Groovy's
> current variants are eager (return collections and lists).
> Groovy-stream gets around this by adding stream-named variants:
> filter, map, and flatMap.


does Iterator really automatically mean lazy? Is this possible then?

def l = [1,2,3]
def iterator = l.iterator().findAll{it>1}
l[0] = 20
assert iterator.toList() == [20,2,3]

The combination of lazy and mutable can be problematic. Also, can I do
iterator.toList() multiple times?

> One option is to break backwards compatibility (Groovy 5 only). So
> only for the versions of those methods which take an Iterator as
> input, change the return type to be Iterator. Given how widely used
> those methods are, I don't think that is an option for us.

well... we could have lazyIt() which return a LazyIterator. that would
make things clear.

> Actually, findAll currently doesn't have an Iterator variant, so we
> could add that but it would still be a behavioral compatibility
> breakage since the Object version is used for Iterators and it returns
> a list.
>
> So, we could give up on lazy variants for those methods, but again
> given how commonly used they are, that is a pretty big gap.
>
> So, the other option is to provide alternative names. The best to me seem:
>
> (A) findAllLazy, collectLazy, collectManyLazy
> (B) findAllThen, collectThen, collectManyThen
> (C) filter, map, flatMap
> (D) something else?

(E) lazyIt()

I am pretty sure it will not be considered as because it is a bit
ugly... but frankly I am no fan of A at all for the very same reason. B
I find more nice because it is more semantics based.

hmm... what happens if you mix lazy and non-lazy... like for example
findAllthen(...).findAll(...)

> Option (C) is what Groovy-stream did and would be familiar to Java
> Stream users but folks are going to ask, why can't I have that "alias"
> for Iterables and arrays, but the intent here is just for the Iterator
> variants. I think Lazy best conveys that. Use without "Lazy" for the
> eager (think terminal operator) variant and with "Lazy" for the lazy
> (think intermediate operator) variant. It also is easier to extend,
> the fourth method in terms of gaps is collectEntries, which currently
> returns a Map. An Iterator<Map.Entry> return value could be made for
> collectEntriesLazy if we wanted.

somehow does not really convince me all.

> Note that many of our operators are terminal in nature, find, count*,
> inject, etc, so this isn't about doing this for all operators
> eventually.

but findAllThen does not come over as terminal. If they are supposed to,
then I don't find the names fitting in the cases of A and B.

bye jochen