overload
СОДЕРЖАНИЕ
- НАЗВАНИЕ
- СИНТАКСИС
- ОПИСАНИЕ
- РЕАЛИЗАЦИЯ
- КУЛИНАРНАЯ КНИГА
- АВТОР
- СМОТРИТЕ ТАКЖЕ
- ДИАГНОСТИКА
- ОШИБКИ И КОНФЛИКТЫ
НАЗВАНИЕ
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; ОПИСАНИЕ
Этот pragma позволяет перегрузить операторы 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 в случае унарного оператора.
Третий аргумент устанавливается в ИСТИНА, если (и только если) два операнда поменялись местами. 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 в качестве третьего аргумента. Это всё равно оценивается как ЛОЖЬ, согласуясь с тем, что операнды не поменялись местами, но даёт подпрограмме возможность изменить своё поведение в этих случаях.
Во всех вышеприведённых примерах minus() требуется только вернуть результат вычитания: Perl позаботится о присваивании $x. На самом деле, такие методы не должны изменять свои операнды, даже если undef передается в качестве третьего аргумента (см. "Перегружаемые операции").
Это не относится к реализациям ++ и --: ожидается, что они будут изменять свой операнд. Соответствующая реализация -- может выглядеть так
use overload '--' => "decr",
# ...
sub decr { --${$_[0]}; } Если включена функция "bitwise" (см. feature), в подпрограммы, обрабатывающие &, |, ^ и ~, передаётся пятый аргумент ИСТИНА. Это указывает, что вызывающая сторона ожидает числового поведения. Четвёртый аргумент будет 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" в 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') работают как обычно: если операнд не является освящённым глобальным или файловым дескриптором, он преобразуется в строку (с использованием метода для '""', '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, # ... ;Это отключает "Автогенерацию Magic".
-
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? Если два операнда оператора имеют разные типы и оба перегружают оператор, какая реализация используется? Ниже приведены правила приоритета:
-
Если первый операнд объявил подпрограмму для перегрузки оператора, используйте эту реализацию.
-
В противном случае, если обратная совместимость TRUE или неопределена для первого операнда, посмотрите, разрешают ли правила автоматического создания использование другого его оператора вместо него.
-
Если оператор не является оператором присваивания (
+=,-=, и т.д.), повторите шаг (1) относительно второго операнда. -
Повторите шаг (2) относительно второго операнда.
-
Если у первого операнда есть метод "nomethod", используйте его.
-
Если у второго операнда есть метод "nomethod", используйте его.
-
Если
fallbackравно TRUE для обоих операндов, выполните обычную операцию для оператора, рассматривая операнды как числа, строки или булевы значения, соответствующие оператору (см. примечание). -
Ничто не сработало — выведите ошибку.
Где существует только один операнд (или только один операнд с перегрузкой), проверки относительно другого операнда выше пропускаются.
Есть исключения из вышеизложенных правил для операций разыменования (которые, если шаг 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)
-
Возвращает строковое значение
arg, как и в случае отсутствия перегрузки для строк. Если вы используете это для получения адреса ссылки (полезно для проверки, указывают ли две ссылки на одно и то же), то лучше использоватьbuiltin::refaddr()илиScalar::Util::refaddr(), которые быстрее. - overload::Overloaded(arg)
-
Возвращает true, если
argподвержен перегрузке некоторых операций. - overload::Method(obj,op)
-
Возвращает
undefили ссылку на метод, реализующийop.Такой метод всегда принимает три аргумента, что будет выполнено, если это метод XS.
Перегрузка констант
Для некоторых приложений синтаксический анализатор Perl слишком сильно видоизменяет константы. Можно подключиться к этому процессу с помощью функций overload::constant() и overload::remove_constant().
Эти функции принимают хэш в качестве аргумента. Признанные ключи этого хэша:
- integer
-
для перегрузки целочисленных констант,
- float
-
для перегрузки констант с плавающей точкой,
- binary
-
для перегрузки восьмеричных и шестнадцатеричных констант,
- q
-
для перегрузки
q-строк, постоянных частейqq- иqx-строк и документов здесь, - qr
-
для перегрузки постоянных частей регулярных выражений.
Соответствующие значения — ссылки на функции, которые принимают три аргумента: первый — исходная строковая форма константы, второй — как Perl интерпретирует эту константу, третий — как используется константа. Обратите внимание, что исходная строковая форма не содержит разделителей строк и имеет обратные слеши в комбинациях обратных слешей с разделителями (следовательно, значение разделителя не имеет значения для обработки этой строки). Возвращаемое значение этой функции — то, как Perl будет интерпретировать эту константу. Третий аргумент не определен, если не для перегруженных q- и qr-констант, он равен q в контексте одиночной кавычки (получается из строк, регулярных выражений и документов здесь с одиночными кавычками), он равен 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 и их магию загрязнения. Однако магия, которая реализует перегрузку, применяется к хранилищам, которые редко используются непосредственно, поэтому не должна замедляться 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 — это ссылка на ссылку на промежуточный массив, который содержит ссылку на фактический массив и хеш доступа. Объект tie() для хеша доступа — это ссылка на ссылку на фактический массив, поэтому
-
Нет циклов ссылок.
-
Оба "объекта", благословлённые в класс
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+' => \#
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, описана в "ОШИБКИ"). Можно использовать этот новый интерфейс так:
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>.
СМОТРИТЕ ТАКЖЕ
Pragma overloading может использоваться для включения или выключения перегруженных операций в лексическом пространстве — см. перегрузка.
ДИАГНОСТИКА
Когда Perl запускается с переключателем -Do или его эквивалентом, перегрузка вызывает диагностические сообщения.
Используя команду m отладчика Perl (см. perldebug), можно определить, какие операции перегружены (и какой предок вызывает эту перегрузку). Скажем, если eq перегружена, то отладчик покажет метод (eq. Метод () соответствует ключу fallback (на самом деле наличие этого метода показывает, что в этом пакете включена перегрузка, и это то, что используется функцией Overloaded модуля overload).
Модуль может выдать следующие предупреждения:
- Нечётное число аргументов для overload::constant
-
(W) Вызов overload::constant содержал нечётное число аргументов. Аргументы должны передаваться парами.
- Тип '%s' не может быть перегружен
-
(W) Вы пытались перегрузить тип константы, о котором пакет перегрузки ничего не знает.
- '%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или подобное) сразу после 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.36.0/overload