Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Request: Splice for Mojo::Collection #1350

Closed
srchulo opened this issue May 10, 2019 · 16 comments
Closed

Feature Request: Splice for Mojo::Collection #1350

srchulo opened this issue May 10, 2019 · 16 comments
Assignees

Comments

@srchulo
Copy link

srchulo commented May 10, 2019

I think this could be a useful method. It would remove the elements from the existing collection, and return a new collection with those elements. I'd be happy to help with a pull request and tests if this seems like a good idea.

https://perldoc.perl.org/functions/splice.html

@srchulo
Copy link
Author

srchulo commented May 10, 2019

Currently I get around this like this:

my $spliced_c = c(splice @$c, $offset, $length);

@s1037989
Copy link
Contributor

There are times when I'd like to chain splice as well! 👍

@CandyAngel CandyAngel self-assigned this Jun 5, 2019
@CandyAngel
Copy link
Contributor

As you have to hold a reference to the original collection to use the leftovers, splice would have to always be the first method in the chain. For example, this is the same as slice as the flattened collection is lost and $original is unchanged.

my $original = c([0, 1], [0, 2]);
my $spliced = $original->flatten->splice(1, 2);

Unless you plan on using tap to store it, which could easily get lost in a long chain..

my $original = c([0, 1], [0, 2]);
my $spliced = $original->flatten->tap(sub { $original = $_ })->splice(1, 2);

Do you have an example where having splice available would make things clearer? Perhaps one where it isn't the first in the chain?

Creating a collection role for this (and your #1320 methods) is also an option to bear in mind if this isn't introduced to Mojo::Collection :)

@jhthorsen
Copy link
Member

I think splice() in a chain gets very awkward, so based on that I'm not very positive to adding this Mojo::Collection. I do wonder though if we could add a bit more logic to slice() so we don't get undef elements. The one-liner below prints "2" when I expect it to print "0":

perl -Mojo -le'my $c=c(1,2,3); print $c->slice(4, 5)->size'

I could be off topic here, but the code example @srchulo looks more like "slice" than "splice".

@Grinnz
Copy link
Contributor

Grinnz commented Jun 5, 2019

I think that slice behavior is consistent with list slicing. @foo[4,5] will also return two undefs from an array without those indexes. There is always ->compact.

@s1037989
Copy link
Contributor

s1037989 commented Jun 5, 2019

I do wonder though if we could add a bit more logic to slice()

Speaking of slice(), I've often wanted the ability to slice the end, instead of the beginning. For example, When I'm dealing with a large collection, I might want to do some testing and grab the first 100.

$coll->slice(0..99);

But sometimes, I need to focus more on the latter elements, so it'd be nice to slice the end.

$coll->slice(-99..-0);

Is there a way that slicing could grab to the end? Perhaps by passing a callback if necessary to provide access to $_?

$coll->slice(sub{-99 .. $#_});

@srchulo
Copy link
Author

srchulo commented Jun 5, 2019

@CandyAngel So the example that I ran into I would have a reference to $original, and splice was the first in the chain. I was using splice to do batches of 100:

while (my $batch = $original->splice(0, 100)) {
    # ...
}

@jhthorsen Is the reason that it's awkward in a chain because it modifies the original collection? I would think that methods could be useful in Mojo::Collection that aren't part of a chain.

@Grinnz
Copy link
Contributor

Grinnz commented Jun 5, 2019

Is there a way that slicing could grab to the end?

Would wrappers of the new List::Util head and tail functions suit your use case?

@jhthorsen
Copy link
Member

jhthorsen commented Jun 5, 2019

@srchulo: I think it gets awkward when doing $c2 = $c1->splice($offset, $len, @list), since I would expect it to mutate $l1 and return the removed elements.

@jhthorsen
Copy link
Member

@Grinnz: Using compact() would not result in the same thing, in the case of c(1, 2, 3, undef, '', '', 0, 4)

@Grinnz
Copy link
Contributor

Grinnz commented Jun 5, 2019

I was using splice to do batches of 100:

The List::UtilsBy role could be a good way to do this.

(I do the same thing with splice but in this case, I don't think there's any sensible and intuitive way to add the behavior at least to the core Mojo::Collection.)

@s1037989
Copy link
Contributor

s1037989 commented Jun 5, 2019

Is there a way that slicing could grab to the end?

Would wrappers of the new List::Util head and tail functions suit your use case?

Yes! That looks like it would be great!!

@CandyAngel
Copy link
Contributor

CandyAngel commented Jun 5, 2019

while (my $batch = $original->splice(0, 100)) {
    # ...
}

This example would require splice to return a false value instead of an empty collection, which would make it unusual from the other methods in a third way.
The others only return values from the collection (e.g. first, last) or return a collection (e.g. each, map, slice). None of "might do either", because doing so actually makes that method unchainable. For example:

my $c = c(1, 2, 300);
my $large = $c->splice(5, 10)->grep(sub { $_ > 100 }); # fatal error

As it would have behaviour that is very inconsistent with the other methods in Mojo::Collection, I feel the use cases would need to be pretty compelling to get into core.

Also, if you are looking to partition data in X sized chunks, bundle_by in Mojo::Collection::Role::UtilsBy can do that. It is even the example used for it :) This makes your example chainable too.

my $c = c(1 .. 1000)->with_roles('+UtilsBy');
my $part_c = $c->bundle_by(sub { c(@_) }, 100)->each(sub { ... });

EDIT: Fixed incorrect reference to partition_by, should have been bundle_by

@srchulo
Copy link
Author

srchulo commented Jun 10, 2019

@CandyAngel that's a good point. I modified my example from this:

while (my @batch = splice @$original, $offset, $length) {
    my $c = c(@batch);
    # ...
}

And just using $batch does make it break. I agree that if it was included, returning an empty collection makes more sense than returning undef.

The suggestion of bundle_by is a good idea. I like how it makes it chainable!

It's possible splice may still make sense in another way...but I can't think of one right now. I think it would only make sense in the basic use case:

my $spliced_c = $c->splice(0, 10);

With the only real benefits here being that it may be expected since it is available on arrays, and that you are returned a Mojo::Collection vs an array, which as I mentioned about you can really get just by wrapping it:

my $spliced_c = c(splice @$c, 0, 10);

I guess the same may be true for the methods I mentioned in #1320.

I wonder if it makes sense to have methods that can be chained, and methods that are included for completeness with perl arrays. Or is the expectation there to use the built in methods and then wrap the results with c?

@stale
Copy link

stale bot commented Aug 30, 2020

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the wontfix label Aug 30, 2020
@kraih kraih removed the stale label Nov 5, 2020
@kraih
Copy link
Member

kraih commented Feb 10, 2021

Looks like everyone agrees that splice in a method chain is awkward.

@kraih kraih closed this as completed Feb 10, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants