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; ОПИСАНИЕ
Эта макрокоманда позволяет перегружать операторы Perl для класса. Чтобы перегрузить встроенные функции, см. "Переопределение встроенных функций" в perlsub вместо этого.
Основы
Объявление
Аргументы директивы use overload являются парами (ключ, значение). Полный список допустимых ключей см. в Перегружаемые операции ниже.
Реализации операторов (значения) могут быть подпрограммами, ссылками на подпрограммы или анонимными подпрограммами — то есть, чем угодно, что допустимо внутри &{ ... } вызова. Значения, заданные в виде строк, интерпретируются как имена методов. Таким образом
package Number;
use overload
"-" => "minus",
"*=" => \&muas,
'""' => sub { ...; }; объявляет, что вычитание должно выполняться методом minus() в классе Number (или в одном из его базовых классов), а функция Number::muas() должна использоваться для присваивания вида умножения, *=. Она также определяет анонимную подпрограмму для строкового представления: она вызывается всякий раз, когда объект, благословленный в пакет Number, используется в контексте строки (эта подпрограмма может, например, возвращать число в виде римской цифры).
Правила вызова и магическое автогенерирование
Следующая примерная реализация minus() (которая предполагает, что объекты Number — это просто благословлённые ссылки на скаляры) демонстрирует правила вызова:
package Number;
sub minus {
my ($self, $other, $swap) = @_;
my $result = $$self - $other; # *
$result = -$result if $swap;
ref $result ? $result : bless \$result;
}
# * may recurse once - see table below В все подпрограммы, указанные в директиве use overload, передаются три аргумента (есть исключения — см. ниже, особенно nomethod).
Первый из них — операнд, обеспечивающий реализацию перегруженного оператора — в данном случае, объект, метод minus() которого вызывается.
Второй аргумент — это другой операнд или undef в случае унарного оператора.
Третий аргумент устанавливается в TRUE, если (и только если) два операнда были поменены местами. Perl может сделать это, чтобы гарантировать, что первый аргумент ($self) — это объект, реализующий перегруженную операцию, в соответствии с общими правилами вызова объектов. Например, если $x и $y являются Number:
operation | generates a call to
============|======================
$x - $y | minus($x, $y, '')
$x - 7 | minus($x, 7, '')
7 - $x | minus($x, 7, 1) Perl также может использовать minus() для реализации других операторов, не указанных в директиве use overload, в соответствии с правилами Магического автогенерирования, описанными позже. Например, use overload выше не объявила подпрограмму для ни одного из операторов --, neg (ключ перегрузки для унарного минуса) или -=. Таким образом
operation | generates a call to
============|======================
-$x | minus($x, 0, 1)
$x-- | minus($x, 1, undef)
$x -= 3 | minus($x, 3, undef) Обратите внимание на undef: где автогенерирование приводит к использованию метода стандартного оператора, который не изменяет ни один из своих операндов, например -, для реализации оператора, изменяющего операнд («мутаторы»: здесь, -- и -=), Perl передаёт undef в качестве третьего аргумента. Это всё ещё оценивается как FALSE, согласуется с тем фактом, что операнды не были поменены, но даёт подпрограмме возможность изменить своё поведение в этих случаях.
Во всех вышеприведённых примерах minus() требуется только для возвращения результата вычитания: Perl позаботится об присваивании $x. Фактически, такие методы не должны изменять свои операнды, даже если undef передаётся в качестве третьего аргумента (см. Перегружаемые операции).
Это не относится к реализациям ++ и --: ожидается, что они изменят свой операнд. Соответствующая реализация -- может выглядеть так
use overload '--' => "decr",
# ...
sub decr { --${$_[0]}; } Если включена функция "bitwise" (см. feature), подпрограммам, обрабатывающим &, |, ^ и ~, передаётся пятый аргумент TRUE. Это указывает, что вызывающая сторона ожидает числового поведения. Четвёртый аргумент будет undef, поскольку эта позиция ($_[3]) зарезервирована для использования nomethod.
Математическая магия, мутаторы и конструкторы копирования
Термин «математическая магия» описывает перегруженную реализацию математических операторов. Математическая магия поднимает вопрос. Рассмотрим код:
$a = $b;
--$a; Если $a и $b являются скалярами, то после этих утверждений
$a == $b - 1 Однако объект — это ссылка на благословлённые данные, поэтому если $a и $b являются объектами, то присваивание $a = $b копирует только ссылку, оставляя $a и $b ссылающимися на те же данные объекта. Поэтому можно ожидать, что операция --$a уменьшит $b так же, как и $a.
Однако это не согласуется с тем, как мы ожидаем работы математических операторов.
Perl разрешает эту дилемму, прозрачно вызывая конструктор копирования перед вызовом метода, определённого для реализации мутатора (--, += и т. д.). В вышеприведённом примере, когда Perl достигает оператора декремента, он создаёт копию данных объекта в $a и присваивает $a ссылку на скопированные данные. Только тогда он вызывает decr(), который изменяет скопированные данные, оставляя $b неизменным. Таким образом, метафора объекта сохраняется по возможности, в то время как математические операции по-прежнему работают в соответствии с метафорой арифметики.
Примечание: предыдущий абзац описывает то, что происходит, когда Perl автоматически генерирует конструктор копирования для объекта, основанного на скаляре. Для других случаев см. Конструктор копирования.
Перегружаемые операции
Полный список ключей, которые могут быть указаны в директиве use overload, приведены, разделённые пробелами, в значениях хэша %overload::ops.
with_assign => '+ - * / % ** << >> x .',
assign => '+= -= *= /= %= **= <<= >>= x= .=',
num_comparison => '< <= > >= == !=',
'3way_comparison'=> '<=> cmp',
str_comparison => 'lt le gt ge eq ne',
binary => '& &= | |= ^ ^= &. &.= |. |.= ^. ^.=',
unary => 'neg ! ~ ~.',
mutators => '++ --',
func => 'atan2 cos sin exp abs log sqrt int',
conversion => 'bool "" 0+ qr',
iterators => '<>',
filetest => '-X',
dereferencing => '${} @{} %{} &{} *{}',
matching => '~~',
special => 'nomethod fallback =' Большинство перегружаемых операторов отображаются один к одному на эти ключи. Исключения, включая дополнительные перегружаемые операции, не очевидные из этого хэша, приведены в последующих примечаниях. Этот список может расширяться со временем.
Выдаётся предупреждение, если попытка зарегистрировать оператор, не найденный выше.
-
notОператор
notне является допустимым ключом дляuse overload. Однако, если оператор!перегружен, то то же самое реализация будет использована дляnot(поскольку два оператора различаются только приоритетом). -
negКлюч
negиспользуется для унарного минуса, чтобы отличить его от бинарного-. -
++,--Предполагая, что они должны вести себя аналогично
++и--в Perl, перегруженные реализации этих операторов обязаны изменять свои операнды.Различия между префиксной и постфиксной формами операторов инкремента и декремента не делается: они различаются только моментом, в котором Perl вызывает соответствующую подпрограмму при вычислении выражения.
-
Присваивания
+= -= *= /= %= **= <<= >>= x= .= &= |= ^= &.= |.= ^.=Простое присваивание не может быть перегружено (ключ
'='используется для "Копирующего Конструктора"). В Perl есть способ сделать так, чтобы присваивания объекту выполняли любые нужные действия, но это связано с использованием tie(), а не overload - см. "tie" в perlfunc и примеры "COOKBOOK" ниже.Подпрограмма для варианта присваивания оператора обязана только возвращать результат операции. Разрешается изменять значение своего операнда (это безопасно, потому что Perl сначала вызывает копирующий конструктор), но это необязательно, так как Perl в любом случае присваивает возвращаемое значение левому операнду.
Объект, перегружающий оператор присваивания, делает это только относительно присваиваний этому объекту. Другими словами, Perl никогда не вызывает соответствующие методы с третьим аргументом (аргументом "swap") установленным в TRUE. Например, операция
$a *= $bне может привести к вызову реализации
*=в$b, даже если$aявляется скаляром. (Однако это может вызвать вызов метода$bдля*). -
Не-мутаторы с мутаторным вариантом
+ - * / % ** << >> x . & | ^ &. |. ^.Как описано выше, Perl может вызывать методы для операторов, таких как
+и&, в процессе реализации отсутствующих операций, таких как++,+=, и&=. Хотя эти методы могут обнаруживать это использование, проверяя определённость третьего аргумента, они во всех случаях должны избегать изменения своих операндов. Это связано с тем, что Perl не вызывает копирующий конструктор перед вызовом этих методов. -
intТрадиционно, функция Perl
intокругляет до 0 (см. "int" в perlfunc), поэтому для типов, похожих на числа с плавающей точкой, следует придерживаться той же семантики. -
Преобразования строк, чисел, логических значений и регулярных выражений
"" 0+ boolЭти преобразования вызываются в соответствии с контекстом по мере необходимости. Например, подпрограмма для
'""'(преобразование в строку) может быть использована, когда перегруженный объект передаётся в качестве аргумента вprint, а для'bool'— когда он проверяется в условии оператора управления потоком (например,while) или условного оператора?:.Конечно, в контекстах, например,
$obj + 1, Perl вызовет реализацию$objв+, а не (в данном примере) преобразование$objв число с помощью метода numify'0+'(исключение из этого — когда метод не предоставлен для'+'и "fallback" установлен в TRUE).Подпрограммы для
'""','0+', и'bool'могут возвращать любое произвольное значение Perl. Если соответствующая операция для этого значения также перегружена, операция будет вызвана снова с этим значением.В качестве специального случая, если перегрузка возвращает сам объект, он будет использован напрямую. Перегрузка преобразования, возвращающая объект, вероятно, является ошибкой, потому что вы, скорее всего, получите что-то, что выглядит как
YourPackage=HASH(0x8172b34).qrПодпрограмма для
'qr'используется всякий раз, когда объект интерполируется в или используется как регулярное выражение, включая случаи, когда он появляется в правой части оператора=~или!~.qrдолжно возвращать скомпилированное регулярное выражение или ссылку на скомпилированное регулярное выражение (например, то, что возвращаетqr//), и любая дальнейшая перегрузка возвращаемого значения будет проигнорирована. -
Итерация
Если
<>перегружен, то та же реализация используется для синтаксиса чтения из файлового дескриптора<$var>и синтаксиса глоббинга<${var}>. -
Тесты файлов
Ключ
'-X'используется для указания подпрограммы для обработки всех операторов тестирования файлов (-f,-x, и так далее: см. "-X" в perlfunc для полного списка); перегрузить любой оператор тестирования файлов индивидуально невозможно. Для их различения буква, следующая за '-', передаётся в качестве второго аргумента (то есть в тот же слот, что и для бинарных операторов, используемый для передачи второго операнда).Вызов перегруженного оператора тестирования файлов не влияет на значение stat, связанное со специальным файловым дескриптором
_. Оно по-прежнему ссылается на результат последнегоstat,lstatили неперегруженного тестирования файла.Эта перегрузка была введена в Perl 5.12.
-
Сопоставление
Ключ
"~~"позволяет переопределить логику интеллектуального сопоставления, используемую оператором~~и конструкцией switch (given/when). См. "Switch Statements" в perlsyn и feature.Необычно, перегруженная реализация оператора интеллектуального сопоставления не получает полного контроля над поведением интеллектуального сопоставления. В частности, в следующем коде:
package Foo; use overload '~~' => 'match'; my $obj = Foo->new(); $obj ~~ [ 1,2,3 ];интеллектуальное сопоставление не вызывает вызов метода таким образом:
$obj->match([1,2,3],0);вместо этого, правило распределения интеллектуального сопоставления имеет приоритет, поэтому $obj интеллектуально сопоставляется с каждым элементом массива по очереди до тех пор, пока не будет найдено соответствие, поэтому вы можете увидеть от одного до трёх таких вызовов:
$obj->match(1,0); $obj->match(2,0); $obj->match(3,0);См. таблицу сопоставлений в "Оператор интеллектуального сопоставления" в perlop для получения подробностей о том, когда вызывается перегрузка.
-
Деферирование
${} @{} %{} &{} *{}Если эти операторы не явным образом перегружены, они работают стандартным образом, возвращая базовый скаляр, массив или что угодно, хранящий данные объекта (или соответствующее сообщение об ошибке, если оператор деферирования не соответствует ему). Определение универсальной функции
'nomethod'(см. ниже) не изменяет это, так как универсальная функция не будет вызвана для реализации отсутствующего оператора деферирования.Если оператор деферирования перегружен, он должен возвращать ссылку соответствующего типа (например, подпрограмма для ключа
'${}'должна возвращать ссылку на скаляр, а не скаляр) или другой объект, который перегружает оператор: то есть подпрограмма только определяет, что деферируется, а фактическое деферирование остаётся на стороне Perl. В качестве специального случая, если подпрограмма возвращает сам объект, она больше не будет вызываться, чтобы избежать бесконечной рекурсии. -
Специальное
nomethod fallback =
Автогенерация магических функций
Если метод для операции не найден, Perl пытается сгенерировать замещающую реализацию из определённых операций.
Примечание: поведение, описанное в этом разделе, может быть отключено путём установки fallback в FALSE (см. "fallback").
В следующих таблицах числа указывают приоритет. Например, таблица ниже указывает, что, если реализация для '!' не определена, Perl реализует её, используя 'bool' (то есть, инвертируя значение, возвращаемое методом для 'bool'); если преобразование в булево значение также не реализовано, Perl будет использовать '0+' или, в случае неудачи, '""'.
operator | can be autogenerated from
|
| 0+ "" bool . x
=========|==========================
0+ | 1 2
"" | 1 2
bool | 1 2
int | 1 2 3
! | 2 3 1
qr | 2 1 3
. | 2 1 3
x | 2 1 3
.= | 3 2 4 1
x= | 3 2 4 1
<> | 2 1 3
-X | 2 1 3 Примечание: операторы итератора ('<>') и тестирования файлов ('-X') работают как обычно: если операнд не является освященной ссылкой на glob или IO, он преобразуется в строку (с использованием метода для '""', '0+', или 'bool'), чтобы интерпретироваться как glob или имя файла.
operator | can be autogenerated from
|
| < <=> neg -= -
=========|==========================
neg | 1
-= | 1
-- | 1 2
abs | a1 a2 b1 b2 [*]
< | 1
<= | 1
> | 1
>= | 1
== | 1
!= | 1
* one from [a1, a2] and one from [b1, b2] Так же, как числовые сравнения могут быть сгенерированы автоматически из метода для '<=>', строковые сравнения могут быть сгенерированы автоматически из метода для 'cmp':
operators | can be autogenerated from
====================|===========================
lt gt le ge eq ne | cmp Аналогично, автогенерация для ключей '+=' и '++' аналогична '-=' и '--' выше:
operator | can be autogenerated from
|
| += +
=========|==========================
+= | 1
++ | 1 2 И другие варианты присваивания аналогичны '+=' и '-=' (и аналогичны '.=' и 'x=' выше):
operator || *= /= %= **= <<= >>= &= ^= |= &.= ^.= |.=
-------------------||-------------------------------------------
autogenerated from || * / % ** << >> & ^ | &. ^. |. Обратите также внимание, что копирующий конструктор (ключ '=') может быть сгенерирован автоматически, но только для объектов, основанных на скалярах. См. "Копирующий Конструктор".
Минимальный набор перегруженных операций
Поскольку некоторые операции могут быть автоматически сгенерированы из других, существует минимальный набор операций, которые необходимо перегрузить, чтобы получить полный набор перегруженных операций. Конечно, сгенерированные автоматически операции могут не делать ровно то, что ожидает пользователь. Минимальный набор:
+ - * / % ** << >> x
<=> cmp
& | ^ ~ &. |. ^. ~.
atan2 cos sin exp log sqrt int
"" 0+ bool
~~ Из преобразований требуется только одно из строк, логических значений или чисел, потому что каждое может быть сгенерировано из любого из двух других.
Специальные ключи для использования перегрузки
nomethod
Ключ 'nomethod' используется для указания универсальной функции, которая должна вызываться для любого оператора, который не перегружен индивидуально. Указанная функция будет передана четырем параметрам. Первые три аргумента совпадают с теми, что были бы переданы соответствующему методу, если бы он был определён. Четвёртый аргумент — ключ use overload для этого отсутствующего метода. Если включена функция "bitwise" (см. feature), пятый аргумент TRUE передаётся подпрограммам, обрабатывающим &, |, ^ и ~, чтобы указать, что вызывающий ожидает числового поведения.
Например, если $a является объектом, благословлённым в пакет, объявляющий
use overload 'nomethod' => 'catch_all', # ... то операция
3 + $a может (если метод не объявлен специально для ключа '+') привести к вызову
catch_all($a, 3, 1, '+') См. "Как Perl выбирает реализацию оператора".
fallback
Значение, присвоенное ключу 'fallback', указывает Perl, как усердно он должен пытаться найти альтернативный способ реализации отсутствующего оператора.
-
определён, но FALSE
use overload "fallback" => 0, # ... ;Это отключает "Магическое автосоздание".
-
undefВ случае по умолчанию, когда значению
fallbackявно не присвоено, магическое автосоздание включено. -
TRUE
То же, что и для
undef, но если отсутствующий оператор не может быть автоматически сгенерирован, вместо вывода сообщения об ошибке Perl может вернуться к тому, что он бы сделал для этого оператора, если бы не было директивыuse overload.Примечание: в большинстве случаев, особенно для "Копирующего конструктора", это маловероятно будет подходящим поведением.
См. "Как Perl выбирает реализацию оператора".
Копирующий конструктор
Как упоминалось выше, эта операция вызывается при применении мутатора к ссылке, которая разделяет свой объект с другой ссылкой. Например, если $b является математически магическим, а '++' перегружен с 'incr', и '=' перегружен с 'clone', тогда код
$a = $b;
# ... (other code which does not modify $a or $b) ...
++$b; будет выполнен аналогично
$a = $b;
# ...
$b = $b->clone(undef, "");
$b->incr(undef, ""); Примечание:
-
Подпрограмма для
'='не перегружает оператор присваивания Perl: она используется только для того, чтобы позволить мутаторам работать, как описано здесь. (См. "Присваивания" выше.) -
Как и для других операций, подпрограмма, реализующая '=', получает три аргумента, хотя последние два всегда
undefи''. -
Копирующий конструктор вызывается только перед вызовом функции, объявленной для реализации мутатора, например, если
++$b;в коде выше выполняется через метод, объявленный для ключа'++'(или 'nomethod', переданный'++'в качестве четвёртого аргумента) или, при автосоздании,'+='. Он не вызывается, если операция инкремента выполняется вызовом метода для'+'так как в эквивалентном коде$a = $b; $b = $b + 1;данные, на которые ссылается
$a, не изменяются присваиванием$bссылки на новые данные объекта. -
Копирующий конструктор не вызывается, если Perl определяет, что он не нужен, потому что нет других ссылок на данные, которые изменяются.
-
Если
'fallback'не определено или TRUE, то копирующий конструктор может быть сгенерирован автоматически, но только для объектов, основанных на скалярах. В других случаях его нужно определить явно. Если данные объекта хранятся, например, в массиве скаляров, то следующим может быть подходящим:use overload '=' => sub { bless [ @{$_[0]} ] }, # ... -
Если
'fallback'равно TRUE и копирующий конструктор не определён, то для объектов, не основанных на скалярах, Perl может тихо перейти на простое присваивание — то есть, присваивание ссылки на объект. По сути, это отключает механизм копирующего конструктора, так как не создаётся новая копия данных объекта. Это почти наверняка не то, что нужно. (Однако это согласуется: например, резервным вариантом Perl для оператора++является инкремент самой ссылки.)
Как Perl выбирает реализацию оператора
Какой из них проверяется первым, nomethod или fallback? Если два операнда оператора разных типов и оба перегружают оператор, какая реализация используется? Ниже приведены правила приоритета:
-
Если первый операнд объявил подпрограмму для перегрузки оператора, то используйте эту реализацию.
-
В противном случае, если для первого операнда fallback TRUE или undefined, проверьте, разрешают ли правила автосоздания использовать другой из его операторов вместо него.
-
Если оператор не присваивания (
+=,-=, и т.д.), повторите шаг (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так же, как при отсутствии перегрузки строкового представления. Если вы используете это для получения адреса ссылки (полезно для проверки, указывают ли две ссылки на одно и то же), то вам может быть лучше использоватьScalar::Util::refaddr(), что быстрее. - overload::Overloaded(арг)
-
Возвращает true, если
argподвержено перегрузке некоторых операций. - overload::Method(объект, опер)
-
Возвращает
undefили ссылку на метод, реализующийop.
Перегрузка констант
В некоторых приложениях Perl-парсер слишком сильно изменяет константы. Можно подключиться к этому процессу через функции overload::constant() и overload::remove_constant().
Эти функции принимают хеш в качестве аргумента. Признанные ключи этого хеша:
- integer
-
для перегрузки целочисленных констант,
- float
-
для перегрузки констант с плавающей запятой,
- binary
-
для перегрузки восьмеричных и шестнадцатеричных констант,
- q
-
для перегрузки
q-строк, постоянных частейqq- иqx-строк и here-документов, - qr
-
для перегрузки постоянных частей регулярных выражений.
Соответствующие значения являются ссылками на функции, которые принимают три аргумента: первый — это начальная строковая форма константы, второй — как Perl интерпретирует эту константу, третий — как используется константа. Обратите внимание, что начальная строковая форма не содержит разделителей строк и содержит обратные косые черты в комбинациях с разделителями обратной косой черты, удаленными (следовательно, значение разделителя не имеет отношения к обработке этой строки). Возвращаемое значение этой функции — это то, как эта константа будет интерпретирована Perl. Третий аргумент не определен, если не для перегруженных констант q и qr, он равен q в контексте одинарных кавычек (получен из строк, регулярных выражений и HERE-документов с одинарными кавычками), он равен tr для аргументов операторов tr/y, он равен s для правой части оператора s, и он равен qq в противном случае.
Поскольку выражение "ab$cd,," — это просто сокращение для 'ab' . $cd . ',,', ожидается, что перегруженные константные строки оснащены разумным перегруженным оператором конкатенации, в противном случае будут получены абсурдные результаты. Аналогично, отрицательные числа рассматриваются как отрицания положительных констант.
Обратите внимание, что, вероятно, бессмысленно вызывать функции overload::constant() и overload::remove_constant() из любого места, кроме методов import() и unimport(). Из этих методов они могут вызываться как
sub import {
shift;
return unless @_;
die "unknown import: @_" unless @_ == 1 and $_[0] eq ':constant';
overload::constant integer => sub {Math::BigInt->new(shift)};
} РЕАЛИЗАЦИЯ
Следующее может быть изменено RSN.
Таблица методов для всех операций кэшируется в магии для хэша таблицы символов для пакета. Кэш аннулируется во время обработки use overload, no overload, новых определений функций и изменений в @ISA.
(У каждого элемента SVish есть очередь магии, а магия — это запись в этой очереди. Таким образом, одна переменная может участвовать в нескольких формах магии одновременно. Например, переменные среды обычно имеют две формы одновременно: их магию %ENV и их магию taint. Однако магия, реализующая перегрузку, применяется к стекам, которые редко используются напрямую, поэтому не должна замедляться.)
Если пакет использует перегрузку, он несет специальный флаг. Этот флаг также устанавливается при определении новых функций или изменении @ISA. После этого первое выполняемое действие, поддерживающее перегрузку, будет иметь небольшую штрафную скорость, пока таблицы перегрузки не обновится. Если перегрузка отсутствует, флаг сбрасывается. Таким образом, единственный штраф за скорость после этого — проверка этого флага.
Ожидается, что аргументы методов, которые явно не должны изменяться, являются постоянными (но это не навязывается).
СБОРНИК РЕЦЕПТОВ
Пожалуйста, добавьте примеры к следующему!
Двуликие скаляры
Поместите это в two_face.pm в каталоге вашей библиотеки Perl:
package two_face; # Scalars with separate string and
# numeric values.
sub new { my $p = shift; bless [@_], $p }
use overload '""' => \&str, '0+' => \&num, fallback => 1;
sub num {shift->[1]}
sub str {shift->[0]} Используйте его следующим образом:
require two_face;
my $seven = two_face->new("vii", 7);
printf "seven=$seven, seven=%d, eight=%d\n", $seven, $seven+1;
print "seven contains 'i'\n" if $seven =~ /i/; (Вторая строка создает скаляр, который имеет и строковое, и числовое значение.) Это печатает:
seven=vii, seven=7, eight=8
seven contains 'i' Двуликие ссылки
Предположим, вы хотите создать объект, который доступен как ссылка на массив, так и ссылка на хэш.
package two_refs;
use overload '%{}' => \&gethash, '@{}' => sub { $ {shift()} };
sub new {
my $p = shift;
bless \ [@_], $p;
}
sub gethash {
my %h;
my $self = shift;
tie %h, ref $self, $self;
\%h;
}
sub TIEHASH { my $p = shift; bless \ shift, $p }
my %fields;
my $i = 0;
$fields{$_} = $i++ foreach qw{zero one two three};
sub STORE {
my $self = ${shift()};
my $key = $fields{shift()};
defined $key or die "Out of band access";
$$self->[$key] = shift;
}
sub FETCH {
my $self = ${shift()};
my $key = $fields{shift()};
defined $key or die "Out of band access";
$$self->[$key];
} Теперь к объекту можно получить доступ, используя как синтаксис массива, так и хэша:
my $bar = two_refs->new(3,4,5,6);
$bar->[2] = 11;
$bar->{two} == 11 or die 'bad hash fetch'; Обратите внимание на несколько важных особенностей этого примера. Во-первых, фактический тип $bar — это ссылка на скаляр, и мы не перегружаем разыменование скаляра. Таким образом, мы можем получить фактическое неопределенное содержимое $bar, просто используя $$bar (что мы делаем в функциях, которые перегружают разыменование). Аналогично, объект, возвращаемый методом TIEHASH(), — это ссылка на скаляр.
Во-вторых, каждый раз, когда используется синтаксис хэша, создается новый связанный хэш. Это позволяет нам не беспокоиться о возможности циклической ссылки, которая приведет к утечке памяти.
Обе эти проблемы можно исправить. Скажем, если мы хотим перегрузить разыменование хэша по ссылке на объект, который реализован как хэш, единственная проблема, которую нужно обойти, заключается в том, как получить доступ к этому фактическому хэшу (в отличие от виртуального хэша, демонстрируемого перегруженным оператором разыменования). Вот один из возможных методов извлечения:
sub access_hash {
my ($self, $key) = (shift, shift);
my $class = ref $self;
bless $self, 'overload::dummy'; # Disable overloading of %{}
my $out = $self->{$key};
bless $self, $class; # Restore overloading
$out;
} Чтобы удалить создание связанного хэша при каждом доступе, можно использовать дополнительный уровень косвенности, который позволяет создать нециклическую структуру ссылок:
package two_refs1;
use overload '%{}' => sub { ${shift()}->[1] },
'@{}' => sub { ${shift()}->[0] };
sub new {
my $p = shift;
my $a = [@_];
my %h;
tie %h, $p, $a;
bless \ [$a, \%h], $p;
}
sub gethash {
my %h;
my $self = shift;
tie %h, ref $self, $self;
\%h;
}
sub TIEHASH { my $p = shift; bless \ shift, $p }
my %fields;
my $i = 0;
$fields{$_} = $i++ foreach qw{zero one two three};
sub STORE {
my $a = ${shift()};
my $key = $fields{shift()};
defined $key or die "Out of band access";
$a->[$key] = shift;
}
sub FETCH {
my $a = ${shift()};
my $key = $fields{shift()};
defined $key or die "Out of band access";
$a->[$key];
} Теперь, если $baz перегружен таким образом, то $baz — это ссылка на ссылку на промежуточный массив, который хранит ссылку на фактический массив и хэш доступа. Объект связывания (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, что приведет к бесконечному циклу.
Добавьте метод pretty-печати в модуль symbolic.pm:
sub pretty {
my ($meth, $a, $b) = @{+shift};
$a = 'u' unless defined $a;
$b = 'u' unless defined $b;
$a = $a->pretty if ref $a;
$b = $b->pretty if ref $b;
"[$meth $a $b]";
} Теперь можно завершить скрипт следующим образом:
print "side = ", $side->pretty, "\n"; Метод pretty выполняет преобразование объекта в строку, поэтому естественно перегрузить оператор "" с помощью этого метода. Однако внутри такого метода нет необходимости красиво отображать компоненты $a и $b объекта. В вышеприведенной подпрограмме "[$meth $a $b]" является конкатенацией некоторых строк и компонентов $a и $b. Если эти компоненты используют перегрузку, оператор конкатенации будет искать перегруженный оператор .; если он отсутствует, он будет искать перегруженный оператор "". Таким образом, достаточно использовать
use overload nomethod => \&wrap, '""' => \&str;
sub str {
my ($meth, $a, $b) = @{+shift};
$a = 'u' unless defined $a;
$b = 'u' unless defined $b;
"[$meth $a $b]";
} Теперь можно изменить последнюю строку скрипта на
print "side = $side\n"; что выводит
side = [/ [- [sqrt [+ 1 [** [n 1 u] 2]] u] 1] [n 1 u]] и можно проверить значение в отладчике всеми возможными методами.
Что-то все еще не так: рассмотрим переменную цикла $cnt скрипта. Это было число, а не объект. Мы не можем сделать это значение типа symbolic, так как тогда цикл не завершится.
Действительно, чтобы завершить цикл, $cnt должен стать ложным. Однако оператор bool для проверки ложности перегружен (на этот раз через перегруженное "") и возвращает длинную строку, поэтому любой объект типа symbolic является истинным. Чтобы решить эту проблему, нам нужен способ сравнить объект с 0. Фактически, проще написать функцию числового преобразования.
Вот текст symbolic.pm с добавленной функцией такого преобразования (и немного измененной функцией str()):
package symbolic; # Primitive symbolic calculator
use overload
nomethod => \&wrap, '""' => \&str, '0+' => \#
sub new { shift; bless ['n', @_] }
sub wrap {
my ($obj, $other, $inv, $meth) = @_;
($obj, $other) = ($other, $obj) if $inv;
bless [$meth, $obj, $other];
}
sub str {
my ($meth, $a, $b) = @{+shift};
$a = 'u' unless defined $a;
if (defined $b) {
"[$meth $a $b]";
} else {
"[$meth $a]";
}
}
my %subr = ( n => sub {$_[0]},
sqrt => sub {sqrt $_[0]},
'-' => sub {shift() - shift()},
'+' => sub {shift() + shift()},
'/' => sub {shift() / shift()},
'*' => sub {shift() * shift()},
'**' => sub {shift() ** shift()},
);
sub num {
my ($meth, $a, $b) = @{+shift};
my $subr = $subr{$meth}
or die "Do not know how to ($meth) in symbolic";
$a = $a->num if ref $a eq __PACKAGE__;
$b = $b->num if ref $b eq __PACKAGE__;
$subr->($a,$b);
} Вся работа по числовому преобразованию выполняется в %subr и num(). Конечно, %subr неполная, она содержит только операторы, используемые в примере ниже. Вот дополнительный вопрос: зачем нам нужна явная рекурсия в num()? (Ответ в конце этого раздела.)
Используйте этот модуль следующим образом:
require symbolic;
my $iter = symbolic->new(2); # 16-gon
my $side = symbolic->new(1);
my $cnt = $iter;
while ($cnt) {
$cnt = $cnt - 1; # Mutator '--' not implemented
$side = (sqrt(1 + $side**2) - 1)/$side;
}
printf "%s=%f\n", $side, $side;
printf "pi=%f\n", $side*(2**($iter+2)); Он печатает (без лишних переносов строк)
[/ [- [sqrt [+ 1 [** [/ [- [sqrt [+ 1 [** [n 1] 2]]] 1]
[n 1]] 2]]] 1]
[/ [- [sqrt [+ 1 [** [n 1] 2]]] 1] [n 1]]]=0.198912
pi=3.182598 Вышеупомянутый модуль очень примитивный. Он не реализует методы-мутаторы (++, -= и т. д.), не выполняет глубокое копирование (без мутаторов не требуется!), и реализует только те арифметические операции, которые используются в примере.
Реализовать большинство арифметических операций легко; нужно просто использовать таблицы операций и изменить код, который заполняет %subr, на
my %subr = ( 'n' => sub {$_[0]} );
foreach my $op (split " ", $overload::ops{with_assign}) {
$subr{$op} = $subr{"$op="} = eval "sub {shift() $op shift()}";
}
my @bins = qw(binary 3way_comparison num_comparison str_comparison);
foreach my $op (split " ", "@overload::ops{ @bins }") {
$subr{$op} = eval "sub {shift() $op shift()}";
}
foreach my $op (split " ", "@overload::ops{qw(unary func)}") {
print "defining '$op'\n";
$subr{$op} = eval "sub {$op shift()}";
} Поскольку подпрограммы, реализующие операторы присваивания, не обязаны изменять свои операнды (см. "Перегружаемые операции" выше), нам ничего не нужно, чтобы сделать += и аналогичные функции, кроме добавления этих операторов в %subr и определения конструктора копирования (необходимого, поскольку у Perl нет способа узнать, что реализация '+=' не изменяет аргумент — см. "Конструктор копирования").
Для реализации конструктора копирования добавьте '=' => \&cpy в строку use overload и код (этот код предполагает, что мутаторы изменяют вещи только на один уровень вглубь, поэтому рекурсивное копирование не требуется):
sub cpy {
my $self = shift;
bless [@$self], ref $self;
} Чтобы сделать ++ и -- работоспособными, нам нужно реализовать фактические мутаторы, либо напрямую, либо в nomethod. Мы продолжим делать вещи внутри nomethod, поэтому добавим
if ($meth eq '++' or $meth eq '--') {
@$obj = ($meth, (bless [@$obj]), 1); # Avoid circular reference
return $obj;
} после первой строки wrap(). Это не самое эффективное решение, можно рассмотреть
sub inc { $_[0] = bless ['++', shift, 1]; } вместо этого.
В заключение, обратите внимание, что можно заполнить %subr следующим образом:
my %subr = ( 'n' => sub {$_[0]} );
foreach my $op (split " ", $overload::ops{with_assign}) {
$subr{$op} = $subr{"$op="} = eval "sub {shift() $op shift()}";
}
my @bins = qw(binary 3way_comparison num_comparison str_comparison);
foreach my $op (split " ", "@overload::ops{ @bins }") {
$subr{$op} = eval "sub {shift() $op shift()}";
}
foreach my $op (split " ", "@overload::ops{qw(unary func)}") {
$subr{$op} = eval "sub {$op shift()}";
}
$subr{'++'} = $subr{'+'};
$subr{'--'} = $subr{'-'}; Это завершает реализацию примитивного символьного калькулятора в 50 строках кода Perl. Поскольку числовые значения подвыражений не кешируются, калькулятор очень медленный.
Вот ответ на задачу: в случае str() нам не нужна явная рекурсия, так как перегруженный оператор . вернется к существующему перегруженному оператору "". Перегруженные арифметические операторы не возвращаются к числовому преобразованию, если fallback не запрошен явно. Таким образом, без явной рекурсии num() будет преобразовывать ['+', $a, $b] в $a + $b, что просто перестроит аргумент функции num().
Если вас интересует, почему значения по умолчанию для преобразования отличаются для str() и num(), обратите внимание, насколько легко было написать символьный калькулятор. Эта простота обусловлена соответствующим выбором значений по умолчанию. Еще одна заметка: из-за явной рекурсии num() более хрупкий, чем sym(): нам нужно явно проверить тип $a и $b. Если компоненты $a и $b имеют какой-либо связанный тип, это может привести к проблемам.
Действительно Символьный калькулятор
Можно задаться вопросом, почему мы называем вышеприведенный калькулятор символическим. Причина в том, что фактическое вычисление значения выражения откладывается до момента, когда значение используется.
Чтобы увидеть это в действии, добавьте метод
sub STORE {
my $obj = shift;
$#$obj = 1;
@$obj->[0,1] = ('=', shift);
} в пакет symbolic. После этого изменения можно сделать
my $a = symbolic->new(3);
my $b = symbolic->new(4);
my $c = sqrt($a**2 + $b**2); и числовое значение $c становится 5. Однако после вызова
$a->STORE(12); $b->STORE(5); числовое значение $c становится 13. Теперь нет сомнений, что модуль symbolic действительно предоставляет символический калькулятор.
Чтобы скрыть неровности под капотом, предоставьте интерфейс tie() для пакета symbolic. Добавьте методы
sub TIESCALAR { my $pack = shift; $pack->new(@_) }
sub FETCH { shift }
sub nop { } # Around a bug (ошибка, исправленная в Perl 5.14, описана в "BUGS"). Этот новый интерфейс можно использовать так:
tie $a, 'symbolic', 3;
tie $b, 'symbolic', 4;
$a->nop; $b->nop; # Around a bug
my $c = sqrt($a**2 + $b**2); Теперь числовое значение $c равно 5. После $a = 12; $b = 5 числовое значение $c становится 13. Для изоляции пользователя модуля добавьте метод
sub vars { my $p = shift; tie($_, $p), $_->nop foreach @_; } Теперь
my ($a, $b);
symbolic->vars($a, $b);
my $c = sqrt($a**2 + $b**2);
$a = 3; $b = 4;
printf "c5 %s=%f\n", $c, $c;
$a = 12; $b = 5;
printf "c13 %s=%f\n", $c, $c; показывает, что числовое значение $c следует изменениям значений $a и $b.
АВТОР
Илья Захаревич <ilya@math.mps.ohio-state.edu>.
СМОТРИТЕ ТАКЖЕ
Предикат overloading может использоваться для включения или отключения перегруженных операций в лексическом пространстве — см. перегрузку.
ДИАГНОСТИКА
Когда Perl запускается с переключателем -Do или его эквивалентом, перегрузка вызывает диагностические сообщения.
Используя команду m отладчика Perl (см. perldebug), можно определить, какие операции перегружены (и какой предок вызывает эту перегрузку). Скажем, если eq перегружен, то метод (eq отображается отладчиком. Метод () соответствует ключу fallback (на самом деле наличие этого метода показывает, что в этом пакете включена перегрузка, и именно она используется функцией Overloaded модуля overload).
Модуль может выдать следующие предупреждения:
- Нечетное число аргументов для overload::constant
-
(W) Вызов overload::constant содержал нечетное число аргументов. Аргументы должны быть парами.
- '%s' — не перегружаемый тип
-
(W) Вы попытались перегрузить тип константы, о котором пакет overload не знает.
- '%s' не является ссылкой на код
-
(W) Второй (четвертый, шестой и т. д.) аргумент overload::constant должен быть ссылкой на код. Либо анонимная подпрограмма, либо ссылка на подпрограмму.
- Аргумент перегрузки '%s' недействителен
-
(W)
use overloadбыл передан аргумент, который он не распознал. Возможно, вы ошиблись в написании оператора?
ОШИБКИ И ПОДВОХИ
-
Подвох, когда откат TRUE и Perl прибегает к встроенной реализации оператора, заключается в том, что некоторые операторы имеют более одного семантического значения, например
|:use overload '0+' => sub { $_[0]->{n}; }, fallback => 1; my $x = bless { n => 4 }, "main"; my $y = bless { n => 8 }, "main"; print $x | $y, "\n";Можно ожидать, что это выведет "12". На самом деле выводится "<": результат ASCII-представления "|" как битового оператора строк — то есть результат обработки операндов как строк "4" и "8", а не чисел. Тот факт, что numify (
0+) реализован, но stringify ("") нет, не имеет значения, так как последний просто генерируется автоматически из первого.Единственный способ изменить это — предоставить свою подпрограмму для
'|'. -
Магическая автоматическая генерация увеличивает вероятность непреднамеренного создания самоссылочных структур. В настоящее время Perl не будет освобождать самоссылочные структуры, пока циклы явно не будут разорваны. Например,
use overload '+' => 'add'; sub add { bless [ \$_[0], \$_[1] ] };представляет опасность, так как
$obj += $y;фактически станет
$obj = add($obj, $y, undef);с тем же результатом, что и
$obj = [\$obj, \$foo];Даже если в сценарии нет явных вариантов присваивания операторов, они могут быть сгенерированы оптимизатором. Например,
"obj = $obj\n"может быть оптимизировано до
my $tmp = 'obj = ' . $obj; $tmp .= "\n"; -
Таблица символов заполнена именами, похожими на помехи.
-
Эта ошибка была исправлена в Perl 5.18, но все еще может вызвать проблемы, если вы используете более старые версии:
Для целей наследования каждый перегруженный пакет ведет себя так, как будто
fallbackприсутствует (возможно, не определен). Это может привести к интересным эффектам, если какой-либо пакет не перегружен, но наследуется от двух перегруженных пакетов. -
До Perl 5.14 связь между перегрузкой и связыванием tie() была нарушена. Перегрузка вызывалась или нет, исходя из предыдущего класса связанной переменной.
Это произошло потому, что проверка наличия перегрузки производилась слишком рано, до попытки любого доступа к связанной переменной. Если класс значения, извлеченного из связанной переменной FETCH(), не меняется, простым способом решения для кода, который должен работать в более старых версиях Perl, является доступ к значению (через
() = $fooили подобное) сразу после связывания tie(), чтобы после этого вызова предыдущий класс совпадал с текущим. -
Необработанные слова не охватываются перегруженными строковыми константами.
-
Оператор диапазона
..не может быть перегружен.
© 1993–2020 Larry Wall and others
Licensed under the GNU General Public License version 1 or later, or the Artistic License.
The Perl logo is a trademark of the Perl Foundation.
https://perldoc.perl.org/5.32.0/overload