Perl 5.30.0。截至今天,库都是最新的。
如果哈希是只读的,我希望 Scalar::Util::readonly 返回一些真实值,事实上它确实如此:
perl -MReadonly -M'Scalar::Util qw(readonly)' -E'
say readonly(%ENV);
Readonly::Hash %ENV => %ENV;
say readonly(%ENV);
'
0
134283264
Run Code Online (Sandbox Code Playgroud)
...除了当我也想使用 Types::Standard 时,那么 Scalar::Util::readonly 不再有效?!
perl -MReadonly -M'Scalar::Util qw(readonly)' -MTypes::Standard -E'
say readonly(%ENV);
Readonly::Hash %ENV => %ENV;
say readonly(%ENV);
'
0
0
Run Code Online (Sandbox Code Playgroud)
我查看了 Types::Standard 的未解决问题,但没有任何内容直接描述我的问题。
这里发生了什么 ?
这不是正确的使用方法readonly。
将哈希值传递给子进程是不可能的。只有标量可以作为参数传递给 subs。原型可用于使其看起来像是将哈希值传递给子对象,但这里的情况并非如此。
$ perl -E'
use Scalar::Util qw( readonly );
say prototype( "readonly" ) // "[none]";
'
$
Run Code Online (Sandbox Code Playgroud)
该原型意味着
readonly( %ENV )
Run Code Online (Sandbox Code Playgroud)
方法
&readonly( scalar( %ENV ) )
Run Code Online (Sandbox Code Playgroud)
它不检查是否%ENV是只读的;它检查在标量上下文中求值所获得的值是否%ENV是只读的。这是完全错误的。
Scalar::Util::readonly不能用于检查哈希(或数组)是否为readonly,只能用于检查标量。
那么如何检查哈希是否是只读的呢?
嗯,Perl 提供了一个内置的子工程,就像Scalar::Util::readonly称为Internals::SvREADONLY. 与 不同的是readonly,SvREADONLY它可以作为散列和标量来处理数组。
$ perl -E'say prototype( "Internals::SvREADONLY" ) // "[none]";'
\[$%@];$
Run Code Online (Sandbox Code Playgroud)
此原型导致传递对第一个参数的引用,而不是参数本身。像这样,
Internals::SvREADONLY( %x )
Run Code Online (Sandbox Code Playgroud)
是缩写
&Internals::SvREADONLY( \%x )
Run Code Online (Sandbox Code Playgroud)
问题是,返回的哈希值Readonly::Hash实际上并不是只读的。所以Internals::SvREADONLY没有什么用处Scalar::Util::readonly。
$ perl -E'
use Readonly qw( );
say Internals::SvREADONLY( %x ) ?1:0;
Readonly::Hash %x => %x;
say Internals::SvREADONLY( %x ) ?1:0;
'
0
0
Run Code Online (Sandbox Code Playgroud)
Readonly::Hash用于tie拦截更改哈希值的尝试。
$ perl -E'
use Devel::Peek qw( Dump );
use Readonly qw( );
Readonly::Hash %x => %x;
Dump( %x );
'
SV = PVHV(0x561f1e51b340) at 0x561f1e5435a8
REFCNT = 1
FLAGS = (RMG,OOK,SHAREKEYS) <--- No READONLY flag.
MAGIC = 0x561f1e558290
MG_VIRTUAL = &PL_vtbl_pack
MG_TYPE = PERL_MAGIC_tied(P) <--- tie() magic was added
MG_FLAGS = 0x02 to intercept attempts
REFCOUNTED to change the hash.
MG_OBJ = 0x561f1e515680
SV = IV(0x561f1e515670) at 0x561f1e515680
REFCNT = 1
FLAGS = (ROK)
RV = 0x561f1e5d39b8
SV = PVHV(0x561f1e51b400) at 0x561f1e5d39b8
REFCNT = 1
FLAGS = (OBJECT,SHAREKEYS)
STASH = 0x561f1e5d3c88 "Readonly::Hash"
ARRAY = 0x0
KEYS = 0
FILL = 0
MAX = 7
AUX_FLAGS = 0
ARRAY = 0x561f1e541950
KEYS = 0
FILL = 0
MAX = 7
RITER = -1
EITER = 0x0
RAND = 0x2685e09f
Run Code Online (Sandbox Code Playgroud)
以下是模块如何检查是否已将哈希设置为只读:
tied( %x ) =~ 'Readonly::Hash'
Run Code Online (Sandbox Code Playgroud)
Readonly::Hash那么为什么使用后输出会有差异呢?
虽然这个问题没有实际意义,但这仍然是一个有趣的问题。
嗯,这是由于 中的错误造成的Readonly::Hash:它在标量上下文中返回错误的值。
$ perl -E'
use Readonly qw( );
my %x = ( a=>4, b=>5, c=>6 );
say scalar( %x );
Readonly::Hash %x => %x;
say scalar( %x );
'
3
1
Run Code Online (Sandbox Code Playgroud)
当%x在标量上下文中使用时,Perl 返回散列中的元素数量。[1]
另一方面,添加的魔法Readonly::Hash使其在散列不为空时返回真值,在散列为空时返回假值。
差异就在于此。
Perl 返回一个计数作为临时标量。它被创建来包含返回的值,并将在调用者有机会复制它后被释放。花时间将其设置为只读是没有意义的。[2]
另一方面,Readonly::Hash 不仅返回任何 true 或 false 标量。它返回与每个返回 true 或 false 的 Perl 运算符返回的完全相同的 true 和 false 标量。不是副本,而是完全相同的标量,&PL_sv_yes并且&PL_sv_no. [3]这些标量是只读的。[4]
那么为什么 Types::Standard 有效果呢?
虽然这个问题没有实际意义,但这仍然是一个有趣的问题。
不幸的是,我还没有弄清楚这一点。
但情况并非总是如此。它从来不只返回真/假,但旧值实际上仅用作真/假值。
遇到一些困难,您可以修改它(my $r = \scalar(%x); ++$$r),但没有意义。这样做不会对哈希产生影响。
琐事:与 一起&PL_sv_undef,它们是仅有的三个静态分配的标量。
它们是只读的,因为我们不想因为意外更改而4 == 5开始返回真实值。&PL_sv_no