During profiling, I came across this function in List::UtilsBy:
sub rev_nsort_by(&@) {
my $keygen = shift;
my @keys = map { local $_ = $_[$_]; scalar $keygen->( $_ ) } 0 .. $#_;
return map { $_[$_] } sort { $keys[$b] <=> $keys[$a] } 0 .. $#_;
}
rev_nsort_by does a reverse numeric sort based on some key predicate, for example:
my @objects = load_objects_from_database();
# sort by rating, highest first
@objects = rev_nsort_by { $_->rating } @objects;
I understand perfectly why rev_nsort_by
, as shown above, works as intended, but I'm wondering why it's so complex. Specifically, I wonder why
my @keys = map { local $_ = $_[$_]; scalar $keygen->( $_ ) } 0 .. $#_;
was not written as
my @keys = map { scalar $keygen->( $_ ) } @_;
which looks functionally equivalent to me. Am I missing some corner-case behavior of $_
here, which the longer version amounts for in some way?
You can use the getClass() method to determine the class of an object. Also, if you want to check if an object implements an Interface or Class, you can use the instanceof keyword. That's it about checking the datatype of an object in Groovy.
Variables in Groovy can be defined in two ways − using the native syntax for the data type or the next is by using the def keyword. For variable definitions it is mandatory to either provide a type name explicitly or to use "def" in replacement. This is required by the Groovy parser.
In groovy, the ==~ operator (aka the "match" operator) is used for regular expression matching. != is just a plain old regular "not equals".
The def keyword is used to define an untyped variable or a function in Groovy, as it is an optionally-typed language.
There is a subtle edge case here: Inside foreach
loops, or map
expressions, the default variable $_
is aliased to the original value. E.g.
@nums = 1..5;
@foo = map { $_ *= 2 } @nums;
# both @foo and @nums contain 2, 4, 6, 8, 10 now.
However, constants aren't valid lvalues, so we couldn't do that like
@foo = map { $_ *= 2 } 1, 2, 3, 4, 5;
# Modification of read-only value
The @_
array too is aliased to the original values, so imagine the following edge cases:
sub buggy (&@) { my $cb = shift; map $cb->($_), @_ };
buggy { $_ *= 2 } 1, 2, 3; # Modification of read-only value attempted
buggy { $_[0] *= 2} 1, 2, 3; # ditto
my @array = 1 .. 5;
buggy { $_ *= 2 } @array; # @array now is 2, 4, 6, 8, 10
buggy { $_[0] *= 2 } @array; # ditto
Aliases are transitive, so inner $_[0]
is aliased to $_
, which is aliased to outer $_[0]
, which is an alias for the constant 1
/ $array[0]
.
So, what does local $_ = $_[$_]
do here?
$_
visible to the callback.Ensuring copying semantics (thus avoiding unexpected side effects) feels natural for Perl, so this function is well-designed and not especially overengineered.
(Note: map {local $_ = $_; ...} @_
would have been sufficient to make the copy)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With