Почему Types::Standard нарушает Scalar::Util::readonly?

Перл 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

... За исключением случаев, когда я также хочу использовать 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, но ничего не выскакивает из прямого описания моей проблемы.
Что здесь происходит ?

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
0
85
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Это неправильный способ использования readonly.

Невозможно передать хэш сабвуферу. Только скаляры могут быть переданы в качестве аргументов подпрограммам. Прототипы можно использовать, чтобы создать впечатление, что вы передаете хеш подпрограмме, но здесь это не так.

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

Этот прототип означает, что

readonly( %ENV )

означает

&readonly( scalar( %ENV ) )

Он не проверяет, доступен ли %ENV только для чтения; он проверяет, доступно ли значение, полученное в результате оценки %ENV в скалярном контексте, только для чтения. Это совершенно неправильно.

Scalar::Util::readonly нельзя использовать для проверки того, является ли хэш (или массив) readonly, только скаляры.


Так как же проверить, доступен ли хеш только для чтения?

Что ж, Perl предоставляет встроенную подпрограмму, которая работает точно так же, как Scalar::Util::readonly, называемую Internals::SvREADONLY. В отличие от readonly, SvREADONLY работает с массивами как с хэшами, так и со скалярами.

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

Этот прототип вызывает передачу ссылки на первый аргумент вместо самого аргумента. Как таковой,

Internals::SvREADONLY( %x )

сокращение от

&Internals::SvREADONLY( \%x )

Дело в том, что хэш, возвращаемый 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

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

Вот как модуль проверяет, сделал ли он уже хэш доступным только для чтения:

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

Так почему же используется разница в выводе после 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

Когда %x используется в скалярном контексте, Perl возвращает количество элементов в хэше.[1]

С другой стороны, магия, добавленная Readonly::Hash, заставляет возвращать истинное значение, когда хэш не пуст, и ложное значение, когда хэш пуст.

И в этом заключается разница.

Perl возвращает счетчик как временный скаляр. Он создается для хранения возвращаемого значения и будет освобожден после того, как вызывающая сторона сможет его скопировать. Нет смысла тратить время на то, чтобы сделать его доступным только для чтения.[2]

Readonly::Hash, с другой стороны, возвращает не просто любые истинные или ложные скаляры. Он возвращает одни и те же скаляры true и false, возвращаемые всеми операторами Perl, которые возвращают true или false. Не копии, а те самые скаляры, &PL_sv_yes и &PL_sv_no.[3] Эти скаляры доступны только для чтения.[4]


Так почему же Types::Standard имеет эффект?

Хотя вопрос спорный, на него все еще интересно ответить.

К сожалению, я не разобрался с этим.


  1. Так было не всегда. Он никогда не возвращал только значение true/false, но старое значение практически было полезно только как значение true/false.

  2. С некоторым трудом его можно было бы модифицировать (my $r = \scalar(%x); ++$$r), но смысла в этом не было бы. Это не повлияет на хэш.

  3. Интересный факт: наряду с &PL_sv_undef они являются единственными тремя статически распределенными скалярами.

  4. Они доступны только для чтения, потому что мы бы не хотели, чтобы 4 == 5 начала возвращать истинное значение, потому что &PL_sv_no было случайно изменено.

Отличный обзор, спасибо. Я рассмотрел альтернативы только для чтения, но все они, похоже, ведут себя немного по-разному — например, Const::Fast слишком строг (для меня) при доступе к несуществующим ключам («Попытка доступа к запрещенному ключу»).

robut 12.05.2022 15:54

Re "Я посмотрел на альтернативы Readonly", Почему? Проблема заключалась в том, что вы использовали readonly. Если это связано с тем, что %ro возвращает неверный результат в скалярном контексте, просто используйте вместо этого keys(%ro) (в скалярном контексте).

ikegami 12.05.2022 16:06

Другие вопросы по теме