为什么 Types::Standard 会破坏 Scalar::Util::readonly?

rob*_*but 4 perl

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 的未解决问题,但没有任何内容直接描述我的问题。
这里发生了什么 ?

ike*_*ami 7

这不是正确的使用方法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. 与 不同的是readonlySvREADONLY它可以作为散列和标量来处理数组。

$ 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 有效果呢?

虽然这个问题没有实际意义,但这仍然是一个有趣的问题。

不幸的是,我还没有弄清楚这一点。


  1. 但情况并非总是如此。它从来不只返回真/假,但旧值实际上仅用作真/假值。

  2. 遇到一些困难,您可以修改它(my $r = \scalar(%x); ++$$r),但没有意义。这样做不会对哈希产生影响。

  3. 琐事:与 一起&PL_sv_undef,它们是仅有的三个静态分配的标量。

  4. 它们是只读的,因为我们不想因为意外更改而4 == 5开始返回真实值。&PL_sv_no