How to create a class that doesn't inherit from any other class?

277 Views Asked by At

If you create a class:

class Foo { }

the class will inherit all of its methods from Any, and then Mu.

I want to create a class that doesn't inherit from any other class: it should contain a single FALLBACK method that should catch all method calls to instances of that object.

I've looked through the MetaModel code, but there does not seem to be a simple way to achieve this goal. All suggestions are welcome!

UPDATE: I decided to go the intercept any method call way as described by Jonathan Worthington. This resulted in two new Perl 6 modules on CPAN: InterceptAllMethods and Object::Trampoline.

2

There are 2 best solutions below

0
On BEST ANSWER

This is possible, although you're likely to run into practical problems that will require further effort. Calling construction logic is a good example already pointed out in a comment. Further to that, everything is expected to successfully type check against Mu; such checks are elided in most places as an optimization, but not others, and so you can expect to run into assorted type check failures.

With that aside, here's how to do it. First, create a module which exports a new meta-type for class.

class RootHOW is Metamodel::ClassHOW {
    method has_default_parent_type(|) { False }
}
package EXPORTHOW {
    constant class = RootHOW;
}

The metamodel has to somehow be used to set up the Mu type in the first place, and so here we (ab)use a mechanism that ordinarily means "no, there's no default parent type yet because we didn't bootstrap our object model that far". Stick this into a module, say called Parentless, and then it's possible to do this:

use Parentless;
class NotAMu {
    method FALLBACK($name, |c) {
        say "called $name with {c.perl}"
    }
}
NotAMu.new

Which outputs:

called new with \()

If your goal is simply to intercept every method dispatch, there's a far less disruptive way that doesn't mess with the type system. For the moment it needs a custom metaclass that disables method cache publication:

class InterceptHOW is Metamodel::ClassHOW {
    method publish_method_cache(|) { }
}
package EXPORTHOW {
    constant class = InterceptHOW;
}

You can then write:

use InterceptAllTheMethods;
class InterceptThemAll {
    method ^find_method(Mu $obj, Str $name) {
        return -> | { say "calling $name" }
    }
}
InterceptThemAll.new

Note that unlike FALLBACK, here you return a code object that will then be invoked. You can write this find_method implementation in the metaclass too, which may be a better factoring; it's hard to say without knowing the problem at hand.

This approach will cause no type-check related issues, let you intercept every method dispatch, and it's easy enough to look up things like bless and just delegate those to the Mu implementation.

0
On

Here is another idea: You could create a new meta class that inherits from ClassHOW, but overrides the methods that role Perl6::Metamodel::MROBasedMethodDispatch provides with versions that skip all parent classes.

For example, this:

# Maybe this belongs on a role. Also, may be worth memoizing.
method can($obj, $name) {
    my @meths;
    my %smt := self.submethod_table($obj);
    if nqp::existskey(%smt, $name) {
        @meths.push(%smt{$name});
    }
    for self.mro($obj) {
        my %mt := $_.HOW.method_table($_);
        if nqp::existskey(%mt, $name) {
            @meths.push(%mt{$name})
        }
    }
    @meths
}

would become

method can($obj, $name) {
    my @meths;
    my %smt := self.submethod_table($obj);
    if nqp::existskey(%smt, $name) {
        @meths.push(%smt{$name});
    }
    @meths
}

That way you don't run into trouble with code that expects all types to conform to Mu, but you can still avoid accidentally calling methods from Mu.