Как добавить метод к существующему классу в Perl 6?
класс Int имеет метод is_prime
, поэтому я решил, что для хихиканья я хотел бы добавить некоторые другие методы для Int
для некоторых моих проектов по хобби, которые занимаются теорией чисел.
Я думал, что могу сделать что-то вроде этого:
class Int {
method is-even (Int:D $number ) returns Bool:D {
return False if $number % 2;
return True;
}
}
say 137.is-even;
Но это не работает:
===SORRY!===
P6opaque: must compose before allocating
Я не знаю, означает ли это, что я не могу этого сделать, или что я делаю это неправильно.
Я мог бы легко создать новый класс, который наследует от Int
, но это не то, что меня интересует:
class MyInt is Int {
method is-even () returns Bool:D {
return False if self % 2;
return True;
}
}
my $n = MyInt.new(138);
say $n.is-even;
Я не ищу обходные пути или альтернативные решения.
Ответы
Ответ 1
Там синтаксический сахар для этого - augment
:
use MONKEY-TYPING;
augment class Int {
method is-even() returns Bool:D {
return False if self % 2;
return True;
}
}
Дополнение класса считается опасным по двум причинам: во-первых, действие на расстоянии, а во-вторых, потому что (насколько мне известно) существует потенциал для deoptimization undefined поведения как он может оставить кеши различных методов в недопустимом состоянии.
Таким образом, требуется предоставить прагму MONKEY-TYPING
, прежде чем вы сможете ее использовать.
Отметим, что is-even
можно записать более компактно как self %% 2
.
Ответ 2
Да, это работает, и я думал, что пробовал раньше, и мне нравится лучше, чем то, что я представил в вопросе.
Int.^add_method( 'is-even', method () returns Bool:D {
return False if self % 2;
return True;
} );
say 137.is-even;
Я не уверен, что это должно сработать. add_method docs говорит, что мы должны делать это только до того, как будет создан тип. Если я вызываю Int.^methods
, is-even
не отображается. Тем не менее, он, кажется, можно вызывать и делать правильные вещи.
Лексические методы
Играя больше, я подумал, что могу сделать метод, не привязанный к какому-либо классу, и вызвать его на объекте:
my &is-even = method (Int:D :) returns Bool:D { self %% 2 };
Это построит a Callable
(посмотрите &is-even.WHAT
). В сигнатуре я ограничиваю его определенным значением Int (Int:D
), но не давайте ему имени. Я добавляю двоеточие после ограничения типа, чтобы отметить, что первый аргумент является invocant. Теперь я могу применить этот метод к любому объекту, который мне нравится:
say 137.&is-even;
say 138.&is-even;
say "foo".&is-even; # works, although inside is-even blow up
Это хорошо в другом измерении, поскольку он лексический, но не очень приятный в том, что объект неправильного типа может его назвать. Ошибка появляется после того, как она думает, что у нее есть способ отправки.
Ответ 3
Вы также можете просто вызвать sub как метод, включив &
sygil:
sub is-even(Int $n) { $n %% 2 }
say 4.&is-even; # True
Это просто синтаксический сахар, конечно, но я сделал это несколько раз, когда он выглядел более читабельным, чем просто вызов суб.
(Я знаю, что вы "не ищите обходные пути или альтернативные решения", но вы сами поделились с кем-то, поэтому я подумал, почему бы и нет?)
Ответ 4
Есть еще один интересный способ сделать это, если вам это нужно только для некоторых экземпляров класса. Вы можете украсить объект ролью:
my $decorated = $object but role { ... }