I'd like to start out with a disclaimer. The ideas below are primarily a demonstration of the flexibility of Perl's object system. Using it in a real application is potentially dangerous. I suspect it will have little, if any, benifit for solving real-world problems. It's just a crazy, random idea that managed to escape the bowels of my brain and into the light. I have no idea if it is even truely orginal (really, it's a natural progression of a fairly well-known Perl construct). It won't hurt to learn about it, but then again, many programmers have perfectly happy careers not knowing about Duff's Device.

Many objects use accessors and mutators to provide restricted access to otherwise private data. In Perl, the accessors and mutators are often combined into a single method, like this:

sub foo { my $self = shift; $self->{foo} = shift if @_; return $self->{foo}; }

90% of the time, the code for each accessor/mutator is identical to all other accessors/mutators, except for the name of the field. In Perl, there is a shortcut we can use involving closures to generate the accessors/mutators (this snippet is a trimmed-down version of the one on pp. 338-339 of the Blue Camel):

for my $field (qw(name race aliases)) { my $slot = __PACKAGE__ . "::$field"; no strict "refs"; # So symbolic ref to typeglob works *$field = sub { my $self = shift; $self->{$slot} = shift if @_; return $self->{$slot}; }; }

That is cool enough, and cuts out a lot of redundant code. Once I finally understood what was going on here, I was awestruck.

Let's move into a working application of the above:

#!/usr/bin/perl use strict; use warnings; package Foo; sub new { my $invocant = shift; my $class = ref($invocant) || $invocant; my $self = { }; bless $self, $class; $self->{baz} = 1; $self->{bah} = 2; return $self; } sub baz { die "baz() not overriden\n"; } sub bah { die "bah() not overriden\n"; } package Bar; our @ISA = qw(Foo); for my $field (qw(baz bah)) { my $slot = __PACKAGE__ . "::$field"; no strict 'refs'; # Need symbolic refs to typeglob *$field = sub { my $self = shift; $self->{$slot} = shift if @_; return $self->{$slot}; }; } package main; my $obj = Bar->new(); print "Baz: ", $obj->baz, "\n"; print "Bah: ", $obj->bah, "\n";

Run that, and you'll see it print out:

Baz: 1 Bah: 2

Now remove the 'bah' on line 37 from the list. It will now die when it calls $obj->bah.

Now for the dangerous part. Using closures, we can create and destroy methods at runtime whenever we feel like it.

#!/usr/bin/perl use strict; use warnings; package Foo; sub new { my $invocant = shift; my $class = ref($invocant) || $invocant; my $self = { }; bless $self, $class; $self->{baz} = 1; $self->{bah} = 2; return $self; } sub baz { die "baz() not overriden\n"; } sub bah { die "bah() not overriden\n"; } package Bar; our @ISA = qw(Foo); my @DYNAMIC_METHODS = qw(faz); sub make_methods { for my $field (@DYNAMIC_METHODS) { my $slot = __PACKAGE__ . "::$field"; no strict 'refs'; *$field = sub { print "Called faz\n"; }; } } sub destroy_methods { for my $field (@DYNAMIC_METHODS) { my $slot = __PACKAGE__ . "::$field"; no strict 'refs'; undef *$field; } } for my $field (qw(baz bah)) { my $slot = __PACKAGE__ . "::$field"; no strict 'refs'; # Need symbolic refs to typeglob *$field = sub { my $self = shift; $self->{$slot} = shift if @_; return $self->{$slot}; }; } package main; my $obj = Bar->new(); print "Baz: ", $obj->baz, "\n"; print "Bah: ", $obj->bah, "\n"; $obj->make_methods(); $obj->faz(); $obj->destroy_methods(); $obj->faz();

The second call to $obj->faz() will fail. Crazy, no?

Update: Fixed bug in the example closure where $fields and $slot were in the wrong places (thanks to djantzen for pointing this out).


In reply to Now you see it, now you don't by hardburn

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



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.