Website hosting service by Active-Venture.com
  

 Back to Index

Metaclassical Tools

Even though proxy methods can provide a more convenient approach to making more struct-like classes than tediously coding up data methods as functions, it still leaves a bit to be desired. For one thing, it means you have to handle bogus calls that you don't mean to trap via your proxy. It also means you have to be quite careful when dealing with inheritance, as detailed above.

Perl programmers have responded to this by creating several different class construction classes. These metaclasses are classes that create other classes. A couple worth looking at are Class::Struct and Alias. These and other related metaclasses can be found in the modules directory on CPAN.

Class::Struct

One of the older ones is Class::Struct. In fact, its syntax and interface were sketched out long before perl5 even solidified into a real thing. What it does is provide you a way to "declare" a class as having objects whose fields are of a specific type. The function that does this is called, not surprisingly enough, struct(). Because structures or records are not base types in Perl, each time you want to create a class to provide a record-like data object, you yourself have to define a new() method, plus separate data-access methods for each of that record's fields. You'll quickly become bored with this process. The Class::Struct::struct() function alleviates this tedium.

Here's a simple example of using it:

 
    use Class::Struct qw(struct);
    use Jobbie;  # user-defined; see below

    struct 'Fred' => {
        one        => '$',
        many       => '@',
        profession => Jobbie,  # calls Jobbie->new()
    };

    $ob = Fred->new;
    $ob->one("hmmmm");

    $ob->many(0, "here");
    $ob->many(1, "you");
    $ob->many(2, "go");
    print "Just set: ", $ob->many(2), "\n";

    $ob->profession->salary(10_000);  

You can declare types in the struct to be basic Perl types, or user-defined types (classes). User types will be initialized by calling that class's new() method.

Here's a real-world example of using struct generation. Let's say you wanted to override Perl's idea of gethostbyname() and gethostbyaddr() so that they would return objects that acted like C structures. We don't care about high-falutin' OO gunk. All we want is for these objects to act like structs in the C sense.

 
    use Socket;
    use Net::hostent;
    $h = gethostbyname("perl.com");  # object return
    printf "perl.com's real name is %s, address %s\n",
	$h->name, inet_ntoa($h->addr);  

Here's how to do this using the Class::Struct module. The crux is going to be this call:

 
    struct 'Net::hostent' => [  	# note bracket
	name       => '$',
	aliases    => '@',
	addrtype   => '$',
	'length'   => '$',
	addr_list  => '@',
     ];  

Which creates object methods of those names and types. It even creates a new() method for us.

We could also have implemented our object this way:

 
    struct 'Net::hostent' => {  	# note brace
	name       => '$',
	aliases    => '@',
	addrtype   => '$',
	'length'   => '$',
	addr_list  => '@',
     };  

and then Class::Struct would have used an anonymous hash as the object type, instead of an anonymous array. The array is faster and smaller, but the hash works out better if you eventually want to do inheritance. Since for this struct-like object we aren't planning on inheritance, this time we'll opt for better speed and size over better flexibility.

Here's the whole implementation:

 
    package Net::hostent;
    use strict;

    BEGIN {
	use Exporter   ();
	our @EXPORT      = qw(gethostbyname gethostbyaddr gethost);
	our @EXPORT_OK   = qw(
			       $h_name         @h_aliases
			       $h_addrtype     $h_length
			       @h_addr_list    $h_addr
			   );
	our %EXPORT_TAGS = ( FIELDS => [ @EXPORT_OK, @EXPORT ] );
    }
    our @EXPORT_OK;

    # Class::Struct forbids use of @ISA
    sub import { goto &Exporter::import }

    use Class::Struct qw(struct);
    struct 'Net::hostent' => [
       name        => '$',
       aliases     => '@',
       addrtype    => '$',
       'length'    => '$',
       addr_list   => '@',
    ];

    sub addr { shift->addr_list->[0] }

    sub populate (@) {
	return unless @_;
	my $hob = new();  # Class::Struct made this!
	$h_name     =    $hob->[0]              = $_[0];
	@h_aliases  = @{ $hob->[1] } = split ' ', $_[1];
	$h_addrtype =    $hob->[2]              = $_[2];
	$h_length   =    $hob->[3]              = $_[3];
	$h_addr     =                             $_[4];
	@h_addr_list = @{ $hob->[4] } =         @_[ (4 .. $#_) ];
	return $hob;
    }

    sub gethostbyname ($)  { populate(CORE::gethostbyname(shift)) }

    sub gethostbyaddr ($;$) {
	my ($addr, $addrtype);
	$addr = shift;
	require Socket unless @_;
	$addrtype = @_ ? shift : Socket::AF_INET();
	populate(CORE::gethostbyaddr($addr, $addrtype))
    }

    sub gethost($) {
	if ($_[0] =~ /^\d+(?:\.\d+(?:\.\d+(?:\.\d+)?)?)?$/) {
	   require Socket;
	   &gethostbyaddr(Socket::inet_aton(shift));
	} else {
	   &gethostbyname;
	}
    }

    1;  

We've snuck in quite a fair bit of other concepts besides just dynamic class creation, like overriding core functions, import/export bits, function prototyping, short-cut function call via &whatever, and function replacement with goto &whatever. These all mostly make sense from the perspective of a traditional module, but as you can see, we can also use them in an object module.

You can look at other object-based, struct-like overrides of core functions in the 5.004 release of Perl in File::stat, Net::hostent, Net::netent, Net::protoent, Net::servent, Time::gmtime, Time::localtime, User::grent, and User::pwent. These modules have a final component that's all lowercase, by convention reserved for compiler pragmas, because they affect the compilation and change a builtin function. They also have the type names that a C programmer would most expect.

Data Members as Variables

If you're used to C++ objects, then you're accustomed to being able to get at an object's data members as simple variables from within a method. The Alias module provides for this, as well as a good bit more, such as the possibility of private methods that the object can call but folks outside the class cannot.

Here's an example of creating a Person using the Alias module. When you update these magical instance variables, you automatically update value fields in the hash. Convenient, eh?

 
    package Person;

    # this is the same as before...
    sub new {
	 my $that  = shift;
	 my $class = ref($that) || $that;
	 my $self = {
	    NAME  => undef,
	    AGE   => undef,
	    PEERS => [],
	};
	bless($self, $class);
	return $self;
    }

    use Alias qw(attr);
    our ($NAME, $AGE, $PEERS);

    sub name {
	my $self = attr shift;
	if (@_) { $NAME = shift; }
	return    $NAME;
    }

    sub age {
	my $self = attr shift;
	if (@_) { $AGE = shift; }
	return    $AGE;
    }

    sub peers {
	my $self = attr shift;
	if (@_) { @PEERS = @_; }
	return    @PEERS;
    }

    sub exclaim {
        my $self = attr shift;
        return sprintf "Hi, I'm %s, age %d, working with %s",
            $NAME, $AGE, join(", ", @PEERS);
    }

    sub happy_birthday {
        my $self = attr shift;
        return ++$AGE;
    }  

The need for the our declaration is because what Alias does is play with package globals with the same name as the fields. To use globals while use strict is in effect, you have to predeclare them. These package variables are localized to the block enclosing the attr() call just as if you'd used a local() on them. However, that means that they're still considered global variables with temporary values, just as with any other local().

It would be nice to combine Alias with something like Class::Struct or Class::MethodMaker.

 

 

 

Domain name registration service & domain search - 
Register cheap domain name from $7.95 and enjoy free domain services 
 

Cheap domain name search service -
Domain name services at just
$8.95/year only
 

Register domain name -
Buy domain name registration and cheap domain transfer at low, affordable price.

© 2002-2004 Active-Venture.com Web Site Hosting Service

 

[ Come to think of it, there are already a million monkeys on a million typewriters, and Usenet is nothing like Shakespeare.   ]

 

 
 
 

Disclaimer: This documentation is provided only for the benefits of our web hosting customers.
For authoritative source of the documentation, please refer to http://www.perldoc.com