Beefy Boxes and Bandwidth Generously Provided by pair Networks
Welcome to the Monastery
 
PerlMonks  

comment on

( [id://3333]=superdoc: print w/replies, xml ) Need Help??

interface.pm explained

Recently I'd been bothered by Perl's seeming lack of a feature I really wanted: Interfaces. I gotta admit I couldn't figure out to implement this feature myself, but discovering interface.pm on CPAN and figuring out how that worked was a real pleasure.

Interfaces

For those not familiar with the term, an interface (popularized by Java; OOP purists might say protocol) is a set of methods that a class takes upon itself to implement. The programming language enforces this agreement, preferably at compile time. An interface is like a base class, except that it contains only abstract ("pure virtual") methods, and even people who frown at multiple inheritance tend to accept a class which implements more than one interface. Interfaces provide a way for a module of code to say "I can do this" (and do it its own way, but giving other modules a standard way of asking for it). An interface can also extend another interface.

One well-known example of an interface is Cloneable: a class that implements it must provide a clone method which returns a copy of of its invocant. Naturally, you could implement cloning from outside of a class by using something like Storable's dclone method on an object, but that might not work correctly in some cases. For example, older versions of Storable would not maintain the tiedness of members. Also, some class data might rightfully need to be updated upon cloning: it's good design to let the class encapsulate that logic. So once again, if you design with interfaces, your class declares itself as implementing the Cloneable interface, and defines a clone method that does precisely the right thing for your class. No more, no less; no messy multiple inheritance.

Doing it in Perl

So what's the problem with doing interfaces with Perl? The only thing you need to do, it seems, is to define stub methods in the "base" interface:

package Cloneable; # interface sub clone { die "you must implement clone() yourself" } 1;
Then, if your class says use base 'Cloneable'; but neglects to override clone, you'll get a descriptive error message when you do call it. One disadvantage of doing it this way is that you have to repeat the body of the abstract method for each such method. But the real problem with this is that the error comes too late: perhaps you never even wrote the code that uses your clone, but you still want the language to raise an error to the effect that you haven't fulfilled your end of the contract by supplying the code yourself. You want a compile-time error!

The problem making this a compile-time error is that Perl doesn't give you any natural hook to look at a package's defined methods at the right time. You want to be able to say use interface 'Cloneable'; and not worry about it, right? But if you put that at the top of your package, perl will load interface.pm while your package is still being compiled — before your methods have been registered. If interface.pm looks at your module at this stage, it will complain that you haven't implemented the methods, even though you did!

Perl's specially timed blocks don't help here, either. BEGIN is obviously premature, since no matter where we put it, it'd cause our hook to be run at (something's) compile time: too early. perlmod tells us that INIT and CHECK blocks "are useful to catch the transition between the compilation phase and the execution phase", but in order to have the interface check happen during either of these you need to split the use call to a require and an import -- resulting in the unwieldy

INIT { require interface; interface->import("Cloneable") }

Not very elegant, since you have to put this in your client code, repeating it whenever you implement an interface. But it looks as if this can't be done any better!

But it turns out that it can.

Enter the clever hack

What interface.pm does is check whether the appropriate methods are implemented in the calling class (or one of its parents) during the import hook. But as we stated above, this is too early because the methods haven't been compiled yet. What to do? Get them to compile! import contains this code (edited):

sub import { my $callerpackage = caller; return 1 if($locks{$callerpackage}); $locks{$callerpackage} = 1; # they need to finish loading before we can inspect what methods the +y've defined eval "use $callerpackage;"; # [do the Interface validation and die if it fails...] undef $locks{$callerpackage}; }

The eval line there merely causes Perl to finish the compilation of the calling module; none of its actual functionality is used — only looked at. To avoid infinite loops, interface.pm maintains a %locks global that keeps track of which calling packages are in the middle of validation. %locks is a hash and not a simple bit lock so that it works with complex hierarchies and many calls to interface.pm. Every module is guaranteed to get its interface validated (think why!). The calling code simply says

use interface qw(Cloneable Describable SomeOtherInterface);

And that's it.

Conclusion

To be successful as an extension to the language, a framework that enables interfaces must use simple syntax, preferably familiar syntax. What's more familiar to the Perl hacker than a use statement? The module I described here achieves this usage goal, as well as its main operative goal of compile-time interface satisfaction checking. True, it uses a hack to get there, but the hack is nicely encapsulated so that the user doesn't need to know about it. An alternative implementation of this feature would be to use source filters, but these are probably more hackish and have their own problems.


In reply to interface.pm explained by gaal

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post; it's "PerlMonks-approved HTML":



  • Are you posting in the right place? Check out Where do I post X? to know for sure.
  • Posts may use any of the Perl Monks Approved HTML tags. Currently these include the following:
    <code> <a> <b> <big> <blockquote> <br /> <dd> <dl> <dt> <em> <font> <h1> <h2> <h3> <h4> <h5> <h6> <hr /> <i> <li> <nbsp> <ol> <p> <small> <strike> <strong> <sub> <sup> <table> <td> <th> <tr> <tt> <u> <ul>
  • Snippets of code should be wrapped in <code> tags not <pre> tags. In fact, <pre> tags should generally be avoided. If they must be used, extreme care should be taken to ensure that their contents do not have long lines (<70 chars), in order to prevent horizontal scrolling (and possible janitor intervention).
  • Want more info? How to link or How to display code and escape characters are good places to start.
Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others taking refuge in the Monastery: (5)
As of 2024-04-19 15:26 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found