The Struct package helps to build perl object classes. The objects store their data members in an array. The data accesors have the standard method call syntax, but in the most cases the method call is replaced by the random access to an array element. This makes the Struct based classes much more efficient than the classical solution based on hashes, and, unlike the fields module, does not require special typed lexical variables.

We introduce the features of the Struct package by examples of increasing complexity.

Basic usage

package Example; use Struct ( '$x', # scalar data member, initialized with "" '@y', # array data member, initialized with [ ] '%z', # hash data member, initialized with { } '&f', # function pointer, initialized with undef ); $e=new Example; $e->x=123; # scalar member accessor returns a lvalue push @{$e->y}, 456; # modify the array $e->z->{ABC}=789; $e->y=[ 456 ]; # replace the array completely $e->z={ ABC => 789 }; $e->f=sub { ... }; # define and call a function via pointer $e->f->(1,2,3);

This is the most simple case. Each object of the class Example consists of four data members. The constructor new is generated automatically. It initializes the data members with appropriate default values.

The type signs $, @, %, and & in the use operator are only needed to choose the correct default value. The access methods have the same syntax independently from the member type. For array types they always return a reference, which can be either dereferenced or assigned to, the latter completely replacing the array data member.

The function pointer distinguishes between subroutine and method references (the latter being defined via the method attribute: sub NAME : method { ... } . When a method is called via the function pointer, the reference to the object is automatically pushed in front of the argument list.

The data member accessors can also be called indirectly, that is, with member name obtained from an expression:

$field="x"; $e->$field=123;

Only in this case the access operation will have the full costs of a method call. In the case of a constant member name the member call operation is executed only once and then replaced by a much more efficient array access operation.

Note that there is no automatically generated destructor. If you need one, you must define it yourself.

Constructor with initialization

package Example; use Struct ( [ '$x' => '123' ], [ '@y' => '( 456 )' ], [ '%z' => '{ ABC => 789 }' ], [ '&f' => '\&sub_name' ], [ '&m' => "method_name" ], );

This time the generated constructor will assign the given initial values to the data members. You can freely mix the explicitly initialized members with default-initialized.

The function pointers accept both subroutine references and method names. The method lookup is postponed until the first call via the pointer; it is performed using the standard @ISA based algorithm.

The initial values don't need to be constants, they can be expressions of arbitrary complexity. But they must be always quoted, if their evaluation should be postponed till the constructor execution. To make the difference clear, here two further examples:

my $global=1; package Class1; use Struct [ '$x' => '$global' ]; package Class2; use Struct [ '$x' => $global ]; package main; $global=2; $c1=new Class1; $c2=new Class2;

Now $c1->x == 2 and $c2->x == 1.

Constructor with parameters

package Example; use Struct ( [ new => '$;$' ], [ '$x' => '#1' ], [ '$y' => '#2 * 2' ], );

The special keyword new introduces the signature for the generated constructor. It is built according to the standard perl rules for subroutine prototypes. The initial values may contain tokens of the form #i referring to the i-th argument of the constructor call. The arguments are counted starting with 1, since the leading argument is, as usual, the package name. As shown in this example, the argument references may stand alone or be involved in expressions. In the latter case the #i token must be separated with white spaces from the rest of the expression.

Constructor with extra functionality

package Example; use Struct ... # as above sub new { my $this=&_new; ... # some special code return $this; }

If you need to perform some additional operations in the constructor which do not fit well in the initial expressions, you can simply redefine the constructor and put all the special stuff there. The automatically generated code with initializers is still available as _new, which should be called using & syntax, that is, without creating a new argument list @_. The automatic constructor _new pops the package name off the argument list @_.

Constructor with arbitrary many arguments

package Example; use Struct ( [ new => '$@' ], [ '$x' => '#1' ], [ '@y' => '@' ], ); my $e=new Example(1, 2, 3); # equvalent to: $e->x = 1; $e->y = [ 2, 3 ]

You can gather the trailing arguments in an array-type data member using the special initializer '@'. Please note that the same symbol must be put at the end of the signature. If you declare optional positional arguments like this: '$;$@', then the trailing list will only contain the arguments behind the last positional one, in this example starting with the third argument.

Constructor with keyword arguments

package Example; use Struct ( [ new => '$%' ], [ '$x' => '#1' ], [ '$y' => '##' ], '$u', '$v', '$w', '$z', ); my $e=new Example(1, z => 2, v => 'abc');

If the constructor signature finishes with the character %, the generated constructor will accept the keyword => value pairs for all data members. The members omitted in the constructor call will be initialized with default values according to their type. All or some keyword arguments may also be passed in anonymous hashes:

my $some_args={ z => 2, v => 'abc' }; my $e=new Example(1, u => 'ABC', $some_args);

You can mix positional and keyword arguments in a constructor, as shown in the example. The data members initialized with positional arguments or with a special initializer ## are excluded from keyword lookup. Specifying their names in the constructor call will cause an exception, exactly the same as if the keywords were unknown.

Another form of mixing keyword and positional arguments allows to gather the trailing arguments after the keyword => value pairs:

package Example2; use Struct ( [ new => '%@' ], '$x', '$y', '$z', [ '@trailing' => '@' ], ); my $e=new Example2(x => 1, y => 'abc', 10..20);

Here the keyword => value pairs are expected at the beginning of the argument list. The trailing arguments, starting with an argument not having this syntax (and not being an anonymous hash), are stored in the array-type member designated by the '@' initializer.

Keywords and access filters

package Example; use Struct ( [ new => '%' ], [ '$x' => 'convert_x(#%)' ], [ '$y' => 'convert_y(#%)', default => '"def_y"' ], );

When building the constructor with keyword arguments, you can also specify converter expressions for some data members. Then the data member will be initialized with the result of this expression. The special token #% is replaced by the two-element list member_name => value as given to the constructor. You may also specify an optional default value which will be used for initialization if the keyword does not occur among the constructor arguments. Note that the default value is not taken verbatim, but converted by the given access filter. In this example, the member y will be default-initialized with the return value of convert_y("def_y") .

The converter and default expressions may do whatever appropriate, and even raise an exception if the passed value does not pass some consistency checks.

Later on, you can test a class member whether it was initialized with the default value using the following expression, returning a boolean value:

Struct::is_default($obj->member_name)

Another interesting features of the converter expressions is that they are applied not only to the constructor arguments, but also in the assignments to the data members. This way, for an object defined as in example above,

$e->x=123;

will be automatically translated into

$e->x=convert_x(x => 123);

The translation will be done whereever the assignment occurs, not only in the own package, and even if the accessor name is specified indirectly.

Initializers accessing other data members

In a constructor with keyword arguments, some members may be initialized (or given default values) with expressions involving other (previously defined) data members. The latter have to be accessed via the $this-> object pointer. Such initializers are executed out of order, after the object has been created (otherwise $this would be void).

For example:

package Example; use Struct ( [ new => '%' ], '$x', [ '$y' => '$this->x+1' ], [ '$z' => '$this->convert_z(#%)', default => '$this->x-1' ] ); sub convert_z : method { ... }

Overriding and merging keyword constructor arguments

As already mentioned above, the list of keyword => value argument pairs in the constructor call can be intermixed with anonymous hashes carrying further argument pairs. This is primarily devised for situations where some part of the arguments is firmly encoded in the function creating an object, while another part comes from an outer function, possibly called by the end user. Let's consider a typical visualization method callable directly by the user:

user_method VISUAL(%Visual::PointSet::decorations) { my ($self, $decor)=@_; ... new Visual::PointSet( Name => $self->name, VertexColor => $def_vertex_color, ... $decor ); }

In this example the user options coming from the outside (let's recall that they are stored in an anonymous hash referenced by $decor) are passed as the last constructor argument. Hence the default vertex color $def_vertex_color will be only taken if it is lacking in the VISUAL call.

If you want to enforce some certain value for the VertexColor member, you should obviously put it below the $decor argument:

new Visual::PointSet( ... $decor, VertexColor => $fixed_vertex_color, );

Finally, you might want to define some merging function which combines the default setting with user wishes in some senseful way. To achieve this, you must first define the merging function itself and specify the call to it in the class description using the special merge option:

package Visual::PointSet; use Struct ( [ '$VertexColor' => '$def_vertex_color', merge => '$this->merge_color(#%)' ], ... ); sub merge_color { my ($this, $color_attr, $new_value)=@_; ... }

The merging function receives the object reference, as usual, in the first argument $this, followed by the member name (in this example it will be "VertexColors") and the new value for the member. If you need to pass additional arguments, just put them behind the #% token. This function is expected to compute the final value and to assign it to the member. Its return value is discarded.

Now you can utilize the merging functions by calling the merge method of the newly created object:

new Visual::PointSet( ... , $decor) -> merge( VertexColor => $fixed_vertex_color, ... );

The merge method is created automatically together with the constructor. It accepts a list of keyword => value pairs for all or some members which merging functions are declared for and calls the latter in the order of member definitions. It always returns the reference to the object itself.

The merge option can be naturally combined with the access filter and default option described above. In this case the member first becomes initialized with the (filtered) default value, then the merging function is called, and as soon as it assigns the new value to the member, it undergoes again the same filtering procedure.

Redirecting calls via function pointers

package Example; sub fallback : method { ... } use Struct ( [ '$super' => 'undef' ], [ '&f' => '->super' ], [ '&g' => '->super || \&fallback' ], ); my $x=new Example; $x->f=sub : method { ... }; my $y=new Example; $y->super=$x; $y->f->(1); # calls $x->f->(1); $y->g->(2); # calls fallback($x,2); $y->g=sub { ... }; $y->g->(3); # now calls the function assigned in the line above

The function pointer members may have initializers of a special form '->other_member' which allow to forward the calls made via the pointer to another object stored in some other data field. The fallback value is applied only when the method search arrives at end of the redirection chain.

The methods called this way get the last object in the redirection chain as the first argument. To obtain the reference to the original object at the chain begin, the method must call the special function original_object.

Aliases for members

package Example; use Struct ( '$x | X', );

An object of this class has one data member, accessible under two names as $obj->x or $obj->X.

A member may have arbitrary many aliases, separated in the declaration by bars | and optional white spaces. Note that the type character (in this example $) must be specified only once, in front of the first name.

An aliased member may have all the kinds of initializers and access filters described above. If the data member is initialized via keyword argument, any of the aliases may appear in the argument list, and the used one will be passed to the access filter. But specifying several aliases of the same member in a constructor call is vorbidden and causes an exception.

Derived classes

Classes built with Struct may have superclasses and be derived as usual. However, there is one restriction: Struct does not support multiple inheritance. You may specify several parent classes, but at most one of them may be in its turn built with Struct. All other ancestors must be dataless abstract classes (aka interfaces).

If you are deriving from a Struct based class and want to add new data members, you must use the special syntax in the Struct parameter list; simply putting the parent class name in @ISA won't work:

package Example2; use Struct ( [ '@ISA' => 'Example' ], '$z', );

Inheriting static class members

If a class definition lies in a scope with namespace mode enabled, Struct recognizes this and puts the parent class in the lookup list of the current class. This way all parent's functions and variables are visible to the derived class without qualification, like static class members in C++.

Overriding the inherited initializers

Sometimes the constructor for the derived class needs to initialize the base class data members differently from the inherited constructor. Or it must get more arguments than the inherited one, and therefore its signature must be changed. Both can be done by specifying new signature and/or initializers directly after the ISA setting:

package Example2; use Struct ( [ '@ISA' => 'Example' ], [ new => '$%' ], [ '$x' => 'other_convert(#%)', default => "other_def_x" ], [ '$z' => '#1' ], );

The $y initializer is the same as in the base class Example. It is important to put all overriding initializers to the beginning of the parameter list, otherwise Struct will complain about duplicate field names.

The merging functions for the members are inherited as well. They can also be overridden in the derived class, but only together with the initializer.

Additional methods

A class built with Struct automatically gets the following methods additionally to the constructor and member accessors:

_new
as already mentioned above, the automatically generated constructor is available under two names, new and _new. If you redefine the constructor, you can still access the generated code via this alias
sizeof
return the number of data members = the length of the array representing an object
original_object
in a method called via function pointer with redirection, returns the reference to the object at the beginning of the redirection chain. If no redirection took place, repeats the first argument $_[0] .