From: Junio C Hamano <hidden> Date: 2021-05-20 23:53:59
Jeff King [off-list ref] writes:
I like all of this, except for the change in the interface of
Git::config_regexp(). You mention that it's new-ish, and probably not in
wide use. And I agree that's probably true. But it feels like violating
a principle of not breaking APIs, and we should stick to that principle
and not bend it for "well, it's not that old an API".
I'd find it more compelling if it the existing interface was broken or
hard to avoid changing. But couldn't we just add a new function with the
extra info (config_regexp_with_values() or something)?
We seem to use it in-tree only once, but we have no control over
what out-of-tree users have been doing.
I do like your proposed name for the fact that it has _with_values
in it; the original gave us only a list of configuration variable
names, and now we are getting name-value tuples.
BUT
I am not sure if I like the new interface, and I do not know if the
the name "config_with_values" is a good match for what it does.
Namely, when a function is described as:
=item config_regexp ( RE )
Retrieve the list of configuration key names matching the regular
expression C<RE>. The return value is an ARRAY of key-value pairs.
I would expect it to return ([key1, value1], [key2, value2], ...),
but the implementation returns (key1, value1, key2, value2, ...),
i.e. a flattened list, if I am not mistaken.
my @cmd = ('config', '--null', '--get-regexp', $regex);
unshift @cmd, $self if $self;
my $data = command(@cmd);
my (@kv) = map { split /\n/, $_, 2 } split /\0/, $data;
return @kv;
We get NUL terminated records, so we first split the entire output
at NULs (to get a list of key-value records). Each key-value record
has the key followed by LF followed by value, so we split it at the
first LF (i.e. a value with an embedded LF would behave correctly)
to extract key and value out of it. But the resulting list is a
flat list with alternating key and value.
The side that works on the returned value of courese knows that it
is getting a flattened list and acts accordingly:
my @kv = Git::config_regexp("^sende?mail[.]");
while (my ($k, $v) = splice @kv, 0, 2) {
push @{$known_config_keys{$k}} => $v;
}
Perhaps it may be more performant than a more obvious and
straight-forward "list of tuples", i.e.
return map { [split /\n/, $_, 2] } split /\0/, $data;
my @kv = Git::config_regexp("^sende?mail[.]");
for my $tuple (@kv) {
push @{$known_config_keys{$tuple->[0]}}, $tuple->[1];
}
but somehow the flattened list felt unnatural at least to a naïve
reader like me.
I like all of this, except for the change in the interface of
Git::config_regexp(). You mention that it's new-ish, and probably not in
wide use. And I agree that's probably true. But it feels like violating
a principle of not breaking APIs, and we should stick to that principle
and not bend it for "well, it's not that old an API".
I'd find it more compelling if it the existing interface was broken or
hard to avoid changing. But couldn't we just add a new function with the
extra info (config_regexp_with_values() or something)?
We seem to use it in-tree only once, but we have no control over
what out-of-tree users have been doing.
I thought people might have bought my argument in v1 that we just
document these as "public" as part of some copy/pasting in Git.pm, but
sure, I'll re-roll and make this a new function or something...
I do like your proposed name for the fact that it has _with_values
in it; the original gave us only a list of configuration variable
names, and now we are getting name-value tuples.
BUT
I am not sure if I like the new interface, and I do not know if the
the name "config_with_values" is a good match for what it does.
Namely, when a function is described as:
=item config_regexp ( RE )
Retrieve the list of configuration key names matching the regular
expression C<RE>. The return value is an ARRAY of key-value pairs.
I would expect it to return ([key1, value1], [key2, value2], ...),
but the implementation returns (key1, value1, key2, value2, ...),
i.e. a flattened list, if I am not mistaken.
...
my @cmd = ('config', '--null', '--get-regexp', $regex);
unshift @cmd, $self if $self;
my $data = command(@cmd);
my (@kv) = map { split /\n/, $_, 2 } split /\0/, $data;
return @kv;
We get NUL terminated records, so we first split the entire output
at NULs (to get a list of key-value records). Each key-value record
has the key followed by LF followed by value, so we split it at the
first LF (i.e. a value with an embedded LF would behave correctly)
to extract key and value out of it. But the resulting list is a
flat list with alternating key and value.
The side that works on the returned value of courese knows that it
is getting a flattened list and acts accordingly:
my @kv = Git::config_regexp("^sende?mail[.]");
while (my ($k, $v) = splice @kv, 0, 2) {
push @{$known_config_keys{$k}} => $v;
}
Perhaps it may be more performant than a more obvious and
straight-forward "list of tuples", i.e.
return map { [split /\n/, $_, 2] } split /\0/, $data;
my @kv = Git::config_regexp("^sende?mail[.]");
for my $tuple (@kv) {
push @{$known_config_keys{$tuple->[0]}}, $tuple->[1];
}
but somehow the flattened list felt unnatural at least to a naïve
reader like me.
The "performant" really doesn't matter here, we're comparing shelling
out to getting a small number of config keys back. So I wasn't trying to
optimize this.
Returning a flattened list is idiomatic in Perl, it means that a caller
can do any of:
# I only care about the last value for a key, or only about
# existence checks
my %hash = func();
Or:
# I want all key-values to iterate over
my @kv = func();
Returning touples like this makes that less convenient for both, who'll
need to do more work to unpack them.
For what it's worth In Perl "return a list" means "flattened list", the
term "flattened list" I think comes from other languages. You'd call
what you're suggesting a list of arrays, or (if a top-level reference)
an array of arrays, AoA for short, AoH for array (ref) of hashes etc.
From: Jeff King <hidden> Date: 2021-05-21 09:13:44
On Fri, May 21, 2021 at 08:23:15AM +0200, Ævar Arnfjörð Bjarmason wrote:
The "performant" really doesn't matter here, we're comparing shelling
out to getting a small number of config keys back. So I wasn't trying to
optimize this.
Returning a flattened list is idiomatic in Perl, it means that a caller
can do any of:
# I only care about the last value for a key, or only about
# existence checks
my %hash = func();
Or:
# I want all key-values to iterate over
my @kv = func();
Returning touples like this makes that less convenient for both, who'll
need to do more work to unpack them.
For what it's worth In Perl "return a list" means "flattened list", the
term "flattened list" I think comes from other languages. You'd call
what you're suggesting a list of arrays, or (if a top-level reference)
an array of arrays, AoA for short, AoH for array (ref) of hashes etc.
Yeah, I think that is reasonable. But it made me wonder how we handle
value-less booleans, and I think there's indeed a bug.
Try a config like this:
$ cat >foo <<\EOF
[foo]
key-with-value = string
key-with-empty =
just-bool
another-key-with-value = another
EOF
A regular config --list looks like:
$ git config --file=foo --list
foo.key-with-value=string
foo.key-with-empty=
foo.just-bool
foo.another-key-with-value=another
Note how "just-bool" drops the "=" to distinguish it from the empty
string. With "-z", it looks like this:
$ git config --file=foo --list -z
foo.key-with-value
string^@foo.key-with-empty
^@foo.just-bool^@foo.another-key-with-value
another^@
The NULs separate keys, but keys are separated from their values by a
newline. And again, just-bool omits the newline.
Your parser splits on newline, so for that entry we'll get only one
string returned in the list (the key), rather than two (the key and
value). In a flattened list, that becomes ambiguous. E.g., adapting your
parser into a stand-alone script:
$ git config --file=foo --list -z |
perl -e '
local $/;
my $data = <STDIN>;
my (@kv) = map { split /\n/, $_, 2 } split /\0/, $data;
while (@kv) {
my $k = shift @kv;
print "key: $k\n";
my $v = shift @kv;
print " value: ", (defined $v ? $v : "undef"), "\n";
}
'
key: foo.key-with-value
value: string
key: foo.key-with-empty
value:
key: foo.just-bool
value: foo.another-key-with-value
key: another
value: undef
We end up misinterpreting a key as a value, and vice versa.
Using a non-flattened structure would have prevented this (we'd sensibly
get undef when trying to access the missing second element of the
array). But I do agree the flattened structure is more perl-ish.
Probably you'd want to insert an explicit "undef" into the list. The
most perl-ish I could come up with is:
my (@kv) = map { my ($k, $v) = split /\n/, $_, 2;
($k, $v)
} split /\0/, $data;
I notice that $known_keys then becomes a non-flat representation. You'd
either want to turn that back into a zero-length array there, or store
the "undef" and handle it appropriately (it can be a synonym for "true",
though that is just an optimization at this point).
-Peff
On Fri, May 21, 2021 at 08:23:15AM +0200, Ævar Arnfjörð Bjarmason wrote:
quoted
The "performant" really doesn't matter here, we're comparing shelling
out to getting a small number of config keys back. So I wasn't trying to
optimize this.
Returning a flattened list is idiomatic in Perl, it means that a caller
can do any of:
# I only care about the last value for a key, or only about
# existence checks
my %hash = func();
Or:
# I want all key-values to iterate over
my @kv = func();
Returning touples like this makes that less convenient for both, who'll
need to do more work to unpack them.
For what it's worth In Perl "return a list" means "flattened list", the
term "flattened list" I think comes from other languages. You'd call
what you're suggesting a list of arrays, or (if a top-level reference)
an array of arrays, AoA for short, AoH for array (ref) of hashes etc.
Yeah, I think that is reasonable. But it made me wonder how we handle
value-less booleans, and I think there's indeed a bug.
Try a config like this:
$ cat >foo <<\EOF
[foo]
key-with-value = string
key-with-empty =
just-bool
another-key-with-value = another
EOF
A regular config --list looks like:
$ git config --file=foo --list
foo.key-with-value=string
foo.key-with-empty=
foo.just-bool
foo.another-key-with-value=another
Note how "just-bool" drops the "=" to distinguish it from the empty
string. With "-z", it looks like this:
$ git config --file=foo --list -z
foo.key-with-value
string^@foo.key-with-empty
^@foo.just-bool^@foo.another-key-with-value
another^@
The NULs separate keys, but keys are separated from their values by a
newline. And again, just-bool omits the newline.
Your parser splits on newline, so for that entry we'll get only one
string returned in the list (the key), rather than two (the key and
value). In a flattened list, that becomes ambiguous. E.g., adapting your
parser into a stand-alone script:
$ git config --file=foo --list -z |
perl -e '
local $/;
my $data = <STDIN>;
my (@kv) = map { split /\n/, $_, 2 } split /\0/, $data;
while (@kv) {
my $k = shift @kv;
print "key: $k\n";
my $v = shift @kv;
print " value: ", (defined $v ? $v : "undef"), "\n";
}
'
key: foo.key-with-value
value: string
key: foo.key-with-empty
value:
key: foo.just-bool
value: foo.another-key-with-value
key: another
value: undef
We end up misinterpreting a key as a value, and vice versa.
Using a non-flattened structure would have prevented this (we'd sensibly
get undef when trying to access the missing second element of the
array). But I do agree the flattened structure is more perl-ish.
Probably you'd want to insert an explicit "undef" into the list. The
most perl-ish I could come up with is:
my (@kv) = map { my ($k, $v) = split /\n/, $_, 2;
($k, $v)
} split /\0/, $data;
I notice that $known_keys then becomes a non-flat representation. You'd
either want to turn that back into a zero-length array there, or store
the "undef" and handle it appropriately (it can be a synonym for "true",
though that is just an optimization at this point).
-Peff
Ah yes, that's indeed a bug. I'd forgetten about the empty value case.
For what it's worth you can slightly golf that as (split /\n/, $_,
2)[0,1], but I think in this case your version is better than that, it's
more obvious what we're trying to do in always returning the $v.
From: Jeff King <hidden> Date: 2021-05-21 09:37:10
On Fri, May 21, 2021 at 11:24:00AM +0200, Ævar Arnfjörð Bjarmason wrote:
quoted
Using a non-flattened structure would have prevented this (we'd sensibly
get undef when trying to access the missing second element of the
array). But I do agree the flattened structure is more perl-ish.
Probably you'd want to insert an explicit "undef" into the list. The
most perl-ish I could come up with is:
my (@kv) = map { my ($k, $v) = split /\n/, $_, 2;
($k, $v)
} split /\0/, $data;
I notice that $known_keys then becomes a non-flat representation. You'd
either want to turn that back into a zero-length array there, or store
the "undef" and handle it appropriately (it can be a synonym for "true",
though that is just an optimization at this point).
-Peff
Ah yes, that's indeed a bug. I'd forgetten about the empty value case.
For what it's worth you can slightly golf that as (split /\n/, $_,
2)[0,1], but I think in this case your version is better than that, it's
more obvious what we're trying to do in always returning the $v.
Heh. Thanks, I almost invited you to golf it because I was curious if we
could continue to do it in one line. I see I didn't need to ask. :)
Yours is clever and I'm glad to be enlightened, but I agree the
two-liner is probably more obvious (perhaps even a comment like
"bool-only values omit the newline and become undef" is worth it).
-Peff
From: Felipe Contreras <hidden> Date: 2021-05-28 15:49:27
Ævar Arnfjörð Bjarmason wrote:
Returning a flattened list is idiomatic in Perl, it means that a caller
can do any of:
# I only care about the last value for a key, or only about
# existence checks
my %hash = func();
I was staying on the sideline because I don't know what's idiomatic in
Perl, but Perl and Ruby share a lot in common (one could say Perl is the
grandfather of Ruby), and I do know very well what's idiomatic in Ruby.
In perl you can do $ENV{'USER'}, and:
while (my ($k, $v) = each %ENV) {
print "$k = $v\n";
}
Obviously it's idiomatic to use hashes this way [1].
It was a waste for Git::config_regexp to not do the sensible thing here.
You can do exactly the same in Ruby: ENV['USER']
ENV.each { |k, v| print "#{k} = #{v}\n" }
And the way I would parse these configurations in Ruby is something like:
c = `git config -l -z`.split("\0").map { |e| e.split("\n") }.to_h
c['sendemail.smtpserver']
And this just gave me an idea...
[1] https://perldoc.perl.org/functions/each
--
Felipe Contreras
Returning a flattened list is idiomatic in Perl, it means that a caller
can do any of:
# I only care about the last value for a key, or only about
# existence checks
my %hash = func();
I was staying on the sideline because I don't know what's idiomatic in
Perl, but Perl and Ruby share a lot in common (one could say Perl is the
grandfather of Ruby), and I do know very well what's idiomatic in Ruby.
In perl you can do $ENV{'USER'}, and:
while (my ($k, $v) = each %ENV) {
print "$k = $v\n";
}
Obviously it's idiomatic to use hashes this way [1].
For what it's worth idiomatic/good idea and "has an example in the perl
documentation" unfortunately aren't always aligned. A lot of experienced
Perl programmers avoid each() like the plague:
http://blogs.perl.org/users/rurban/2014/04/do-not-use-each.html
It was a waste for Git::config_regexp to not do the sensible thing here.
FWIW we're commenting on a v2 of a series that's at v5 now, and doesn't
use config_regexp() at all, the relevant code is inlined in
git-send-email.perl now.
You can do exactly the same in Ruby: ENV['USER']
ENV.each { |k, v| print "#{k} = #{v}\n" }
And the way I would parse these configurations in Ruby is something like:
c = `git config -l -z`.split("\0").map { |e| e.split("\n") }.to_h
c['sendemail.smtpserver']
And this just gave me an idea...
I'd probably do it that way in Ruby, but not in Perl.
Things that superficially look the same in two languages can have
completely different behaviors, a "hash" isn't a single type of data
structure in these programming languages.
In particular Ruby doesn't have hshes in the Perl sense of the word, it
has an ordered key-value pair structure (IIRC under the hood they're
hashes + a double linked list).
Thus you can use it for things like parsing a key=>value text file where
the key is unique and the order is important.
In Perl hashes are only meant for key-value lookup, they are not
ordered, and are actually actively randomly ordered for security
reasons. In any modern version inserting a new key will have an avalance
effect of completely changing the order. It's not even stable across
invocations:
$ perl -wE 'my %h; for ("a".."z") { $h{$_} = $_; say keys %h }'
a
ab
bca
dcba
daebc
cbaedf
aecbfdg
dgfcbaeh
[...]
The other important distinction (but I'm not so sure about Ruby here) is
that Perl doesn't have any way to pass a hash or any other structure to
another function, everything is flattened and pushed onto the stack.
To pass a "hash" you're not passing the hash, but a "flattened" pointer
to it on the stack.
Thus passing and making use of these flattened values is idiomatic in
Perl in a way that doesn't exist in a lot of other languages. In some
other languages a function has to choose whether it's returning an array
or a hash, in Perl you can just push the "flattened" items that make up
the array on the stack, and have the caller decide if they're pushing
those stack items into an array, or to a hash if they expect it to be
meaningful as key-value pairs.
In the context of Git's config format doing that is the perfect fit for
config values, our config values *are* ordered, but they are also
sort-of hashes, but whether it's "all values" or "last value wins" (or
anything else, that's just the common ones) depends on the key/user.
So by having a list of key-value pairs on the stack you can choose to
put it into an array if you don't want to lose information, or put it
into a hash if all you care about is "last key wins", or "I'm going to
check for key existence".
I think that in many other languages that wouldn't make any sense, and
you'd always return a structure like:
[
key => [zero or more values],
[...]
]
Or whatever, the caller can also unambiguously interpret those, but
unlike Perl you'd need to write something to explicitly iterate the
returned value (or a helper) to get it into a hash or a "flattened"
array. In Perl it's trivial due to the "everything is on the stack"
semantics.
Anyway, all that being said the part we're talking about as a trivial
part of this larger series. I'd much prefer to have it just land as
"good enough" at this point. It works, we can always tweak it further
later if there's a need to do that.
From: Felipe Contreras <hidden> Date: 2021-05-29 14:53:53
Ævar Arnfjörð Bjarmason wrote:
On Fri, May 28 2021, Felipe Contreras wrote:
quoted
Ævar Arnfjörð Bjarmason wrote:
quoted
Returning a flattened list is idiomatic in Perl, it means that a caller
can do any of:
# I only care about the last value for a key, or only about
# existence checks
my %hash = func();
I was staying on the sideline because I don't know what's idiomatic in
Perl, but Perl and Ruby share a lot in common (one could say Perl is the
grandfather of Ruby), and I do know very well what's idiomatic in Ruby.
In perl you can do $ENV{'USER'}, and:
while (my ($k, $v) = each %ENV) {
print "$k = $v\n";
}
Obviously it's idiomatic to use hashes this way [1].
For what it's worth idiomatic/good idea and "has an example in the perl
documentation" unfortunately aren't always aligned. A lot of experienced
Perl programmers avoid each() like the plague:
http://blogs.perl.org/users/rurban/2014/04/do-not-use-each.html
Perl is an old language, and each() was introduced in 2010, it's
expected that some old-timers would not adapt to the new idioms.
BTW, Ruby borrowed a lot from Perl, but I'm pretty sure Perl borrowed
each() from Ruby.
Untilmately it doesn't matter what you use to traverse %ENV, my point is
that it's a hash.
quoted
It was a waste for Git::config_regexp to not do the sensible thing here.
FWIW we're commenting on a v2 of a series that's at v5 now, and doesn't
use config_regexp() at all, the relevant code is inlined in
git-send-email.perl now.
I know, I've been following the threads. I'm trying to say it's a shame
Git::config_regexp does not do the sensible thing.
quoted
You can do exactly the same in Ruby: ENV['USER']
ENV.each { |k, v| print "#{k} = #{v}\n" }
And the way I would parse these configurations in Ruby is something like:
quoted
c = `git config -l -z`.split("\0").map { |e| e.split("\n") }.to_h
c['sendemail.smtpserver']
And this just gave me an idea...
I'd probably do it that way in Ruby, but not in Perl.
Things that superficially look the same in two languages can have
completely different behaviors, a "hash" isn't a single type of data
structure in these programming languages.
In particular Ruby doesn't have hshes in the Perl sense of the word, it
has an ordered key-value pair structure (IIRC under the hood they're
hashes + a double linked list).
Thus you can use it for things like parsing a key=>value text file where
the key is unique and the order is important.
In Perl hashes are only meant for key-value lookup, they are not
ordered, and are actually actively randomly ordered for security
reasons. In any modern version inserting a new key will have an avalance
effect of completely changing the order. It's not even stable across
invocations:
$ perl -wE 'my %h; for ("a".."z") { $h{$_} = $_; say keys %h }'
a
ab
bca
dcba
daebc
cbaedf
aecbfdg
dgfcbaeh
[...]
This used to be the case in Ruby too. The order of hashes was not
guaranteed.
The situation is more complicated because not only do you have different
versions, but you have different implementations. AFAIK the Ruby
language specification doesn't say anything about ordering, although
basically all implementations do order.
The other important distinction (but I'm not so sure about Ruby here) is
that Perl doesn't have any way to pass a hash or any other structure to
another function, everything is flattened and pushed onto the stack.
To pass a "hash" you're not passing the hash, but a "flattened" pointer
to it on the stack.
Thus passing and making use of these flattened values is idiomatic in
Perl in a way that doesn't exist in a lot of other languages. In some
other languages a function has to choose whether it's returning an array
or a hash, in Perl you can just push the "flattened" items that make up
the array on the stack, and have the caller decide if they're pushing
those stack items into an array, or to a hash if they expect it to be
meaningful as key-value pairs.
Yeah, that's something that wasn't borrowed. In Ruby everything is an
object.
In the context of Git's config format doing that is the perfect fit for
config values, our config values *are* ordered, but they are also
sort-of hashes, but whether it's "all values" or "last value wins" (or
anything else, that's just the common ones) depends on the key/user.
So by having a list of key-value pairs on the stack you can choose to
put it into an array if you don't want to lose information, or put it
into a hash if all you care about is "last key wins", or "I'm going to
check for key existence".
I think that in many other languages that wouldn't make any sense, and
you'd always return a structure like:
[
key => [zero or more values],
[...]
]
Or whatever, the caller can also unambiguously interpret those, but
unlike Perl you'd need to write something to explicitly iterate the
returned value (or a helper) to get it into a hash or a "flattened"
array. In Perl it's trivial due to the "everything is on the stack"
semantics.
Indeed, but my point is that it's a hash for all intents and purposes:
my %hash = func();
And it makes sense for it to be a hash, just like %ENV.
And although the internals are different something very close would
happen in Ruby:
hash = func().to_h
Anyway, all that being said the part we're talking about as a trivial
part of this larger series. I'd much prefer to have it just land as
"good enough" at this point. It works, we can always tweak it further
later if there's a need to do that.
Indeed, as I said, the entire patch series looks good to me.
Cheers.
--
Felipe Contreras
Returning a flattened list is idiomatic in Perl, it means that a caller
can do any of:
# I only care about the last value for a key, or only about
# existence checks
my %hash = func();
I was staying on the sideline because I don't know what's idiomatic in
Perl, but Perl and Ruby share a lot in common (one could say Perl is the
grandfather of Ruby), and I do know very well what's idiomatic in Ruby.
In perl you can do $ENV{'USER'}, and:
while (my ($k, $v) = each %ENV) {
print "$k = $v\n";
}
Obviously it's idiomatic to use hashes this way [1].
For what it's worth idiomatic/good idea and "has an example in the perl
documentation" unfortunately aren't always aligned. A lot of experienced
Perl programmers avoid each() like the plague:
http://blogs.perl.org/users/rurban/2014/04/do-not-use-each.html
Perl is an old language, and each() was introduced in 2010, it's
expected that some old-timers would not adapt to the new idioms.
each() has been in Perl since 1987 with perl v1.0, you must be confusing
it with something else.
In any case, the recommendation against it has nothing to do with its
age, it's that similar to strtok() it has global state. So e.g. you
can't safely iterate over a hash you're modifying with it, or if you
iterate over a top-level structure and pass that variable down to
another function, and it also uses each()...
From: Felipe Contreras <hidden> Date: 2021-05-30 16:07:42
Ævar Arnfjörð Bjarmason wrote:
On Sat, May 29 2021, Felipe Contreras wrote:
quoted
Ævar Arnfjörð Bjarmason wrote:
quoted
On Fri, May 28 2021, Felipe Contreras wrote:
quoted
Ævar Arnfjörð Bjarmason wrote:
quoted
Returning a flattened list is idiomatic in Perl, it means that a caller
can do any of:
# I only care about the last value for a key, or only about
# existence checks
my %hash = func();
I was staying on the sideline because I don't know what's idiomatic in
Perl, but Perl and Ruby share a lot in common (one could say Perl is the
grandfather of Ruby), and I do know very well what's idiomatic in Ruby.
In perl you can do $ENV{'USER'}, and:
while (my ($k, $v) = each %ENV) {
print "$k = $v\n";
}
Obviously it's idiomatic to use hashes this way [1].
For what it's worth idiomatic/good idea and "has an example in the perl
documentation" unfortunately aren't always aligned. A lot of experienced
Perl programmers avoid each() like the plague:
http://blogs.perl.org/users/rurban/2014/04/do-not-use-each.html
Perl is an old language, and each() was introduced in 2010, it's
expected that some old-timers would not adapt to the new idioms.
each() has been in Perl since 1987 with perl v1.0, you must be confusing
it with something else.
I see. I read the each() documentation [1] too hastily:
When called on a hash in list context, returns a 2-element list
consisting of the key and value for the next element of a hash. In
Perl 5.12 and later only...
In any case, the recommendation against it has nothing to do with its
age, it's that similar to strtok() it has global state.
Yes, that's what I understood from the blog post you shared, but at
least personally I never assume I can modify a hash like that. I see why
some people need to be careful with it, but "avoid it like the plague"
seems way too defensive programming to me.
[1] https://perldoc.perl.org/functions/each
--
Felipe Contreras