自动调用作为子程序引用的散列值
Automatically call hash values that are subroutine references
我有一个散列,其中有一些值不是标量数据,而是 return 标量数据的匿名子例程。我想让这对在散列中查找值的代码部分完全透明,这样它就不必知道某些散列值可能是 return 标量数据的匿名子例程不仅仅是普通的旧标量数据。
为此,有没有什么方法可以在访问匿名子例程的键时执行它们,而无需使用任何特殊语法?这是一个说明目标和问题的简化示例:
#!/usr/bin/perl
my %hash = (
key1 => "value1",
key2 => sub {
return "value2"; # In the real code, this value can differ
},
);
foreach my $key (sort keys %hash) {
print $hash{$key} . "\n";
}
我想要的输出是:
perl ./test.pl
value1
value2
相反,这是我得到的:
perl ./test.pl
value1
CODE(0x7fb30282cfe0)
您需要确定代码引用何时存在,然后将其作为实际调用执行:
foreach my $key (sort keys %hash) {
if (ref $hash{$key} eq 'CODE'){
print $hash{$key}->() . "\n";
}
else {
print "$hash{$key}\n";
}
}
请注意,您可以考虑将所有哈希值设为 sub(真正的调度 table),而不是让一些 return 非代码引用和一些 return 引用。
但是,如果您这样定义散列,则在使用散列时不必做任何特殊的技巧。它在查找键时直接调用 sub 和 returns 值。
key2 => sub {
return "value2";
}->(),
不,不是没有一些辅助代码。您要求一个简单的标量值和一个代码引用以相同的方式运行。这样做的代码远非简单,而且还会在您的哈希及其使用之间注入复杂性。您可能会发现以下方法更简单、更清晰。
您可以使所有值代码引用,使哈希成为 调度 table,用于统一调用
my %hash = (
key1 => sub { return "value1" },
key2 => sub {
# carry on some processing ...
return "value2"; # In the real code, this value can differ
},
);
print $hash{$_}->() . "\n" for sort keys %hash;
当然,这种方法的开销很小。
是的,你可以。您可以使用 tie
hash to implementation that will resolve coderefs to their return values or you can use blessed scalars as values with overload
ed 方法来进行字符串化、谦化以及您想要自动解析的任何其他上下文。
,可以使用各种或多或少神秘的技巧来做到这一点,例如 tie
、重载或魔术变量。然而,这将是不必要的复杂化和毫无意义的混淆。尽管这些技巧很酷,但在实际代码中使用它们至少在 99% 的情况下都是错误的。
实际上,最简单和最干净的解决方案可能是编写一个带有标量的辅助子例程,如果它是代码引用,则执行它并 returns 结果:
sub evaluate {
my $val = shift;
return $val->() if ref($val) eq 'CODE';
return $val; # otherwise
}
foreach my $key (sort keys %hash) {
print evaluate($hash{$key}) . "\n";
}
Perl 针对此类用例的特殊功能之一是 tie
。这允许您将面向对象的样式方法附加到标量或散列。
应谨慎使用,因为它可能意味着您的代码以意想不到的方式做着非常奇怪的事情。
但举个例子:
#!/usr/bin/env perl
package RandomScalar;
my $random_range = 10;
sub TIESCALAR {
my ( $class, $range ) = @_;
my $value = 0;
bless $value, $class;
}
sub FETCH {
my ($self) = @_;
return rand($random_range);
}
sub STORE {
my ( $self, $range ) = @_;
$random_range = $range;
}
package main;
use strict;
use warnings;
tie my $random_var, 'RandomScalar', 5;
for ( 1 .. 10 ) {
print $random_var, "\n";
}
$random_var = 100;
for ( 1 .. 10 ) {
print $random_var, "\n";
}
如您所见 - 这让您可以使用 'ordinary' 标量,并用它做一些有趣的事情。您可以使用与 hash
非常相似的机制 - 一个示例可能是进行数据库查找。
但是,您也需要非常谨慎 - 因为这样做是在远距离创建动作。未来的维护程序员可能不会期望您的 $random_var
每次 运行 都会实际更改,并且实际上不会 'set' 赋值。
它可以非常有用,例如。虽然正在测试,这就是我举个例子的原因。
在您的示例中 - 您可能 'tie' 哈希:
#!/usr/bin/env perl
package MagicHash;
sub TIEHASH {
my ($class) = @_;
my $self = {};
return bless $self, $class;
}
sub FETCH {
my ( $self, $key ) = @_;
if ( ref( $self->{$key} ) eq 'CODE' ) {
return $self->{$key}->();
}
else {
return $self->{$key};
}
}
sub STORE {
my ( $self, $key, $value ) = @_;
$self->{$key} = $value;
}
sub CLEAR {
my ($self) = @_;
$self = {};
}
sub FIRSTKEY {
my ($self) = @_;
my $null = keys %$self; #reset iterator
return each %$self;
}
sub NEXTKEY {
my ($self) = @_;
return each %$self;
}
package main;
use strict;
use warnings;
use Data::Dumper;
tie my %magic_hash, 'MagicHash';
%magic_hash = (
key1 => 2,
key2 => sub { return "beefcake" },
);
$magic_hash{random} = sub { return rand 10 };
foreach my $key ( keys %magic_hash ) {
print "$key => $magic_hash{$key}\n";
}
foreach my $key ( keys %magic_hash ) {
print "$key => $magic_hash{$key}\n";
}
foreach my $key ( keys %magic_hash ) {
print "$key => $magic_hash{$key}\n";
}
这样就少了点邪恶,因为以后的维护程序员可以正常使用你的'hash'。但是动态评估可能会搬起石头砸自己的脚,所以还是要小心。
另一种方法是 'proper' 面向对象 - 创建一个 'storage object' ……基本上与上述类似 - 只是它创建了一个对象,而不是使用 tie
。这对于长期使用来说应该更清楚,因为你不会得到意想不到的行为。 (这是一个正在施展魔法的对象,这很正常,而不是 'works funny' 的散列)。
我不认为其他人写的不赞成 tie
mechanism 的话是有根据的。 None 的作者似乎正确理解它的工作原理以及可用的核心库备份
这是一个基于 Tie::StdHash
的 tie
示例
如果您将散列绑定到 Tie::StdHash
class,那么它可以像普通散列一样工作。这意味着除了您可能想要覆盖的方法之外,没有什么可写的了
在这种情况下,我覆盖了 TIEHASH
,这样我就可以在与 tie
命令相同的语句中指定初始化列表,而 FETCH
调用 superclass的FETCH,如果恰好是子程序引用则调用它
除了您要求的更改外,您的绑定哈希将正常工作。我希望很明显,如果您已将子例程引用存储为散列值,则不再有直接的方法来检索子例程引用。这样的值将始终被不带任何参数调用它的结果替换
SpecialHash.pm
package SpecialHash;
use Tie::Hash;
use base 'Tie::StdHash';
sub TIEHASH {
my $class = shift;
bless { @_ }, $class;
}
sub FETCH {
my $self = shift;
my $val = $self->SUPER::FETCH(@_);
ref $val eq 'CODE' ? $val->() : $val;
}
1;
main.pl
use strict;
use warnings 'all';
use SpecialHash;
tie my %hash, SpecialHash => (
key1 => "value1",
key2 => sub {
return "value2"; # In the real code, this value can differ
},
);
print "$hash{$_}\n" for sort keys %hash;
输出
value1
value2
更新
听起来您的真实情况是使用看起来像这样的现有哈希
my %hash = (
a => {
key_a1 => 'value_a1',
key_a2 => sub { 'value_a2' },
},
b => {
key_b1 => sub { 'value_b1' },
key_b2 => 'value_b2',
},
);
在已经填充的变量上使用 tie
不如在声明点绑定 then 然后插入值那么整洁,因为必须将数据复制到绑定的对象。然而,我在 SpecialHash
class 中编写 TIEHASH
方法的方式使得在 tie
语句
中的操作变得简单
如果可能,最好先tie
每个散列,然后再将数据放入其中并添加它到主哈希
该程序将 %hash
中恰好是散列引用的每个值联系起来。这里面的核心就是语句
tie %$val, SpecialHash => ( %$val )
其功能与
相同
tie my %hash, SpecialHash => ( ... )
在前面的代码中取消引用 $val
以使语法有效,并且还使用散列的当前内容作为绑定散列的初始化数据。这就是数据被复制的方式
之后只有几个嵌套循环转储整个 %hash
以验证关系是否有效
use strict;
use warnings 'all';
use SpecialHash;
my %hash = (
a => {
key_a1 => 'value_a1',
key_a2 => sub { 'value_a2' },
},
b => {
key_b1 => sub { 'value_b1' },
key_b2 => 'value_b2',
},
);
# Tie all the secondary hashes that are hash references
#
for my $val ( values %hash ) {
tie %$val, SpecialHash => ( %$val ) if ref $val eq 'HASH';
}
# Dump all the elements of the second-level hashes
#
for my $k ( sort keys %hash ) {
my $v = $hash{$k};
next unless ref $v eq 'HASH';
print "$k =>\n";
for my $kk ( sort keys %$v ) {
my $vv = $v->{$kk};
print " $kk => $v->{$kk}\n"
}
}
输出
a =>
key_a1 => value_a1
key_a2 => value_a2
b =>
key_b1 => value_b1
key_b2 => value_b2
有一个名为 "magic" 的功能允许在访问变量时调用代码。
向变量添加魔法会大大减慢对该变量的访问,但有些比其他的更昂贵。
- 无需访问散列的每个元素,只需访问一些值即可。
tie
是一种更昂贵的魔法形式,这里不需要它。
因此,最有效的解决方案如下:
use Time::HiRes qw( time );
use Variable::Magic qw( cast wizard );
{
my $wiz = wizard(
data => sub { my $code = $_[1]; $code },
get => sub { ${ $_[0] } = $_[1]->(); },
);
sub make_evaluator { cast($_[0], $wiz, $_[1]) }
}
my %hash;
$hash{key1} = 'value1';
make_evaluator($hash{key2}, sub { 'value2@'.time });
print("$hash{$_}\n") for qw( key1 key2 key2 );
输出:
value1
value2@1462548850.76715
value2@1462548850.76721
其他示例:
my %hash; make_evaluator($hash{key}, sub { ... });
my $hash; make_evaluator($hash->{$key}, sub { ... });
my $x; make_evaluator($x, sub { ... });
make_evaluator(my $x, sub { ... });
make_evaluator(..., sub { ... });
make_evaluator(..., \&some_sub);
您还可以 "fix up" 现有哈希。在您的哈希方案中,
my $hoh = {
{
key1 => 'value1',
key2 => sub { ... },
...
},
...
);
for my $h (values(%$hoh)) {
for my $v (values(%$h)) {
if (ref($v) eq 'CODE') {
make_evaluator($v, $v);
}
}
}
我有一个散列,其中有一些值不是标量数据,而是 return 标量数据的匿名子例程。我想让这对在散列中查找值的代码部分完全透明,这样它就不必知道某些散列值可能是 return 标量数据的匿名子例程不仅仅是普通的旧标量数据。
为此,有没有什么方法可以在访问匿名子例程的键时执行它们,而无需使用任何特殊语法?这是一个说明目标和问题的简化示例:
#!/usr/bin/perl
my %hash = (
key1 => "value1",
key2 => sub {
return "value2"; # In the real code, this value can differ
},
);
foreach my $key (sort keys %hash) {
print $hash{$key} . "\n";
}
我想要的输出是:
perl ./test.pl
value1
value2
相反,这是我得到的:
perl ./test.pl
value1
CODE(0x7fb30282cfe0)
您需要确定代码引用何时存在,然后将其作为实际调用执行:
foreach my $key (sort keys %hash) {
if (ref $hash{$key} eq 'CODE'){
print $hash{$key}->() . "\n";
}
else {
print "$hash{$key}\n";
}
}
请注意,您可以考虑将所有哈希值设为 sub(真正的调度 table),而不是让一些 return 非代码引用和一些 return 引用。
但是,如果您这样定义散列,则在使用散列时不必做任何特殊的技巧。它在查找键时直接调用 sub 和 returns 值。
key2 => sub {
return "value2";
}->(),
不,不是没有一些辅助代码。您要求一个简单的标量值和一个代码引用以相同的方式运行。这样做的代码远非简单,而且还会在您的哈希及其使用之间注入复杂性。您可能会发现以下方法更简单、更清晰。
您可以使所有值代码引用,使哈希成为 调度 table,用于统一调用
my %hash = (
key1 => sub { return "value1" },
key2 => sub {
# carry on some processing ...
return "value2"; # In the real code, this value can differ
},
);
print $hash{$_}->() . "\n" for sort keys %hash;
当然,这种方法的开销很小。
是的,你可以。您可以使用 tie
hash to implementation that will resolve coderefs to their return values or you can use blessed scalars as values with overload
ed 方法来进行字符串化、谦化以及您想要自动解析的任何其他上下文。
tie
、重载或魔术变量。然而,这将是不必要的复杂化和毫无意义的混淆。尽管这些技巧很酷,但在实际代码中使用它们至少在 99% 的情况下都是错误的。
实际上,最简单和最干净的解决方案可能是编写一个带有标量的辅助子例程,如果它是代码引用,则执行它并 returns 结果:
sub evaluate {
my $val = shift;
return $val->() if ref($val) eq 'CODE';
return $val; # otherwise
}
foreach my $key (sort keys %hash) {
print evaluate($hash{$key}) . "\n";
}
Perl 针对此类用例的特殊功能之一是 tie
。这允许您将面向对象的样式方法附加到标量或散列。
应谨慎使用,因为它可能意味着您的代码以意想不到的方式做着非常奇怪的事情。
但举个例子:
#!/usr/bin/env perl
package RandomScalar;
my $random_range = 10;
sub TIESCALAR {
my ( $class, $range ) = @_;
my $value = 0;
bless $value, $class;
}
sub FETCH {
my ($self) = @_;
return rand($random_range);
}
sub STORE {
my ( $self, $range ) = @_;
$random_range = $range;
}
package main;
use strict;
use warnings;
tie my $random_var, 'RandomScalar', 5;
for ( 1 .. 10 ) {
print $random_var, "\n";
}
$random_var = 100;
for ( 1 .. 10 ) {
print $random_var, "\n";
}
如您所见 - 这让您可以使用 'ordinary' 标量,并用它做一些有趣的事情。您可以使用与 hash
非常相似的机制 - 一个示例可能是进行数据库查找。
但是,您也需要非常谨慎 - 因为这样做是在远距离创建动作。未来的维护程序员可能不会期望您的 $random_var
每次 运行 都会实际更改,并且实际上不会 'set' 赋值。
它可以非常有用,例如。虽然正在测试,这就是我举个例子的原因。
在您的示例中 - 您可能 'tie' 哈希:
#!/usr/bin/env perl
package MagicHash;
sub TIEHASH {
my ($class) = @_;
my $self = {};
return bless $self, $class;
}
sub FETCH {
my ( $self, $key ) = @_;
if ( ref( $self->{$key} ) eq 'CODE' ) {
return $self->{$key}->();
}
else {
return $self->{$key};
}
}
sub STORE {
my ( $self, $key, $value ) = @_;
$self->{$key} = $value;
}
sub CLEAR {
my ($self) = @_;
$self = {};
}
sub FIRSTKEY {
my ($self) = @_;
my $null = keys %$self; #reset iterator
return each %$self;
}
sub NEXTKEY {
my ($self) = @_;
return each %$self;
}
package main;
use strict;
use warnings;
use Data::Dumper;
tie my %magic_hash, 'MagicHash';
%magic_hash = (
key1 => 2,
key2 => sub { return "beefcake" },
);
$magic_hash{random} = sub { return rand 10 };
foreach my $key ( keys %magic_hash ) {
print "$key => $magic_hash{$key}\n";
}
foreach my $key ( keys %magic_hash ) {
print "$key => $magic_hash{$key}\n";
}
foreach my $key ( keys %magic_hash ) {
print "$key => $magic_hash{$key}\n";
}
这样就少了点邪恶,因为以后的维护程序员可以正常使用你的'hash'。但是动态评估可能会搬起石头砸自己的脚,所以还是要小心。
另一种方法是 'proper' 面向对象 - 创建一个 'storage object' ……基本上与上述类似 - 只是它创建了一个对象,而不是使用 tie
。这对于长期使用来说应该更清楚,因为你不会得到意想不到的行为。 (这是一个正在施展魔法的对象,这很正常,而不是 'works funny' 的散列)。
我不认为其他人写的不赞成 tie
mechanism 的话是有根据的。 None 的作者似乎正确理解它的工作原理以及可用的核心库备份
这是一个基于 Tie::StdHash
tie
示例
如果您将散列绑定到 Tie::StdHash
class,那么它可以像普通散列一样工作。这意味着除了您可能想要覆盖的方法之外,没有什么可写的了
在这种情况下,我覆盖了 TIEHASH
,这样我就可以在与 tie
命令相同的语句中指定初始化列表,而 FETCH
调用 superclass的FETCH,如果恰好是子程序引用则调用它
除了您要求的更改外,您的绑定哈希将正常工作。我希望很明显,如果您已将子例程引用存储为散列值,则不再有直接的方法来检索子例程引用。这样的值将始终被不带任何参数调用它的结果替换
SpecialHash.pm
package SpecialHash;
use Tie::Hash;
use base 'Tie::StdHash';
sub TIEHASH {
my $class = shift;
bless { @_ }, $class;
}
sub FETCH {
my $self = shift;
my $val = $self->SUPER::FETCH(@_);
ref $val eq 'CODE' ? $val->() : $val;
}
1;
main.pl
use strict;
use warnings 'all';
use SpecialHash;
tie my %hash, SpecialHash => (
key1 => "value1",
key2 => sub {
return "value2"; # In the real code, this value can differ
},
);
print "$hash{$_}\n" for sort keys %hash;
输出
value1
value2
更新
听起来您的真实情况是使用看起来像这样的现有哈希
my %hash = (
a => {
key_a1 => 'value_a1',
key_a2 => sub { 'value_a2' },
},
b => {
key_b1 => sub { 'value_b1' },
key_b2 => 'value_b2',
},
);
在已经填充的变量上使用 tie
不如在声明点绑定 then 然后插入值那么整洁,因为必须将数据复制到绑定的对象。然而,我在 SpecialHash
class 中编写 TIEHASH
方法的方式使得在 tie
语句
如果可能,最好先tie
每个散列,然后再将数据放入其中并添加它到主哈希
该程序将 %hash
中恰好是散列引用的每个值联系起来。这里面的核心就是语句
tie %$val, SpecialHash => ( %$val )
其功能与
相同tie my %hash, SpecialHash => ( ... )
在前面的代码中取消引用 $val
以使语法有效,并且还使用散列的当前内容作为绑定散列的初始化数据。这就是数据被复制的方式
之后只有几个嵌套循环转储整个 %hash
以验证关系是否有效
use strict;
use warnings 'all';
use SpecialHash;
my %hash = (
a => {
key_a1 => 'value_a1',
key_a2 => sub { 'value_a2' },
},
b => {
key_b1 => sub { 'value_b1' },
key_b2 => 'value_b2',
},
);
# Tie all the secondary hashes that are hash references
#
for my $val ( values %hash ) {
tie %$val, SpecialHash => ( %$val ) if ref $val eq 'HASH';
}
# Dump all the elements of the second-level hashes
#
for my $k ( sort keys %hash ) {
my $v = $hash{$k};
next unless ref $v eq 'HASH';
print "$k =>\n";
for my $kk ( sort keys %$v ) {
my $vv = $v->{$kk};
print " $kk => $v->{$kk}\n"
}
}
输出
a =>
key_a1 => value_a1
key_a2 => value_a2
b =>
key_b1 => value_b1
key_b2 => value_b2
有一个名为 "magic" 的功能允许在访问变量时调用代码。
向变量添加魔法会大大减慢对该变量的访问,但有些比其他的更昂贵。
- 无需访问散列的每个元素,只需访问一些值即可。
tie
是一种更昂贵的魔法形式,这里不需要它。
因此,最有效的解决方案如下:
use Time::HiRes qw( time );
use Variable::Magic qw( cast wizard );
{
my $wiz = wizard(
data => sub { my $code = $_[1]; $code },
get => sub { ${ $_[0] } = $_[1]->(); },
);
sub make_evaluator { cast($_[0], $wiz, $_[1]) }
}
my %hash;
$hash{key1} = 'value1';
make_evaluator($hash{key2}, sub { 'value2@'.time });
print("$hash{$_}\n") for qw( key1 key2 key2 );
输出:
value1
value2@1462548850.76715
value2@1462548850.76721
其他示例:
my %hash; make_evaluator($hash{key}, sub { ... });
my $hash; make_evaluator($hash->{$key}, sub { ... });
my $x; make_evaluator($x, sub { ... });
make_evaluator(my $x, sub { ... });
make_evaluator(..., sub { ... });
make_evaluator(..., \&some_sub);
您还可以 "fix up" 现有哈希。在您的哈希方案中,
my $hoh = {
{
key1 => 'value1',
key2 => sub { ... },
...
},
...
);
for my $h (values(%$hoh)) {
for my $v (values(%$h)) {
if (ref($v) eq 'CODE') {
make_evaluator($v, $v);
}
}
}