Spec-Zone.ru › Perl 5.28

перегрузка

СОДЕРЖАНИЕ

  • ИМЯ
  • СИНТАКСИС
  • ОПИСАНИЕ
    • Основы
      • Объявление
      • Правила вызова и магическое автосоздание
      • Математическая магия, мутаторы и конструкторы копирования
    • Перегружаемые операции
    • Магическое автосоздание
      • Минимальный набор перегруженных операций
    • Специальные ключи для использования перегрузки
      • 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 никогда не вызывает соответствующие методы со значением третьего аргумента (аргумента «обмена») равным 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);

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

  • Разыменование

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

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

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

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

    nomethod  fallback  =

    См. "Специальные ключи для use overload".

Автоматическое создание магических методов

Если метод для операции не найден, 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') работают нормально: если операнд не является благословлённой группой или ссылкой на входной поток, он преобразуется в строку (с использованием метода для '""', '0+', или 'bool'), которая интерпретируется как группа или имя файла.

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
~~

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

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

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 выбирает реализацию оператора

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

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

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

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

  4. Повторите шаг (2) по отношению ко второму операнду.

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

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

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

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

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

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

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

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

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

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

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

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

Перегрузка учитывает наследование через иерархию @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)

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

overload::Overloaded(arg)

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

overload::Method(obj,op)

Возвращает 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.

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

Если пакет использует перегрузку, он несет специальный флаг. Этот флаг также устанавливается при определении новых функций или модификации @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 параметр установлен, а не через p команду.

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

Добавьте метод красивого вывода в модуль 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 был передан аргумент, который он не распознал. Возможно, вы ошиблись при написании оператора?

ОШИБКИ И КАРМАННЫЕ ПОДВОХИ

  • Подвох, когда fallback равен 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() были нарушены. Перегрузка запускалась или нет, исходя из предыдущего класса привязанной переменной.

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

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

  • Оператор диапазона .. перегрузить нельзя.

© 1993–2020 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.28.3/overload

Spec-Zone.ru

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