Spec-Zone.ru › Perl 5.34

overload

СОДЕРЖАНИЕ

  • НАЗВАНИЕ
  • СИНТАКСИС
  • ОПИСАНИЕ
    • Основы
      • Объявление
      • Правила вызова и магическое автогенерирование
      • Математическая магия, мутаторы и конструкторы копирования
    • Перегружаемые операции
    • Магическое автогенерирование
      • Минимальный набор перегруженных операций
    • Специальные ключи для использования перегрузки
      • nomethod
      • fallback
      • Конструктор копирования
    • Как Perl выбирает реализацию оператора
    • Потеря перегрузки
    • Наследование и перегрузка
    • Перегрузка во время выполнения
    • Общедоступные функции
    • Перегрузка констант
  • РЕАЛИЗАЦИЯ
  • РЕЦЕПТЫ
    • Двуликие скаляры
    • Двуликие ссылки
    • Символьный калькулятор
    • Действительно символьный калькулятор
  • АВТОР
  • СМОТРИТЕ ТАКЖЕ
  • ДИАГНОСТИКА
  • ОШИБКИ И ЗАСАДЫ

НАЗВАНИЕ

overload - Пакет для перегрузки операций Perl

СИНТАКСИС

package SomeThing;

use overload
    '+' => \&myadd,
    '-' => \&mysub;
    # etc
...

package main;
$a = SomeThing->new( 57 );
$b = 5 + $a;
...
if (overload::Overloaded $b) {...}
...
$strval = overload::StrVal $b;

ОПИСАНИЕ

Эта макрокоманда позволяет перегружать операторы Perl для класса. Чтобы перегрузить встроенные функции, см. "Переопределение встроенных функций" в perlsub вместо этого.

Основы

Объявление

Аргументы директивы use overload являются парами (ключ, значение). Полный список допустимых ключей см. в Перегружаемые операции ниже.

Реализации операторов (значения) могут быть подпрограммами, ссылками на подпрограммы или анонимными подпрограммами — то есть, чем угодно, что допустимо внутри &{ ... } вызова. Значения, заданные в виде строк, интерпретируются как имена методов. Таким образом

package Number;
use overload
    "-" => "minus",
    "*=" => \&muas,
    '""' => sub { ...; };

объявляет, что вычитание должно выполняться методом minus() в классе Number (или в одном из его базовых классов), а функция Number::muas() должна использоваться для присваивания вида умножения, *=. Она также определяет анонимную подпрограмму для строкового представления: она вызывается всякий раз, когда объект, благословленный в пакет Number, используется в контексте строки (эта подпрограмма может, например, возвращать число в виде римской цифры).

Правила вызова и магическое автогенерирование

Следующая примерная реализация minus() (которая предполагает, что объекты Number — это просто благословлённые ссылки на скаляры) демонстрирует правила вызова:

package Number;
sub minus {
    my ($self, $other, $swap) = @_;
    my $result = $$self - $other;         # *
    $result = -$result if $swap;
    ref $result ? $result : bless \$result;
}
# * may recurse once - see table below

В все подпрограммы, указанные в директиве use overload, передаются три аргумента (есть исключения — см. ниже, особенно nomethod).

Первый из них — операнд, обеспечивающий реализацию перегруженного оператора — в данном случае, объект, метод minus() которого вызывается.

Второй аргумент — это другой операнд или undef в случае унарного оператора.

Третий аргумент устанавливается в TRUE, если (и только если) два операнда были поменены местами. Perl может сделать это, чтобы гарантировать, что первый аргумент ($self) — это объект, реализующий перегруженную операцию, в соответствии с общими правилами вызова объектов. Например, если $x и $y являются Number:

operation   |   generates a call to
============|======================
$x - $y     |   minus($x, $y, '')
$x - 7      |   minus($x, 7, '')
7 - $x      |   minus($x, 7, 1)

Perl также может использовать minus() для реализации других операторов, не указанных в директиве use overload, в соответствии с правилами Магического автогенерирования, описанными позже. Например, use overload выше не объявила подпрограмму для ни одного из операторов --, neg (ключ перегрузки для унарного минуса) или -=. Таким образом

operation   |   generates a call to
============|======================
-$x         |   minus($x, 0, 1)
$x--        |   minus($x, 1, undef)
$x -= 3     |   minus($x, 3, undef)

Обратите внимание на undef: где автогенерирование приводит к использованию метода стандартного оператора, который не изменяет ни один из своих операндов, например -, для реализации оператора, изменяющего операнд («мутаторы»: здесь, -- и -=), Perl передаёт undef в качестве третьего аргумента. Это всё ещё оценивается как FALSE, согласуется с тем фактом, что операнды не были поменены, но даёт подпрограмме возможность изменить своё поведение в этих случаях.

Во всех вышеприведённых примерах minus() требуется только для возвращения результата вычитания: Perl позаботится об присваивании $x. Фактически, такие методы не должны изменять свои операнды, даже если undef передаётся в качестве третьего аргумента (см. Перегружаемые операции).

Это не относится к реализациям ++ и --: ожидается, что они изменят свой операнд. Соответствующая реализация -- может выглядеть так

use overload '--' => "decr",
    # ...
sub decr { --${$_[0]}; }

Если включена функция "bitwise" (см. feature), подпрограммам, обрабатывающим &, |, ^ и ~, передаётся пятый аргумент TRUE. Это указывает, что вызывающая сторона ожидает числового поведения. Четвёртый аргумент будет undef, поскольку эта позиция ($_[3]) зарезервирована для использования nomethod.

Математическая магия, мутаторы и конструкторы копирования

Термин «математическая магия» описывает перегруженную реализацию математических операторов. Математическая магия поднимает вопрос. Рассмотрим код:

$a = $b;
--$a;

Если $a и $b являются скалярами, то после этих утверждений

$a == $b - 1

Однако объект — это ссылка на благословлённые данные, поэтому если $a и $b являются объектами, то присваивание $a = $b копирует только ссылку, оставляя $a и $b ссылающимися на те же данные объекта. Поэтому можно ожидать, что операция --$a уменьшит $b так же, как и $a.

Однако это не согласуется с тем, как мы ожидаем работы математических операторов.

Perl разрешает эту дилемму, прозрачно вызывая конструктор копирования перед вызовом метода, определённого для реализации мутатора (--, += и т. д.). В вышеприведённом примере, когда Perl достигает оператора декремента, он создаёт копию данных объекта в $a и присваивает $a ссылку на скопированные данные. Только тогда он вызывает decr(), который изменяет скопированные данные, оставляя $b неизменным. Таким образом, метафора объекта сохраняется по возможности, в то время как математические операции по-прежнему работают в соответствии с метафорой арифметики.

Примечание: предыдущий абзац описывает то, что происходит, когда Perl автоматически генерирует конструктор копирования для объекта, основанного на скаляре. Для других случаев см. Конструктор копирования.

Перегружаемые операции

Полный список ключей, которые могут быть указаны в директиве use overload, приведены, разделённые пробелами, в значениях хэша %overload::ops.

with_assign      => '+ - * / % ** << >> x .',
assign           => '+= -= *= /= %= **= <<= >>= x= .=',
num_comparison   => '< <= > >= == !=',
'3way_comparison'=> '<=> cmp',
str_comparison   => 'lt le gt ge eq ne',
binary           => '& &= | |= ^ ^= &. &.= |. |.= ^. ^.=',
unary            => 'neg ! ~ ~.',
mutators         => '++ --',
func             => 'atan2 cos sin exp abs log sqrt int',
conversion       => 'bool "" 0+ qr',
iterators        => '<>',
filetest         => '-X',
dereferencing    => '${} @{} %{} &{} *{}',
matching         => '~~',
special          => 'nomethod fallback ='

Большинство перегружаемых операторов отображаются один к одному на эти ключи. Исключения, включая дополнительные перегружаемые операции, не очевидные из этого хэша, приведены в последующих примечаниях. Этот список может расширяться со временем.

Выдаётся предупреждение, если попытка зарегистрировать оператор, не найденный выше.

  • not

    Оператор not не является допустимым ключом для use overload. Однако, если оператор ! перегружен, то то же самое реализация будет использована для not (поскольку два оператора различаются только приоритетом).

  • neg

    Ключ neg используется для унарного минуса, чтобы отличить его от бинарного -.

  • ++, --

    Предполагая, что они должны вести себя аналогично ++ и -- в Perl, перегруженные реализации этих операторов обязаны изменять свои операнды.

    Различия между префиксной и постфиксной формами операторов инкремента и декремента не делается: они различаются только моментом, в котором Perl вызывает соответствующую подпрограмму при вычислении выражения.

  • Присваивания

    +=  -=  *=  /=  %=  **=  <<=  >>=  x=  .=
    &=  |=  ^=  &.=  |.=  ^.=

    Простое присваивание не может быть перегружено (ключ '=' используется для "Копирующего Конструктора"). В Perl есть способ сделать так, чтобы присваивания объекту выполняли любые нужные действия, но это связано с использованием tie(), а не overload - см. "tie" в perlfunc и примеры "COOKBOOK" ниже.

    Подпрограмма для варианта присваивания оператора обязана только возвращать результат операции. Разрешается изменять значение своего операнда (это безопасно, потому что Perl сначала вызывает копирующий конструктор), но это необязательно, так как Perl в любом случае присваивает возвращаемое значение левому операнду.

    Объект, перегружающий оператор присваивания, делает это только относительно присваиваний этому объекту. Другими словами, Perl никогда не вызывает соответствующие методы с третьим аргументом (аргументом "swap") установленным в TRUE. Например, операция

    $a *= $b

    не может привести к вызову реализации *= в $b, даже если $a является скаляром. (Однако это может вызвать вызов метода $b для *).

  • Не-мутаторы с мутаторным вариантом

    +  -  *  /  %  **  <<  >>  x  .
    &  |  ^  &.  |.  ^.

    Как описано выше, Perl может вызывать методы для операторов, таких как + и &, в процессе реализации отсутствующих операций, таких как ++, +=, и &=. Хотя эти методы могут обнаруживать это использование, проверяя определённость третьего аргумента, они во всех случаях должны избегать изменения своих операндов. Это связано с тем, что Perl не вызывает копирующий конструктор перед вызовом этих методов.

  • int

    Традиционно, функция Perl int округляет до 0 (см. "int" в perlfunc), поэтому для типов, похожих на числа с плавающей точкой, следует придерживаться той же семантики.

  • Преобразования строк, чисел, логических значений и регулярных выражений

    ""  0+  bool

    Эти преобразования вызываются в соответствии с контекстом по мере необходимости. Например, подпрограмма для '""' (преобразование в строку) может быть использована, когда перегруженный объект передаётся в качестве аргумента в print, а для 'bool' — когда он проверяется в условии оператора управления потоком (например, while) или условного оператора ?:.

    Конечно, в контекстах, например, $obj + 1, Perl вызовет реализацию $obj в +, а не (в данном примере) преобразование $obj в число с помощью метода numify '0+' (исключение из этого — когда метод не предоставлен для '+' и "fallback" установлен в TRUE).

    Подпрограммы для '""', '0+', и 'bool' могут возвращать любое произвольное значение Perl. Если соответствующая операция для этого значения также перегружена, операция будет вызвана снова с этим значением.

    В качестве специального случая, если перегрузка возвращает сам объект, он будет использован напрямую. Перегрузка преобразования, возвращающая объект, вероятно, является ошибкой, потому что вы, скорее всего, получите что-то, что выглядит как YourPackage=HASH(0x8172b34).

    qr

    Подпрограмма для 'qr' используется всякий раз, когда объект интерполируется в или используется как регулярное выражение, включая случаи, когда он появляется в правой части оператора =~ или !~.

    qr должно возвращать скомпилированное регулярное выражение или ссылку на скомпилированное регулярное выражение (например, то, что возвращает qr//), и любая дальнейшая перегрузка возвращаемого значения будет проигнорирована.

  • Итерация

    Если <> перегружен, то та же реализация используется для синтаксиса чтения из файлового дескриптора <$var> и синтаксиса глоббинга <${var}>.

  • Тесты файлов

    Ключ '-X' используется для указания подпрограммы для обработки всех операторов тестирования файлов (-f, -x, и так далее: см. "-X" в perlfunc для полного списка); перегрузить любой оператор тестирования файлов индивидуально невозможно. Для их различения буква, следующая за '-', передаётся в качестве второго аргумента (то есть в тот же слот, что и для бинарных операторов, используемый для передачи второго операнда).

    Вызов перегруженного оператора тестирования файлов не влияет на значение stat, связанное со специальным файловым дескриптором _. Оно по-прежнему ссылается на результат последнего stat, lstat или неперегруженного тестирования файла.

    Эта перегрузка была введена в Perl 5.12.

  • Сопоставление

    Ключ "~~" позволяет переопределить логику интеллектуального сопоставления, используемую оператором ~~ и конструкцией switch (given/when). См. "Switch Statements" в perlsyn и feature.

    Необычно, перегруженная реализация оператора интеллектуального сопоставления не получает полного контроля над поведением интеллектуального сопоставления. В частности, в следующем коде:

    package Foo;
    use overload '~~' => 'match';
    
    my $obj =  Foo->new();
    $obj ~~ [ 1,2,3 ];

    интеллектуальное сопоставление не вызывает вызов метода таким образом:

    $obj->match([1,2,3],0);

    вместо этого, правило распределения интеллектуального сопоставления имеет приоритет, поэтому $obj интеллектуально сопоставляется с каждым элементом массива по очереди до тех пор, пока не будет найдено соответствие, поэтому вы можете увидеть от одного до трёх таких вызовов:

    $obj->match(1,0);
    $obj->match(2,0);
    $obj->match(3,0);

    См. таблицу сопоставлений в "Оператор интеллектуального сопоставления" в perlop для получения подробностей о том, когда вызывается перегрузка.

  • Деферирование

    ${}  @{}  %{}  &{}  *{}

    Если эти операторы не явным образом перегружены, они работают стандартным образом, возвращая базовый скаляр, массив или что угодно, хранящий данные объекта (или соответствующее сообщение об ошибке, если оператор деферирования не соответствует ему). Определение универсальной функции 'nomethod' (см. ниже) не изменяет это, так как универсальная функция не будет вызвана для реализации отсутствующего оператора деферирования.

    Если оператор деферирования перегружен, он должен возвращать ссылку соответствующего типа (например, подпрограмма для ключа '${}' должна возвращать ссылку на скаляр, а не скаляр) или другой объект, который перегружает оператор: то есть подпрограмма только определяет, что деферируется, а фактическое деферирование остаётся на стороне Perl. В качестве специального случая, если подпрограмма возвращает сам объект, она больше не будет вызываться, чтобы избежать бесконечной рекурсии.

  • Специальное

    nomethod  fallback  =

    См. "Специальные ключи для использования перегрузки".

Автогенерация магических функций

Если метод для операции не найден, Perl пытается сгенерировать замещающую реализацию из определённых операций.

Примечание: поведение, описанное в этом разделе, может быть отключено путём установки fallback в FALSE (см. "fallback").

В следующих таблицах числа указывают приоритет. Например, таблица ниже указывает, что, если реализация для '!' не определена, Perl реализует её, используя 'bool' (то есть, инвертируя значение, возвращаемое методом для 'bool'); если преобразование в булево значение также не реализовано, Perl будет использовать '0+' или, в случае неудачи, '""'.

operator | can be autogenerated from
         |
         | 0+   ""   bool   .   x
=========|==========================
   0+    |       1     2
   ""    |  1          2
   bool  |  1    2
   int   |  1    2     3
   !     |  2    3     1
   qr    |  2    1     3
   .     |  2    1     3
   x     |  2    1     3
   .=    |  3    2     4    1
   x=    |  3    2     4        1
   <>    |  2    1     3
   -X    |  2    1     3

Примечание: операторы итератора ('<>') и тестирования файлов ('-X') работают как обычно: если операнд не является освященной ссылкой на glob или IO, он преобразуется в строку (с использованием метода для '""', '0+', или 'bool'), чтобы интерпретироваться как glob или имя файла.

operator | can be autogenerated from
         |
         |  <   <=>   neg   -=    -
=========|==========================
   neg   |                        1
   -=    |                        1
   --    |                   1    2
   abs   | a1    a2    b1        b2    [*]
   <     |        1
   <=    |        1
   >     |        1
   >=    |        1
   ==    |        1
   !=    |        1

* one from [a1, a2] and one from [b1, b2]

Так же, как числовые сравнения могут быть сгенерированы автоматически из метода для '<=>', строковые сравнения могут быть сгенерированы автоматически из метода для 'cmp':

 operators          |  can be autogenerated from
====================|===========================
 lt gt le ge eq ne  |  cmp

Аналогично, автогенерация для ключей '+=' и '++' аналогична '-=' и '--' выше:

operator | can be autogenerated from
         |
         |  +=    +
=========|==========================
    +=   |        1
    ++   |   1    2

И другие варианты присваивания аналогичны '+=' и '-=' (и аналогичны '.=' и 'x=' выше):

          operator ||  *= /= %= **= <<= >>= &= ^= |= &.= ^.= |.=
-------------------||-------------------------------------------
autogenerated from ||  *  /  %  **  <<  >>  &  ^  |  &.  ^.  |.

Обратите также внимание, что копирующий конструктор (ключ '=') может быть сгенерирован автоматически, но только для объектов, основанных на скалярах. См. "Копирующий Конструктор".

Минимальный набор перегруженных операций

Поскольку некоторые операции могут быть автоматически сгенерированы из других, существует минимальный набор операций, которые необходимо перегрузить, чтобы получить полный набор перегруженных операций. Конечно, сгенерированные автоматически операции могут не делать ровно то, что ожидает пользователь. Минимальный набор:

+ - * / % ** << >> x
<=> cmp
& | ^ ~ &. |. ^. ~.
atan2 cos sin exp log sqrt int
"" 0+ bool
~~

Из преобразований требуется только одно из строк, логических значений или чисел, потому что каждое может быть сгенерировано из любого из двух других.

Специальные ключи для использования перегрузки

nomethod

Ключ 'nomethod' используется для указания универсальной функции, которая должна вызываться для любого оператора, который не перегружен индивидуально. Указанная функция будет передана четырем параметрам. Первые три аргумента совпадают с теми, что были бы переданы соответствующему методу, если бы он был определён. Четвёртый аргумент — ключ use overload для этого отсутствующего метода. Если включена функция "bitwise" (см. feature), пятый аргумент TRUE передаётся подпрограммам, обрабатывающим &, |, ^ и ~, чтобы указать, что вызывающий ожидает числового поведения.

Например, если $a является объектом, благословлённым в пакет, объявляющий

use overload 'nomethod' => 'catch_all', # ...

то операция

3 + $a

может (если метод не объявлен специально для ключа '+') привести к вызову

catch_all($a, 3, 1, '+')

См. "Как Perl выбирает реализацию оператора".

fallback

Значение, присвоенное ключу 'fallback', указывает Perl, как усердно он должен пытаться найти альтернативный способ реализации отсутствующего оператора.

  • определён, но FALSE

    use overload "fallback" => 0, # ... ;

    Это отключает "Магическое автосоздание".

  • undef

    В случае по умолчанию, когда значению fallback явно не присвоено, магическое автосоздание включено.

  • TRUE

    То же, что и для undef, но если отсутствующий оператор не может быть автоматически сгенерирован, вместо вывода сообщения об ошибке Perl может вернуться к тому, что он бы сделал для этого оператора, если бы не было директивы use overload.

    Примечание: в большинстве случаев, особенно для "Копирующего конструктора", это маловероятно будет подходящим поведением.

См. "Как Perl выбирает реализацию оператора".

Копирующий конструктор

Как упоминалось выше, эта операция вызывается при применении мутатора к ссылке, которая разделяет свой объект с другой ссылкой. Например, если $b является математически магическим, а '++' перегружен с 'incr', и '=' перегружен с 'clone', тогда код

$a = $b;
# ... (other code which does not modify $a or $b) ...
++$b;

будет выполнен аналогично

$a = $b;
# ...
$b = $b->clone(undef, "");
$b->incr(undef, "");

Примечание:

  • Подпрограмма для '=' не перегружает оператор присваивания Perl: она используется только для того, чтобы позволить мутаторам работать, как описано здесь. (См. "Присваивания" выше.)

  • Как и для других операций, подпрограмма, реализующая '=', получает три аргумента, хотя последние два всегда undef и ''.

  • Копирующий конструктор вызывается только перед вызовом функции, объявленной для реализации мутатора, например, если ++$b; в коде выше выполняется через метод, объявленный для ключа '++' (или 'nomethod', переданный '++' в качестве четвёртого аргумента) или, при автосоздании, '+='. Он не вызывается, если операция инкремента выполняется вызовом метода для '+' так как в эквивалентном коде

    $a = $b;
    $b = $b + 1;

    данные, на которые ссылается $a, не изменяются присваиванием $b ссылки на новые данные объекта.

  • Копирующий конструктор не вызывается, если Perl определяет, что он не нужен, потому что нет других ссылок на данные, которые изменяются.

  • Если 'fallback' не определено или TRUE, то копирующий конструктор может быть сгенерирован автоматически, но только для объектов, основанных на скалярах. В других случаях его нужно определить явно. Если данные объекта хранятся, например, в массиве скаляров, то следующим может быть подходящим:

    use overload '=' => sub { bless [ @{$_[0]} ] },  # ...
  • Если 'fallback' равно TRUE и копирующий конструктор не определён, то для объектов, не основанных на скалярах, Perl может тихо перейти на простое присваивание — то есть, присваивание ссылки на объект. По сути, это отключает механизм копирующего конструктора, так как не создаётся новая копия данных объекта. Это почти наверняка не то, что нужно. (Однако это согласуется: например, резервным вариантом Perl для оператора ++ является инкремент самой ссылки.)

Как Perl выбирает реализацию оператора

Какой из них проверяется первым, nomethod или fallback? Если два операнда оператора разных типов и оба перегружают оператор, какая реализация используется? Ниже приведены правила приоритета:

  1. Если первый операнд объявил подпрограмму для перегрузки оператора, то используйте эту реализацию.

  2. В противном случае, если для первого операнда fallback TRUE или undefined, проверьте, разрешают ли правила автосоздания использовать другой из его операторов вместо него.

  3. Если оператор не присваивания (+=, -=, и т.д.), повторите шаг (1) относительно второго операнда.

  4. Повторите шаг (2) относительно второго операнда.

  5. Если у первого операнда есть метод "nomethod", используйте его.

  6. Если у второго операнда есть метод "nomethod", используйте его.

  7. Если fallback равно TRUE для обоих операндов, выполните обычную операцию для оператора, рассматривая операнды как числа, строки или булевы значения соответственно для оператора (см. примечание).

  8. Ничего не сработало — завершение работы.

Если есть только один операнд (или только один операнд с перегрузкой), проверки относительно другого операнда, описанные выше, пропускаются.

Есть исключения из вышеперечисленных правил для операций разыменования (которые, если шаг 1 не удался, всегда возвращаются к обычным встроенным реализациям — см. Разыменование) и для ~~ (у которого есть свой набор правил — см. Matching в разделе "Перегружаемые операции" выше).

Примечание к шагу 7: некоторые операторы имеют разный смысл в зависимости от типа своих операндов. Поскольку нет способа указать Perl, чтобы обрабатывать операнды как, например, числа вместо строк, результат здесь может не соответствовать ожиданиям. См. "ОШИБКИ И ЛОВУШКИ".

Потеря перегрузки

Ограничение для операции сравнения заключается в том, что даже если, например, cmp должен возвращать освящённую ссылку, сгенерированная функция lt будет производить только стандартное логическое значение на основе числового значения результата cmp. В частности, в этом случае необходима работающая числовая конвертация (возможно, выраженная через другие конвертации).

Аналогично, операторы .= и x= теряют свои математически магические свойства, если применяется замена строковой конвертации.

Когда вы применяете chop() к математически магическому объекту, он преобразуется в строку, и его математически магические свойства теряются. То же самое может произойти и с другими операциями.

Наследование и перегрузка

Перегрузка соблюдает наследование через иерархию @ISA. Наследование взаимодействует с перегрузкой двумя способами.

Имена методов в директиве use overload

Если value в

use overload key => value;

является строкой, она интерпретируется как имя метода, которое может (обычным способом) наследоваться от другого класса.

Перегрузка операции наследуется производными классами

Любой класс, производный от перегруженного класса, также перегружен и наследует его реализации операторов. Если один и тот же оператор перегружен в более чем одном предке, то реализация определяется обычными правилами наследования.

Например, если A наследуется от B и C (в таком порядке), B перегружает + с \&D::plus_sub, и C перегружает + с "plus_meth", то подпрограмма D::plus_sub будет вызвана для реализации операции + для объекта в пакете A.

Обратите внимание, что в версиях Perl до 5.18 наследование ключа fallback не регулировалось вышеуказанными правилами. Использовалось значение fallback в первом перегруженном предке. Это было исправлено в 5.18 для соответствия обычным правилам наследования.

Перегрузка во время выполнения

Поскольку все директивы use выполняются во время компиляции, единственный способ изменить перегрузку во время выполнения — это

eval 'use overload "+" => \&addmethod';

Вы также можете использовать

eval 'no overload "+", "--", "<="';

хотя использование этих конструкций во время выполнения вызывает сомнения.

Открытые функции

Пакет overload.pm предоставляет следующие открытые функции:

overload::StrVal(арг)

Возвращает строковое значение arg так же, как при отсутствии перегрузки строкового представления. Если вы используете это для получения адреса ссылки (полезно для проверки, указывают ли две ссылки на одно и то же), то вам может быть лучше использовать Scalar::Util::refaddr(), что быстрее.

overload::Overloaded(арг)

Возвращает true, если arg подвержено перегрузке некоторых операций.

overload::Method(объект, опер)

Возвращает undef или ссылку на метод, реализующий op.

Перегрузка констант

В некоторых приложениях Perl-парсер слишком сильно изменяет константы. Можно подключиться к этому процессу через функции overload::constant() и overload::remove_constant().

Эти функции принимают хеш в качестве аргумента. Признанные ключи этого хеша:

integer

для перегрузки целочисленных констант,

float

для перегрузки констант с плавающей запятой,

binary

для перегрузки восьмеричных и шестнадцатеричных констант,

q

для перегрузки q-строк, постоянных частей qq- и qx-строк и here-документов,

qr

для перегрузки постоянных частей регулярных выражений.

Соответствующие значения являются ссылками на функции, которые принимают три аргумента: первый — это начальная строковая форма константы, второй — то, как Perl интерпретирует эту константу, третий — как используется константа. Обратите внимание, что начальная строковая форма не содержит разделителей строк и у неё удалены обратные слэши в комбинациях с обратным слэшем-разделителем (следовательно, значение разделителя не имеет отношения к обработке этой строки). Возвращаемое значение этой функции — это то, как эта константа будет интерпретироваться Perl. Третий аргумент не определен, если только не для перегруженных констант q и qr, он равен q в контексте одинарных кавычек (получен из строк, регулярных выражений и HERE-документов с одинарными кавычками), он равен tr для аргументов операторов tr/y, он равен s для правой части оператора s, и он равен qq в противном случае.

Поскольку выражение "ab$cd,," — это просто сокращение для 'ab' . $cd . ',,', ожидается, что перегруженные строковые константы оснащены разумным перегруженным оператором конкатенации, иначе это приведёт к абсурдным результатам. Аналогично, отрицательные числа рассматриваются как отрицания положительных констант.

Обратите внимание, что вызывать функции overload::constant() и overload::remove_constant() из любого места, кроме методов import() и unimport(), вероятно, бессмысленно. Из этих методов они могут вызываться следующим образом

sub import {
   shift;
   return unless @_;
   die "unknown import: @_" unless @_ == 1 and $_[0] eq ':constant';
   overload::constant integer => sub {Math::BigInt->new(shift)};
}

РЕАЛИЗАЦИЯ

Всё нижеследующее может быть изменено RSN.

Таблица методов для всех операций кэшируется в магии для хеша таблицы символов для пакета. Кэш инвалидируется во время обработки use overload, no overload, новых определений функций и изменений в @ISA.

(Каждая SV-вещь имеет очередь магии, и магия — запись в этой очереди. Вот как одна переменная может участвовать в нескольких формах магии одновременно. Например, переменные окружения обычно имеют две формы одновременно: их магию %ENV и их магию taint. Однако магия, которая реализует перегрузку, применяется к хранилищам, которые редко используются непосредственно, поэтому не должна замедляться.)

Если пакет использует перегрузку, он несёт специальный флаг. Этот флаг также устанавливается при определении новых функций или модификации @ISA. Впоследствии будет небольшая потеря скорости при первой операции, которая поддерживает перегрузку, пока обновляются таблицы перегрузки. Если перегрузка отсутствует, флаг отключается. Таким образом, единственной потерей скорости после этого является проверка этого флага.

Ожидается, что аргументы методов, которые явно не должны быть изменены, являются константами (хотя это и не проверяется).

КУХАРСКАЯ КНИГА

Пожалуйста, добавьте примеры к тому, что следует!

Двуликие скаляры

Поместите это в two_face.pm в вашем каталоге библиотеки Perl:

package two_face;             # Scalars with separate string and
                              # numeric values.
sub new { my $p = shift; bless [@_], $p }
use overload '""' => \&str, '0+' => \&num, fallback => 1;
sub num {shift->[1]}
sub str {shift->[0]}

Используйте его следующим образом:

require two_face;
my $seven = two_face->new("vii", 7);
printf "seven=$seven, seven=%d, eight=%d\n", $seven, $seven+1;
print "seven contains 'i'\n" if $seven =~ /i/;

(Вторая строка создаёт скаляр, который имеет как строковое значение, так и числовое значение.) Это выводит:

seven=vii, seven=7, eight=8
seven contains 'i'

Двуликие ссылки

Предположим, что вы хотите создать объект, который доступен как ссылка на массив, так и ссылка на хеш.

package two_refs;
use overload '%{}' => \&gethash, '@{}' => sub { $ {shift()} };
sub new {
  my $p = shift;
  bless \ [@_], $p;
}
sub gethash {
  my %h;
  my $self = shift;
  tie %h, ref $self, $self;
  \%h;
}

sub TIEHASH { my $p = shift; bless \ shift, $p }
my %fields;
my $i = 0;
$fields{$_} = $i++ foreach qw{zero one two three};
sub STORE {
  my $self = ${shift()};
  my $key = $fields{shift()};
  defined $key or die "Out of band access";
  $$self->[$key] = shift;
}
sub FETCH {
  my $self = ${shift()};
  my $key = $fields{shift()};
  defined $key or die "Out of band access";
  $$self->[$key];
}

Теперь к объекту можно получить доступ, используя как синтаксис массива, так и синтаксис хеша:

my $bar = two_refs->new(3,4,5,6);
$bar->[2] = 11;
$bar->{two} == 11 or die 'bad hash fetch';

Обратите внимание на несколько важных особенностей этого примера. Во-первых, фактический тип $bar — это ссылка на скаляр, и мы не перегружаем разыменование скаляра. Таким образом, мы можем получить фактическое нео перегруженное содержимое $bar, просто используя $$bar (что мы делаем в функциях, которые перегружают разыменование). Аналогично, объект, возвращаемый методом TIEHASH(), является ссылкой на скаляр.

Во-вторых, каждый раз, когда используется синтаксис хеша, мы создаём новый связанный хеш. Это позволяет нам не беспокоиться о возможности циклической ссылки, которая приведёт к утечке памяти.

Обе эти проблемы можно исправить. Скажем, если мы хотим перегрузить разыменование хеша для ссылки на объект, который реализован как сам хеш, единственная проблема, которую нужно обойти, — это как получить доступ к этому фактическому хешу (в отличие от виртуального хеша, показанного перегруженным оператором разыменования). Вот один из возможных способов извлечения:

sub access_hash {
  my ($self, $key) = (shift, shift);
  my $class = ref $self;
  bless $self, 'overload::dummy'; # Disable overloading of %{}
  my $out = $self->{$key};
  bless $self, $class;        # Restore overloading
  $out;
}

Чтобы избежать создания связанного хеша при каждом доступе, можно использовать дополнительный уровень косвенности, который позволяет нециклическую структуру ссылок:

package two_refs1;
use overload '%{}' => sub { ${shift()}->[1] },
             '@{}' => sub { ${shift()}->[0] };
sub new {
  my $p = shift;
  my $a = [@_];
  my %h;
  tie %h, $p, $a;
  bless \ [$a, \%h], $p;
}
sub gethash {
  my %h;
  my $self = shift;
  tie %h, ref $self, $self;
  \%h;
}

sub TIEHASH { my $p = shift; bless \ shift, $p }
my %fields;
my $i = 0;
$fields{$_} = $i++ foreach qw{zero one two three};
sub STORE {
  my $a = ${shift()};
  my $key = $fields{shift()};
  defined $key or die "Out of band access";
  $a->[$key] = shift;
}
sub FETCH {
  my $a = ${shift()};
  my $key = $fields{shift()};
  defined $key or die "Out of band access";
  $a->[$key];
}

Теперь, если $baz перегружен таким образом, тогда $baz является ссылкой на ссылку на промежуточный массив, который содержит ссылку на фактический массив и доступный хеш. Объект связывания для доступа к хешу — это ссылка на ссылку на фактический массив, поэтому

  • Нет циклических ссылок.

  • Оба «объекта», благословлённые в класс two_refs1, — это ссылки на ссылку на массив, следовательно, ссылки на скаляр. Таким образом, выражение доступа $$foo->[$ind] не включает перегруженных операций.

Символьный калькулятор

Поместите это в symbolic.pm в вашем каталоге библиотеки Perl:

package symbolic;             # Primitive symbolic calculator
use overload nomethod => \&wrap;

sub new { shift; bless ['n', @_] }
sub wrap {
  my ($obj, $other, $inv, $meth) = @_;
  ($obj, $other) = ($other, $obj) if $inv;
  bless [$meth, $obj, $other];
}

Этот модуль очень необычен по сравнению с перегруженными модулями: он не предоставляет никаких обычных перегруженных операторов, вместо этого он обеспечивает реализацию для "nomethod". В этом примере подпрограмма nomethod возвращает объект, который инкапсулирует операции, выполняемые над объектами: symbolic->new(3) содержит ['n', 3], 2 + symbolic->new(3) содержит ['+', 2, ['n', 3]].

Вот пример скрипта, который «вычисляет» сторону описанного восьмиугольника, используя вышеуказанный пакет:

require symbolic;
my $iter = 1;                 # 2**($iter+2) = 8
my $side = symbolic->new(1);
my $cnt = $iter;

while ($cnt--) {
  $side = (sqrt(1 + $side**2) - 1)/$side;
}
print "OK\n";

Значение $side равно

['/', ['-', ['sqrt', ['+', 1, ['**', ['n', 1], 2]],
                     undef], 1], ['n', 1]]

Обратите внимание, что, хотя мы получили это значение с помощью небольшого скрипта, нет простого способа использовать это значение. На самом деле, это значение можно проверить в отладчике (см. perldebug), но только если bareStringify Oпция установлена, а не через команду p.

Если попытаться вывести это значение, то будет вызван перегруженный оператор "", который вызовет оператор nomethod. Результат этого оператора будет строковый, но этот результат снова будет типа symbolic, что приведёт к бесконечной петле.

Добавьте метод pretty-печати в модуль symbolic.pm:

sub pretty {
  my ($meth, $a, $b) = @{+shift};
  $a = 'u' unless defined $a;
  $b = 'u' unless defined $b;
  $a = $a->pretty if ref $a;
  $b = $b->pretty if ref $b;
  "[$meth $a $b]";
}

Теперь можно завершить скрипт следующим образом:

print "side = ", $side->pretty, "\n";

Метод pretty выполняет преобразование объекта в строку, поэтому естественно перегрузить оператор "" с помощью этого метода. Однако внутри такого метода нет необходимости красиво выводить компоненты $a и $b объекта. В вышеприведённой подпрограмме "[$meth $a $b]" — это конкатенация некоторых строк и компонентов $a и $b. Если эти компоненты используют перегрузку, оператор конкатенации будет искать перегруженный оператор .; если он не найден, он будет искать перегруженный оператор "". Таким образом, достаточно использовать

use overload nomethod => \&wrap, '""' => \&str;
sub str {
  my ($meth, $a, $b) = @{+shift};
  $a = 'u' unless defined $a;
  $b = 'u' unless defined $b;
  "[$meth $a $b]";
}

Теперь можно изменить последнюю строку скрипта на

print "side = $side\n";

что выводит

side = [/ [- [sqrt [+ 1 [** [n 1 u] 2]] u] 1] [n 1 u]]

и можно проверить значение в отладчике, используя все возможные методы.

Что-то всё ещё не так: рассмотрите переменную цикла $cnt скрипта. Это было число, а не объект. Мы не можем сделать это значение типа symbolic, так как цикл тогда не завершится.

Действительно, для завершения цикла $cnt должен стать ложным. Однако оператор bool для проверки ложности перегружен (в этот раз через перегруженный "") и возвращает длинную строку, поэтому любой объект типа symbolic является истинным. Для преодоления этого нам нужен способ сравнения объекта с 0. На самом деле, проще написать процедуру числового преобразования.

Вот текст symbolic.pm с добавленной такой процедурой (и немного изменённой str()):

package symbolic;             # Primitive symbolic calculator
use overload
  nomethod => \&wrap, '""' => \&str, '0+' => \&num;

sub new { shift; bless ['n', @_] }
sub wrap {
  my ($obj, $other, $inv, $meth) = @_;
  ($obj, $other) = ($other, $obj) if $inv;
  bless [$meth, $obj, $other];
}
sub str {
  my ($meth, $a, $b) = @{+shift};
  $a = 'u' unless defined $a;
  if (defined $b) {
    "[$meth $a $b]";
  } else {
    "[$meth $a]";
  }
}
my %subr = ( n => sub {$_[0]},
             sqrt => sub {sqrt $_[0]},
             '-' => sub {shift() - shift()},
             '+' => sub {shift() + shift()},
             '/' => sub {shift() / shift()},
             '*' => sub {shift() * shift()},
             '**' => sub {shift() ** shift()},
           );
sub num {
  my ($meth, $a, $b) = @{+shift};
  my $subr = $subr{$meth}
    or die "Do not know how to ($meth) in symbolic";
  $a = $a->num if ref $a eq __PACKAGE__;
  $b = $b->num if ref $b eq __PACKAGE__;
  $subr->($a,$b);
}

Вся работа по числовому преобразованию выполняется в %subr и num(). Конечно, %subr неполна, она содержит только операторы, используемые в примере ниже. Вот дополнительный вопрос: зачем нам нужна явная рекурсия в num()? (Ответ находится в конце этого раздела.)

Используйте этот модуль следующим образом:

require symbolic;
my $iter = symbolic->new(2);  # 16-gon
my $side = symbolic->new(1);
my $cnt = $iter;

while ($cnt) {
  $cnt = $cnt - 1;            # Mutator '--' not implemented
  $side = (sqrt(1 + $side**2) - 1)/$side;
}
printf "%s=%f\n", $side, $side;
printf "pi=%f\n", $side*(2**($iter+2));

Он выводит (без лишних переносов строк)

[/ [- [sqrt [+ 1 [** [/ [- [sqrt [+ 1 [** [n 1] 2]]] 1]
                        [n 1]] 2]]] 1]
   [/ [- [sqrt [+ 1 [** [n 1] 2]]] 1] [n 1]]]=0.198912
pi=3.182598

Вышеупомянутый модуль очень примитивный. Он не реализует методы мутаторов (++, -= и так далее), не выполняет глубокое копирование (не требуется без мутаторов!), и реализует только те арифметические операции, которые используются в примере.

Реализация большинства арифметических операций проста; нужно просто использовать таблицы операций и изменить код, который заполняет %subr, на

my %subr = ( 'n' => sub {$_[0]} );
foreach my $op (split " ", $overload::ops{with_assign}) {
  $subr{$op} = $subr{"$op="} = eval "sub {shift() $op shift()}";
}
my @bins = qw(binary 3way_comparison num_comparison str_comparison);
foreach my $op (split " ", "@overload::ops{ @bins }") {
  $subr{$op} = eval "sub {shift() $op shift()}";
}
foreach my $op (split " ", "@overload::ops{qw(unary func)}") {
  print "defining '$op'\n";
  $subr{$op} = eval "sub {$op shift()}";
}

Поскольку подпрограммы, реализующие операторы присваивания, не обязаны изменять свои операнды (см. "Перегружаемые операции" выше), нам не нужно ничего особенного, чтобы += и аналогичные функции работали, кроме добавления этих операторов в %subr и определения конструктора копирования (потребуется, поскольку Perl не может знать, что реализация '+=' не изменяет аргумент — см. "Конструктор копирования").

Чтобы реализовать конструктор копирования, добавьте '=' => \&cpy к строке use overload, и код (этот код предполагает, что мутаторы изменяют вещи только на один уровень вглубь, поэтому рекурсивное копирование не требуется):

sub cpy {
  my $self = shift;
  bless [@$self], ref $self;
}

Чтобы заставить ++ и -- работать, нам нужно реализовать фактические мутаторы, либо непосредственно, либо в nomethod. Мы продолжаем выполнять действия внутри nomethod, поэтому добавим

if ($meth eq '++' or $meth eq '--') {
  @$obj = ($meth, (bless [@$obj]), 1); # Avoid circular reference
  return $obj;
}

после первой строки wrap(). Это не самое эффективное решение, можно рассмотреть

sub inc { $_[0] = bless ['++', shift, 1]; }

вместо этого.

В заключение, обратите внимание, что можно заполнить %subr следующим образом:

my %subr = ( 'n' => sub {$_[0]} );
foreach my $op (split " ", $overload::ops{with_assign}) {
  $subr{$op} = $subr{"$op="} = eval "sub {shift() $op shift()}";
}
my @bins = qw(binary 3way_comparison num_comparison str_comparison);
foreach my $op (split " ", "@overload::ops{ @bins }") {
  $subr{$op} = eval "sub {shift() $op shift()}";
}
foreach my $op (split " ", "@overload::ops{qw(unary func)}") {
  $subr{$op} = eval "sub {$op shift()}";
}
$subr{'++'} = $subr{'+'};
$subr{'--'} = $subr{'-'};

Это завершает реализацию примитивного символьного калькулятора в 50 строках кода Perl. Поскольку числовые значения подвыражений не кэшируются, калькулятор очень медленный.

Вот ответ на задание: в случае str() нам не нужна явная рекурсия, так как перегруженный оператор . будет обращаться к существующему перегруженному оператору "". Перегруженные арифметические операторы не обращаются к числовому преобразованию, если fallback не запрошено явно. Таким образом, без явной рекурсии num() будет преобразовывать ['+', $a, $b] в $a + $b, что просто перестроит аргумент num().

Если вы задаётесь вопросом, почему значения по умолчанию для преобразования отличаются для str() и num(), обратите внимание на то, насколько легко написать символьный калькулятор. Эта простота обусловлена подходящим выбором значений по умолчанию. Ещё одно замечание: из-за явной рекурсии num() более хрупкий, чем sym(): нам нужно явно проверять тип $a и $b. Если компоненты $a и $b случайным образом окажутся одного типа, это может привести к проблемам.

Действительно Символьный калькулятор

Можно задаться вопросом, почему мы называем вышеприведенный калькулятор символическим. Причина в том, что фактическое вычисление значения выражения откладывается до момента, когда значение используется.

Чтобы увидеть это в действии, добавьте метод

sub STORE {
  my $obj = shift;
  $#$obj = 1;
  @$obj->[0,1] = ('=', shift);
}

в пакет symbolic. После этого изменения можно сделать

my $a = symbolic->new(3);
my $b = symbolic->new(4);
my $c = sqrt($a**2 + $b**2);

и числовое значение $c становится 5. Однако после вызова

$a->STORE(12);  $b->STORE(5);

числовое значение $c становится 13. Теперь нет сомнений, что модуль symbolic действительно предоставляет символический калькулятор.

Чтобы скрыть неровности под капотом, предоставьте интерфейс tie() для пакета symbolic. Добавьте методы

sub TIESCALAR { my $pack = shift; $pack->new(@_) }
sub FETCH { shift }
sub nop {  }          # Around a bug

(ошибка, исправленная в Perl 5.14, описана в "BUGS"). Этот новый интерфейс можно использовать так:

tie $a, 'symbolic', 3;
tie $b, 'symbolic', 4;
$a->nop;  $b->nop;    # Around a bug

my $c = sqrt($a**2 + $b**2);

Теперь числовое значение $c равно 5. После $a = 12; $b = 5 числовое значение $c становится 13. Для изоляции пользователя модуля добавьте метод

sub vars { my $p = shift; tie($_, $p), $_->nop foreach @_; }

Теперь

my ($a, $b);
symbolic->vars($a, $b);
my $c = sqrt($a**2 + $b**2);

$a = 3; $b = 4;
printf "c5  %s=%f\n", $c, $c;

$a = 12; $b = 5;
printf "c13  %s=%f\n", $c, $c;

показывает, что числовое значение $c следует изменениям значений $a и $b.

АВТОР

Илья Захаревич <ilya@math.mps.ohio-state.edu>.

СМОТРИТЕ ТАКЖЕ

Предикат overloading может использоваться для включения или отключения перегруженных операций в лексическом пространстве — см. перегрузку.

ДИАГНОСТИКА

Когда Perl запускается с переключателем -Do или его эквивалентом, перегрузка вызывает диагностические сообщения.

Используя команду m отладчика Perl (см. perldebug), можно определить, какие операции перегружены (и какой предок вызывает эту перегрузку). Скажем, если eq перегружен, то метод (eq отображается отладчиком. Метод () соответствует ключу fallback (на самом деле наличие этого метода показывает, что в этом пакете включена перегрузка, и именно она используется функцией Overloaded модуля overload).

Модуль может выдать следующие предупреждения:

Нечетное число аргументов для overload::constant

(W) Вызов overload::constant содержал нечетное число аргументов. Аргументы должны быть парами.

'%s' — не перегружаемый тип

(W) Вы попытались перегрузить тип константы, о котором пакет overload не знает.

'%s' не является ссылкой на код

(W) Второй (четвертый, шестой и т. д.) аргумент overload::constant должен быть ссылкой на код. Либо анонимная подпрограмма, либо ссылка на подпрограмму.

Аргумент перегрузки '%s' недействителен

(W) use overload был передан аргумент, который он не распознал. Возможно, вы ошиблись в написании оператора?

ОШИБКИ И ПОДВОХИ

  • Подвох, когда откат TRUE и Perl прибегает к встроенной реализации оператора, заключается в том, что некоторые операторы имеют более одного семантического значения, например |:

    use overload '0+' => sub { $_[0]->{n}; },
        fallback => 1;
    my $x = bless { n => 4 }, "main";
    my $y = bless { n => 8 }, "main";
    print $x | $y, "\n";

    Можно ожидать, что это выведет "12". На самом деле выводится "<": результат ASCII-представления "|" как битового оператора строк — то есть результат обработки операндов как строк "4" и "8", а не чисел. Тот факт, что numify (0+) реализован, но stringify ("") нет, не имеет значения, так как последний просто генерируется автоматически из первого.

    Единственный способ изменить это — предоставить свою подпрограмму для '|'.

  • Магическая автоматическая генерация увеличивает вероятность непреднамеренного создания самоссылочных структур. В настоящее время Perl не будет освобождать самоссылочные структуры, пока циклы явно не будут разорваны. Например,

    use overload '+' => 'add';
    sub add { bless [ \$_[0], \$_[1] ] };

    представляет опасность, так как

    $obj += $y;

    фактически станет

    $obj = add($obj, $y, undef);

    с тем же результатом, что и

    $obj = [\$obj, \$foo];

    Даже если в сценарии нет явных вариантов присваивания операторов, они могут быть сгенерированы оптимизатором. Например,

    "obj = $obj\n"

    может быть оптимизировано до

    my $tmp = 'obj = ' . $obj;  $tmp .= "\n";
  • Таблица символов заполнена именами, похожими на помехи.

  • Эта ошибка была исправлена в Perl 5.18, но все еще может вызвать проблемы, если вы используете более старые версии:

    Для целей наследования каждый перегруженный пакет ведет себя так, как будто fallback присутствует (возможно, не определен). Это может привести к интересным эффектам, если какой-либо пакет не перегружен, но наследуется от двух перегруженных пакетов.

  • До Perl 5.14 связь между перегрузкой и связыванием tie() была нарушена. Перегрузка вызывалась или нет, исходя из предыдущего класса связанной переменной.

    Это произошло потому, что проверка наличия перегрузки производилась слишком рано, до попытки любого доступа к связанной переменной. Если класс значения, извлеченного из связанной переменной FETCH(), не меняется, простым способом решения для кода, который должен работать в более старых версиях Perl, является доступ к значению (через () = $foo или подобное) сразу после связывания tie(), чтобы после этого вызова предыдущий класс совпадал с текущим.

  • Необработанные слова не охватываются перегруженными строковыми константами.

  • Оператор диапазона .. не может быть перегружен.

© 1993–2021 Larry Wall and others
Licensed under the GNU General Public License version 1 or later, or the Artistic License.
The Perl logo is a trademark of the Perl Foundation.
https://perldoc.perl.org/5.34.0/overload

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API