为什么 Types::Standard 打破 Scalar::Util::readonly?

Why does Types::Standard break Scalar::Util::readonly?

Perl 5.30.0。截至今天,图书馆是最新的。

如果散列是只读的,我希望 Scalar::Util::readonly 到 return 一些真实值,而且确实如此:

perl -MReadonly -M'Scalar::Util qw(readonly)' -E'
   say readonly(%ENV);
   Readonly::Hash %ENV => %ENV;
   say readonly(%ENV);
'
0
134283264

...除非我想使用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

我查看了 Types::Standard 的未决问题,但没有直接描述我的问题。
这是怎么回事?

这不是使用 readonly 的正确方法。

不可能将散列传递给子程序。只有标量可以作为参数传递给 subs。原型可用于使它看起来像您正在将哈希传递给子项,但这里不是这种情况。

$ perl -E'
   use Scalar::Util qw( readonly );
   say prototype( "readonly" ) // "[none]";
'
$

那个原型的意思是

readonly( %ENV )

表示

&readonly( scalar( %ENV ) )

它不检查 %ENV 是否为 read-only;它检查在标量上下文中评估 %ENV 获得的值是否为 read-only。这是完全错误的。

Scalar::Util::readonly 不能用于检查散列(或数组)是否为 readonly,只能用于标量。


那么如何检查哈希是否为 read-only?

嗯,Perl 提供了一个内置的子工程,就像 Scalar::Util::readonly 一样,叫做 Internals::SvREADONLY。与 readonly 不同,SvREADONLY 适用于数组作为散列和标量。

$ perl -E'say prototype( "Internals::SvREADONLY" ) // "[none]";'
\[$%@];$

此原型导致传递对第一个参数的引用而不是参数本身。因此,

Internals::SvREADONLY( %x )

的缩写
&Internals::SvREADONLY( \%x )

事实是,由 Readonly::Hash 编辑的散列 return 实际上并不是 read-only。所以 Internals::SvREADONLYScalar::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

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

以下是模块如何检查它是否已经进行了散列 read-only:

tied( %x ) =~ 'Readonly::Hash'

那么为什么使用Readonly::Hash后的输出差异呢?

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

嗯,这是由于 Readonly::Hash 中的一个错误:它 return 在标量上下文中是错误的值。

$ 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

当在标量上下文中使用 %x 时,Perl returns 散列中的元素数。[1]

另一方面,Readonly::Hash 添加的魔法使其在散列不为空时 return 为真值,在散列为空时为假值。

这就是区别所在。

Perl returns 是临时的,又名凡人标量。它被创建为包含 returned 值,并且在调用者有机会复制它后将被释放。没必要花时间去做 read-only.[2]

Readonly::Hash,另一方面,return不只是任何真或假标量。它 return 是由每个 Perl 运算符 return 编辑的非常相同的真和假标量,return 是真或假。不是副本,而是完全相同的标量,&PL_sv_yes&PL_sv_no.[3] 这些标量是 read-only.[4 ]


那么为什么Types::Standard会有效果呢?

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

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


  1. 情况并非总是如此。它从未 return 只是 true/false,但旧值实际上仅用作 true/false 值。

  2. 有点难度,你可以修改它(my $r = \scalar(%x); ++$$r),但没有意义。这样做对散列没有影响。

  3. 琐事:与 &PL_sv_undef 一起,它们是仅有的三个 statically-allocated 标量。

  4. 它们是 read-only 因为我们不希望 4 == 5 开始 return 一个真实的值,因为 &PL_sv_no 被意外更改了。