Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you add a method to an existing class in Perl 6?

Tags:

raku

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.

like image 712
brian d foy Avatar asked Dec 29 '15 05:12

brian d foy


2 Answers

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.

like image 68
Christoph Avatar answered Sep 21 '22 14:09

Christoph


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.

like image 37
brian d foy Avatar answered Sep 18 '22 14:09

brian d foy