Class::Template

One of the older ones is Class::Template. 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::Template::struct() function alleviates this tedium.

Here's a simple example of using it:

    use Class::Template 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::Template 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::Template 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   ();
	use vars       qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
	@ISA         = qw(Exporter);
	@EXPORT      = qw(gethostbyname gethostbyaddr gethost);
	@EXPORT_OK   = qw(
			   $h_name         @h_aliases
			   $h_addrtype     $h_length
			   @h_addr_list    $h_addr
		       );
	%EXPORT_TAGS = ( FIELDS => [ @EXPORT_OK, @EXPORT ] );
    }
    use vars      @EXPORT_OK;

    use Class::Template 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::Template 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, and short-cut function call via &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 lower-case, by convention reserved for compiler pragmas, because they affect the compilation and change a built-in function. They also have the type names that a C programmer would most expect.