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; ОПИСАНИЕ
Этот пragma позволяет перегрузить операторы 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 (ключ overload для унарного минуса) или -=. Таким образом
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 никогда не вызывает соответствующие методы с третьим аргументом (аргументом "обмена") установленным в 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 =
Автогенерация магических методов
Если метод для операции не найден, 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') работают как обычно: если операнд не является благословенным глобом или ссылкой на объект IO, он преобразуется в строку (с использованием метода для '""', '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
~~ Из преобразований достаточно одного из строкового, булева или числового, потому что каждый может быть сгенерирован из любого другого.
Специальные ключи для использования в перегрузке
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? Если два операнда оператора имеют разные типы и оба перегружают оператор, какая реализация используется? Ниже приведены правила приоритета:
-
Если первый операнд объявил подпрограмму для перегрузки оператора, то используйте эту реализацию.
-
В противном случае, если обратное значение равно 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 в контексте одинарных кавычек (происходит из строк, регулярных выражений и документов 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 — ссылка на ссылку на промежуточный массив, который содержит ссылку на фактический массив и доступный хеш. Объект 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 Option установлен, а не через 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>.
См. также
Предикат overloading может использоваться для включения или отключения перегруженных операций в лексическом пространстве — см. 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) и обращении 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(). Если класс значения, извлеченного из привязанной переменной (через
() = $fooили аналогично), не изменяется, простым способом решения проблемы для кода, который должен работать в более старых версиях Perl, является доступ к значению сразу после tie(), чтобы после этого вызова предыдущий класс совпадал с текущим. -
Необъявленные слова не покрываются перегруженными строковыми константами.
-
Оператор диапазона
..не может быть перегружен.
© 1993–2023 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.38.0/overload