Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl: the Markov chain saw
 
PerlMonks  

How to write a Mixin without Exporter mess?

by saintmike (Vicar)
on Jul 06, 2009 at 22:13 UTC ( [id://777695]=perlquestion: print w/replies, xml ) Need Help??

saintmike has asked for the wisdom of the Perl Monks concerning the following question:

Let's say that I want to write a Mixin module that adds the method frobnicate() to a given class Foo:
package Foo; use Mixin qw(frobnicate); sub new { bless {}, shift; } package main; my $foo = Foo->new(); $foo->frobnicate();
The Mixin.pm implementation would then look something like this:
package Mixin; use base 'Exporter'; our @EXPORT_OK = qw(frobnicate); sub frobnicate { print "frobnicating!\n"; }
Now this works ok until I want to add another internal method to the mixin and call it in the same way:
package Mixin; use base 'Exporter'; our @EXPORT_OK = qw(frobnicate); sub frobnicate { my($self) = @_; $self->frobnerize(); } sub frobnerize { my($self) = @_; print "frobnerizing!\n"; } 1;
This will cause $foo->frobnicate() to fail, because frobnerize() isn't known to the object:
Can't locate object method "frobnerize" via package "Foo" at Mixin.pm line 9.
I could call frobnerize($self, ...) instead, but I'd like to use the object syntax for various reasons. Also, I could add frobnerize() in the class extended by the Mixin, like
use Mixin qw(frobnicate frobnerize);
(after adding frobnerize to the EXPORTS_OK array) but that would break the encapsulation, as the main program doesn't care about the internal implementation of Foo's frobnicate(). Or, if I could tell Exporter to use both EXPORTS_OK (frobnicate) and EXPORTS (frobnerize), exporting frobnicate() by request and frobnerize() by default, that would somehow work, although Exporter doesn't seem to support it, and it would, again, break the encapsulation. Better ideas, anyone?

Replies are listed 'Best First'.
Re: How to write a Mixin without Exporter mess?
by chromatic (Archbishop) on Jul 06, 2009 at 22:22 UTC
      chromatic, good point, need to look into it. Is it still true that Moose adds a significant startup time penalty to my script, which isn't running in a persistent environment?

        As of last week it did, but stevan mentioned that the Moose hackers have some ideas in mind to improve startup time in the very near future.

        If your program runs for more than a few seconds, it may be worth considering Moose.

        Is it still true that Moose adds a significant startup time penalty to my script, ...

        This is all relative actually.

        If you talking about a vanilla CGI script that may have tens, hundreds, if not thousands, of invocations a minute, then yes Moose startup will be a problem. It will mostly be a problem because the 1/2 to 1/3 of a second (depending on your machine specs) Moose adds to your startup will be compounded by many concurrent invocations and conspire to slow everything down.

        If you are talking about a command line application where the user expects an immediate response at startup (meaning it is not I/O bound in some way with a DB connection or something), then it might be a problem. I say "might" here because of two things;

        First, if you can present the illusion of response (print a banner or something during the BEGIN phase) then the time taken for the user to see and process this will likely be longer then it takes Moose to load. This is a technique used all over the place in UI design, basically the simplest form being a loading animation.

        Second, since a command line app is probably not going to be used concurrently by many people on the same machine at the same time, it won't suffer the resource starvation that vanilla CGI would. Which means that on a high powered server or something, you might not even notice the startup overhead.

        And then lastly, if your talking about some kind of periodic script or cron-job, which has to startup, run and complete in a given time window, then Moose is probably not a good idea. I say this because the periodic script might awaken during a time of limited resources or of otherwise busy activity, at which point Moose startup might penalize you too heavily.

        Of course the only real way to tell is to try it yourself and see. Run perl -MMoose -e 1 in your target environment and see what happens (make sure you have the latest version though, lots of performance improvements are happening).

        -stvn
Re: How to write a Mixin without Exporter mess?
by JavaFan (Canon) on Jul 06, 2009 at 22:37 UTC
    Considering that Perl doesn't have private methods, the breaking of encapsulation happens the moment you decide to call a sub as a module which should be hidden. After all, if it's hidden, you certainly don't want it to be masked, whether intentionally, or by accident; therefore, there is no reason to call frobnerize as a method. In fact, calling it as a sub is wrong, given your constraints. Call it as a sub - after all, you want it to act as a sub.
Re: How to write a Mixin without Exporter mess?
by ELISHEVA (Prior) on Jul 07, 2009 at 04:22 UTC

    Or you could use multiple inheritance: e.g. our @ISA=qw(A B). Is there a reason that you are using Mixin instead of multiple inheritance or did it just happen that way?

    I don't generally recommend multiple inheritance because weird things can happen if method names aren't orthogonal and you try to get one class hierarchy to define methods that override same named methods in another class hierarchy (e.g. the "diamond inheritance problem"). However, in your case method name clashes doesn't seem to be an issue as you have no concerns importing them into your namespace. If subroutine syntax is an alternative then I would presume your mixin classes are flat as well.

    Best, beth

Re: How to write a Mixin without Exporter mess?
by afoken (Chancellor) on Jul 07, 2009 at 07:33 UTC

    Just a note: In recent perl versions (5.8.3 and newer), you don't have to inherit from Exporter any more. Instead, you can import Exporter's import method into your code (use Exporter qw(import);). This way, you don't have all those Exporter methods in your object / mixin.

    Could mixin help you?

    Alexander

    --
    Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)
Re: How to write a Mixin without Exporter mess?
by trwww (Priest) on Jul 07, 2009 at 02:27 UTC

    You could use:

    sub frobnicate { my($self) = @_; $self->Mixin::frobnerize(); }

    If you don't mind the syntax.

Re: How to write a Mixin without Exporter mess? (conflict)
by tye (Sage) on Jul 07, 2009 at 13:50 UTC
    I could call frobnerize($self, ...) instead, but I'd like to use the object syntax for various reasons.

    Paraphrasing the next paragraph: And you'd like to make object syntax not work for "everyone else".

    It isn't particularly hard to come up with ways to make object syntax for a specific method to only work for some callers (I hope that restating your goals to make how they conflict with each other much clearer also makes the potential solutions much easier to see). And all of those ways suck.

    Most of them don't even fix the real problem with breaking encapsulation. That is, you don't want your mix-in to break if it is used by a class that has its own frobnerize() method. So the real conflict in your goals boils down to:

    For various reasons, you want $self->frobnerize(...) when written in your code to call your frobnerize(). But, for sound design reasons, you want other instances of $self->frobnerize(...) to call some other method or fail if no such other method exists.

    Meanwhile, frobnerize($self,...) solves all of the important problems except the ones that you couldn't bring yourself to admit in public. (:

    Just FYI, I'd like to get a pony for various reasons. And I don't want anybody else to be able to get a pony. ;)

    BTW, if $self->Mixin::frobnerize(...) is too unwieldy for you (especially since the name of your mix-in is surely much longer than "Mixin"), you could use a code ref and $self->$frobnerize(...) with a simple my $frobnerize= \&frobnerize;. I also suspect that this may violate some of your "various reasons".

    Note that I rejected my $frobnerize= sub { ... }; as then stack traces would report calls to such as being to __ANON__ instead of being to Mixin::frobnerize, and I consider that difference important since troubleshooting is too time-consuming compared to coding already.

    - tye        

      ... as then stack traces would report calls to such as being to __ANON__ instead of being to Mixin::frobnerize...

      Point well taken, but for the OP's education, Sub::Name is useful in such cases.

      Paraphrasing the next paragraph: And you'd like to make object syntax not work for "everyone else".

      Actually, no! :) Sorry to lure onto the wrong path with nebulous "various reasons", I've got nothing to hide! Reasons are: I want to use object syntax, not exploiting Perl's way of passing the object ref as the first argument.

      Secondly, in fact, I would like other people's $self->frobnerize() calls to be routed to my Mixin, and I want them to use object syntax as well.

      Hopefully, this has cleared up what I want ... and it looks like the trickery in mixin does exactly the, um, trick. Thanks to afoken who suggested this!

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://777695]
Approved by moritz
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others chanting in the Monastery: (6)
As of 2024-03-19 03:07 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found