The Int class has a method is_prime
, so I figured, just for giggles, I'd like to add some other methods to Int
for some of my hobby projects that do number theory stuff.
I thought I could do something like this:
class Int {
method is-even (Int:D $number ) returns Bool:D {
return False if $number % 2;
return True;
}
}
say 137.is-even;
But that doesn't work:
===SORRY!===
P6opaque: must compose before allocating
I don't know if this means that I can't do that or that I'm doing it incorrectly.
I could easily make a new class that inherits from Int
, but that's not what I'm interested in:
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;
I'm not looking for workarounds or alternate solutions.
There's syntactic sugar for this - augment
:
use MONKEY-TYPING;
augment class Int {
method is-even() returns Bool:D {
return False if self % 2;
return True;
}
}
Augmenting a class is considered dangerous for two reasons: First, action at a distance, and second, because (as far as I'm aware), there's potential for undefined behaviour deoptimization as it might leave various method caches in an invalid state.
Thus, the requirement for providing the MONKEY-TYPING
pragma before you're allowed to use it.
As an aside, note that is-even
could be written more compactly as self %% 2
.
Huh, this works, which I thought I'd tried before and I like better than what I presented in the question.
Int.^add_method( 'is-even', method () returns Bool:D {
return False if self % 2;
return True;
} );
say 137.is-even;
I'm not sure this should work, though. The add_method docs says we should only do this before the type is composed. If I call Int.^methods
the is-even
doesn't show up. Still, it seems to be callable and doing the right thing.
Lexical methods
Playing more, I figured I could make a method that's not attached to any class and call that on an object:
my &is-even = method (Int:D :) returns Bool:D { self %% 2 };
This constructs a Callable
(look at &is-even.WHAT
). In the signature, I constrain it to be a definite Int value (Int:D
) but don't give it a name. I add the colon after type constraint to note that the first argument is the invocant. Now I can apply that method to any object I like:
say 137.&is-even;
say 138.&is-even;
say "foo".&is-even; # works, although inside is-even blow up
This is nice in a different dimension since it's lexical, but not nice in that an object of the wrong type might call it. The error shows up after it thinks it has the method to dispatch to.
You can also simply call a sub like a method by including the &
sygil:
sub is-even(Int $n) { $n %% 2 }
say 4.&is-even; # True
It's just syntactic sugar, of course, but I've done it a few times when it looked more readable than simply calling the sub.
(I know you're “not looking for workarounds or alternate solutions”, but you posted some yourself, so I thought, why not?)
There's another interesting way to do this if you need it only for some instances of a class. You can decorate an object with a role:
my $decorated = $object but role { ... }
来源:https://stackoverflow.com/questions/34504849/how-do-you-add-a-method-to-an-existing-class-in-perl-6