http://www.perlmonks.org?node_id=702435

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

Hi again, most venerated ones,

Continuing the line of my submissions for the idiot question of the decade award, my next (and I truly believe, best yet) attempt goes as follows...

I have (been told I have) a requirement for a module that, when inherited from, will...

The first is/was fairly trivial - via judicious use of a parameterised import routine initialising a package scoped hash and an AUTOLOAD routine using said package variable to categorise uncaught method calls.

The latter requirement wasn't/isn't set out in stone, so shouldn't be a problem either way, as for the middle requirement, well that is, as thay say, another matter entirely...

My initial reaction was to pre-judge a set of likely constructor names and parse the inheriting package for any sub(s) by one of these names. I hacked together a .t script containing a number of package declarations, which when run, passed all tests - at this point, I didn't/couldn't suss WTF was going on.

Shortly thereafter, my co-process gifted me a copy of Perl Hacks, and whilst reading #47, I guessed that B::Deparse (about which I knew nothing) may be put to use in my particular problem.
Soooo, without any further thought at all (as will become/is already obvious), my initial reaction was to attempt to rewrite import to utilise said module against all subs/methods in the inheriting class.

The module was re-visited, along with its test harness and I was (admittedly, not entirely unexpectedly) disappointed to observe that some of the tests failed.
Eventually, it dawned on me that the successful tests all involved packages declared in-line in the .t script, or put another way, the tests that failed involved the use/require of a separate package file...
Anyone seen the root cause of the problem yet (which is more than I had;-) ?

Yep, that's right, at the time a package->import is called by the compiler, only the BEGIN block/sub exists in the inheriting class when it [the inheriting class] is implemented as a disparate package file - the compiler is busy determining dependencies and preloading the symbol table ready to begin the ascent of compiling the package itself.

.oO(Surely I should've seen that ages ago - DOH!!!)

My current thinking (or what passes for it in my world) is that I probably need to beg/borrow/steal, or possibly write, a module that acts as an inheritable compile pragma to any sub-class - unless you know better...:-)

So come on, you are cordially invited to make the most of this opportunity to support my candidacy for the above award...

TIA & rgds to all ,

Exit, stage left, a goon show fan that wishes he'd chosen the more apposite PM user name of Eccles...as in 'Mad Dan'

A user level that continues to overstate my experience :-))

Replies are listed 'Best First'.
Re: Inheritable pragma ... or how I learnt perls' compilation order
by kyle (Abbot) on Aug 05, 2008 at 18:37 UTC

    I'm not sure I follow what you're trying to do, but my reading reminds me of BEGIN, UNITCHECK, CHECK, INIT, and END blocks, which I also don't fully understand.

    If you want to ensure that there are no other constructors before you, I don't think you'll have much luck. Any code any where can bless into your class and cross its digits in hopes that nothing goes wrong.

    I could imagine perhaps a new() method that notes its results (with weak references, of course) in a lexical hash. Then other methods in the class can check up on the objects they're called on in this table of "allowed" objects and throw a tantrum if a transgression is detected. There would be a performance penalty, of course. Also, you can't stop someone from "subclassing" via delegation. Such an object could even override UNIVERSAL::isa so as to pretend to be your object.

    So what are you really trying to gain here? What is the rationale behind these requirements? What do you want to protect yourself against?

      Hi ,

      Thanx for your insight - looks like UNITCHECK or, probably, CHECK would be the way to go.

      Way to go kyle :-))

      Update

      Thinking about it, using the CHECK block would be best as it's also run when using perl -c .... Something like the following would possibly suffice (based on code supplied elsewhere in the thread 702588)...

      CHECK { my $dp = B::Deparse->new(); while (my ($pkg, $hash) = each %INTERFACES) { while (my ($var, $glob) = each %{"${pkg}::"}) { next unless defined &$glob; croak "Constructor detected: ${pkg}::$var" if $dp->coderef2text(\&{"${pkg}::$var"}) =~ /\bbless\b/; } } }
      Note that this is not too much more than thinking aloud i.e. code untried and untested ... yet;-)

      Further Update

      Corrected typo (missing trailing slash) in RE

      At last, a user level that overstates my experience :-))
Re: Inheritable pragma ... or how I learnt perls' compilation order
by Your Mother (Archbishop) on Aug 05, 2008 at 22:16 UTC

    Some of that sounds a bit off. Taking just the first-

    Fully support abstract methods in as much as a call to a non-overridden abstract (by design) method causes the code to croak or otherwise blow up
    ...
    The first is/was fairly trivial - via judicious use of a parameterised import routine initialising a package scoped hash and an AUTOLOAD routine using said package variable to categorise uncaught method calls.

    Methods (object oriented code) should have no use for import, and AUTOLOAD is not for doing abstract parent methods. For those, you just write/stub the method with the croak/die/confess in it. That way any successful call has to have come from a subclass. Maybe you're doing something different but the way it sounds, it sounds like you're going about coding up your specs entirely wrong. :(

      Hi ,

      ... sounds like my description wasn't the best in the world.
      import is used solely for the (pure abstract) inheritor to define the interface it expects its inheritors to implement.
      AUTOLOAD is used solely to catch unimplemented methods, mutators etc. and provide a categorised croak.

      In outline, the implementation was along the following lines (I'm afraid I havn't got the actual code to hand at the moment - it's on a memory stick @ work & I'm working from home at the time of writing - doncha just hate it when that happens)...

      package AbstractBase; use strict; use warnings; use Carp qw/confess/; use Storable qw/dclone/; my %DEF_INTERFACE = ( methods => [], mutators => [], ); use vars qw/%INTERFACES/; sub import { my ($self, $caller) = (shift, caller); %INTERFACES{$caller} = dclone { (%DEF_INTERFACE), @_ }; } sub AUTOLOAD { my $self = shift; (my $method = $AUTOLOAD) =~ s/(.*::)//; my $interface = $INTERFACES{$1}; croak "Abstract method not instantiated: $method" if grep /^$method$/, @{$interface->{methods}}; croak "Mutator method not instantiated: $method" if grep /^$method$/, @{$interface->{mutators}}; croak "Undefined interface component: $method"; }

      At last, a user level that overstates my experience :-))
Re: Inheritable pragma ... or how I learnt perls' compilation order
by dragonchild (Archbishop) on Aug 06, 2008 at 00:23 UTC
    I'm curious - why aren't you using Moose ?

    My criteria for good software:
    1. Does it work?
    2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
      Simply put, I've never heard of it - but you've now filled that gap & I'll away and investigate...

      That being said, IMO, you don't tend to learn much if you havn't been thro' the pain.

      At last, a user level that overstates my experience :-))

        The "abstract" ness you are looking for can be done mostly by using Moose::Role, you can find links to a number of talks/slides that discuss roles in more detail here. And if you have further questions feel free to join the mailing list (moose@perl.org) and/or IRC (#moose@irc.perl.org).

        -stvn