Как добавить метод к существующему классу в 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 { ... }