MRP::BaseClass - My base class object


NAME

MRP::BaseClass - My base class object


DESCRIPTION

Base class for my perl objects that generates the class interface from a definition.


SYNOPSIS

The aim of this package is to allow you to define a classes interface, and have perl generate all of the standard functions for you. Currently, you can define:

fields
Member access functions are auto-generated so that nowhere in your code do you ever directly access member variables.

package variables
Class functions are auto-generated to make package variables work like static class members.

default variables
The package variable an be made to act as the default value for a field of the same name.

delegation support
Simply specify which field is a delegate and which funcitons to delegate to it, and the glue code is auto-generated.

The other realy usefull thing it does is provide dramaticaly better error messages when methods or static functions canot be found. Try invoking the <C -w> flag. It even lists possible correct spellings of misspelled function names!

As a matter of course, I include the class definition in a BEGIN block at the end of the package. This allows the interface to be checked, and the code to be generated at compile time. This has the additional benefit that these checks are performed during a <C -c> compilation.


SEE ALSO

This module relies upon these modules:

MRP::Interface
MRP::Text
MRP::Introspection


A skeletal derived class

 package myDerived;
 use strict;                   # I'm paranoid about this...
 use vars qw ( @ISA %fields );
 use MRP::BaseClass;
 BEGIN {                       # Putting this code in BEGIN makes
                               # errors show up at compile time.
    # our parent class - in this case only MRP::BaseClass.
    @ISA = ('MRP::BaseClass');
    # the object model - member names and initial values
    %fields = (
               'number' => 1,
               'hash' => undef,
               'array' => undef,
               );
    # check4Clashes ensures that you are not overriding base class
    # member variables and creates the member access functions.
    myDerived->check4Clashes();
 }
 # Our constructor - this one is very simple.
 sub new {
    my $class = shift;              # we need to know what type we are.
    my $self = new MRP::BaseClass;  # get an object from the emediate parent
    $self->rebless ($class);        # re-bless the object into this package
                                    # this is when MRP::BaseClass sets up
                                    # member variables
    return $self;                   # return the object.
 }
 # This function is called during re-blessing. Here we are just going
 # to set the member 'hash' to a new hash reference.
 # At this point $self is blessed into this package, and contains
 # all of its members. You could call member funcitons from here if
 # you realy wanted to.
 sub initialize {
    my $self = shift;
    $self->hash({});
    $self->array([]);
 }

You can now use this class as follows:

 $thing = new myDerived();          # get a new myDerived object
 $thing->hash->{'red'} = 1;         # set the key red in hash to 1
 $thing->number(5);                 # set the value of number to 5
 $thing->array->[0] = "goo";        # set the first element of array to 'goo'
 $thing->array->('a','b');          # this is not what you meant. The
                                    # array ref will be replaced with 'a' and
                                    # the rest of the parameters will be lost.
 @{$thing->array} = ('a','b');      # this is better.
 $thing->array([('a','b')]);        # replace the refrerence with a new one
                                    # containing the array ('a','b')
 print "I have the number ", $thing->number, " in me\n";

A class derived from myDerived would just substitute the name myDerived for MRP::BaseClass. Hey presto - all sorted!

The return value of the member access functons is the value of the member. If a value is given to the member access function then the member is set to that value, and it's new value is returned. Thus:

 $val = $thing->hash;     # puts the same reference in $val that was in hash
 $thing->hash(\%myHash);  # sets the hash reference used to\%myHash


Using clever fields

MRP::BaseClass is clever enough to create hash and array members and specialised access functions, as well as methods that check the interface of methods. Include a fields like this...

    %fields = (
               myHashMember => {},
               myArrayMember => [],
               myDelegate => MRP::Interface->ActionDelegate,
               );

and MRP::BaseClass will do the following:

So, reworking the original example,

    %fields = (
               'number' => 1,
               'hash' => {},   # put an empty hash here
               'array' => [],  # and an empty array here
               );

The initialize subroutine can be discarded - you don't need to set anything up now. The rest of the code is unchanged. You can access the members as follows:

 $thing = new myDerived;          # get an object as before
 $thing->number(50);              # access to scalars doesn't change
 $thing->hash('foo'=>1,'bar'=>2); # sets hash to the value passed.
 $ref = $thing->hash;             # returns the reference used
 $val = $thing->hash->{'foo'};    # return 1
 %hash = $thing->hash;            # sets the contence of %hash equal to hash
 $thing->array(@list);            # sets the value of array to @list
 @list = $thing->array();         # sets the value of @list to array
 $array = $thing->array;          # sets $array to the same reference as array
 $val = $thing->array->[3];       # sets $val equal to the 3rd element of array
 $thing->array(\@array);          # sets array to a one member list containing
                                  # \@array. array and @array still point to
                                  # different objects
 $thing->array_ref(\@anarray);    # set array to the reference of @anarray. They
                                  # now refer to the same object.
 $thing->hash_ref(\%ahash);       # likewise, hash now refers to ahash.


Package variables

MRP::BaseClass will also generate access functions for package variables. You must list the ones that you want to expose in the @public_vars array. When check4Clashes is invoked, it will generate access functions for each of the values in this array. For example,

 package myDerived;
 use strict;
 use vars qw(@ISA @public_vars $scalar %hash @array);
 use MRP::BaseClass;
 @ISA = qw(MRP::BaseClass);
 @public_vars = qw($scalar %hash @array);
 myDerived->check4Clashes();
 ...
 $s = myDerived->scalar;      # set $s to $myDerived::scalar
 myDerived->hash(%newValue);  # set %myDerived::hash to %newValue
 @list = myDerived->array;    # set @list to @myDerived::array

The benefit of this is that in any package derived from myDerived, you can still access these variables. This makes package variable access follow all the same rules as member variables do for inheritance. One freebie is that you can access these package variables via an object. So, if $error is a global scalar then $obj->error would access the global value of error for you. In this way, global variables are made to work like static class members.


Package variables and fields with the same name

There will be cases when the same variable is used for a package variable and field name. If you include the variable both in fields and in public_vars, then the access function will decide which value to return based upon whether it was invoked as a class or objet method.

 package myDerived;
 use strict;
 use vars qw(@ISA @public_vars %fields $error $linewidth);
 use MRP::BaseClass;
 @ISA = qw(MRP::BaseClass);
 %fields = { $error=>undef, };
 @public_vars = qw($error);
 myDerived->check4Clashes();
 ...
 $t = new myDerived;   # get a new object
 $t->error;            # returns the error field of object $t
 myDerived->error;     # returns the error associated with the package


Default variables

These are specified in the @defaults array. They do not need to be listed in either fields or public_vars. A field and a package variable will be generate, with the field providing the object-specific value and the package variable provides the default value. This is returned when you query an object for it, and the object value is undef. So - to illustrate with the linewidth field:

 package myDerived;
 use strict;
 use vars qw(@ISA @defaults $linewidth);
 use MRP::BaseClass;
 @ISA = qw(MRP::BaseClass);
 @defaults = qw($linewidth);
 myDerived->check4Clashes();
 ...
 $t = new myDerived;        # make a new myDerived object
 $t->linewidth(72);         # set linewidth to 72 for this object
 myDerived->linewidth(70);  # set the defuault value to 70
 print $t->linewidth, "\n"; # will print '72'
 $t->linewidth(undef);      # make sure that linewidth is undef
 print $t->linewidth, "\n"; # will now print '70' - the default value


Delegation support

Very often, an object will contain a reference to another object that it uses to implement part of its public interface. Usualy this is achieved by just forwarding the function calls directly. MRP::BaseClass will generate these functions automaticaly from the %delegates hash.

  package myComposite;
  use strict;
  use MRP::BaseClass;
  use vars qw(%fields %delegates @ISA);
  BEGIN {
    @ISA = qw(MRP::BaseClass);
    %delegates = (
                  stringMaker => [qw(toString toText)],
                 );
    myComopsite->check4Clashes();
  }

... insert new function and the rest of the functionality here...

The delegates hash contains field names as keys followed by an arrray reference containing function names. Thus, code will be generated to wire myComoposite->toString to myComposite>stringMaker->toString. The arguments are forwarded, so that myComposite objects share a subset of the interface of the stringMaker delegate.

I have invented a hypothetical class called StringMaker that can write out text in different languages. I wish that I had one during school! It can expand messages into a string with toText and to formatted text with toString. It can also have its target set with language. I do not wish myComposite to have a language method, but I do wish it to be able to write out text.

 $mc1 = new myComposite(); # get a composite object
 $mc2 = new myComposite(); # and another
 $mc1->stringMaker($sm1); # set stringMaker to $sm1
 $sm1->language('English UK'); # and set the stringMaker language to british.
 $mc2->stringMaker($sm2); # set this stringmaker as well
 $sm2->language(french); # and set the language to french
 print $mc1->toText('Apologise'); # an eloquant british apology
 print $mc2->toText('Apologise'); # a beautifull french apology

If you try to use a field as a delegate that appears in the fields hash, then a fatal error message will be printed.


Delegation support with type checking

It is not possible to check that the methods are legal for the delegate, as the type of the delegate is not known at compile time. However, by giving an interface object rather than a function name, the object can be validated when the delegate is set.

  %delegates = (stringMaker => [MRP::Interface->StringMaker];);

Now, any object put into the stringMaker field of this class must implement the interfave StringMaker, or an exception will be thrown.


Enhanced <C -w> diagnostics

If you run your script with <C -w> invoked, then MRP::BaseClass gives you some extra diagnostics that I have found to be very usefull. The first line is of the form:

Can't access 'funcName' in [object/package] [objectName/packageName]:

This clearly describes which function could not be located, whether it was invoked as an object or package function, and the object or package.

There may follow a section where the program lists potential corrections. Thus, a common mistake I make is to type check4clashes - incorrect case. In response to this, it would sudgest that I use the function MRP::BaseClass::check4Clashes. It lists both similar filed and function names.

The next line says where the error occured, in the form in temp/testMutipleInheritance.pl at line 40.

It then dumps the model for the object. This consists of its fields and their values, all methods that can be directly accessed via inheritance, and all package variables. The function list does not include functions in baseclasses that are overridden in derived classes, and it doesn't include functions starting with a leading underscore.

Following this, a complete stack trace is displayed.


Functions

new
Returns a new MRP::BaseClass object. This can then be reblessed into your package.

rebless
Converts a base class into your class! Call as $self->rebless($class) where $self is a base class object. It returns $self, blessed, with the correct member variables. It is at this time that initialize, if present, is invoked. This functon should be used in every constructor that has to bless an object from a base class that is derived from MRP::BaseClass into the current package. If you employ multiple inheritance of field members, then use $class->rebless($parent1, $parent2...). This is clever enough to combine the data members from multiple parents. Therefore, it implements multiple inheritance by combining all fields within a single namespace, rather than the c++ approach of each class having its own namespace within the object. This should work fine for most things.

initialize
Override this in each derived class to provide class specific initialization. For example, initialize may put arrays into member variables that need them. MRP::BaseClass will not complain if you do not provied an initialize function. It will assume that it is not needed.

clone
Returns a new object that is a clone of this one. Currently it copies scalars and references and then blesses the new object into the same class as the cloned object. This is a shallow copy by default. Invoke it will a true argument and it will do a deep copy, calling clone for all objects it can. If you invoke clone with shallow then you force a shallow copy. Override this in a base class if this is not the behavure you want. For example, my MRP::Document objects return themselves when you call clone, so that only one object exists for each directory being documented.

_PrintMembers
This function is provided for debugging. It returns a string representaion of your object model, including members, methods and package variables.

check4Clashes
Call this function after you have defined your classes interface. It does the following:

The template for the automaticaly generated code can be over-ridden in any subclass to give more precise functionality. For example, in MRP::Matrix, the delegation funciton is replaced with one that systematicaly modifies the argumet list. This is a painless operation under this system.


package::memberAccess

When the class is created, MRP::BaseClass creates a package called class::memberAccess. This package contains the member access functions. MRP::BaseClass then makes class::memberAccess the first parent in @class::ISA. Thus, you can do things like this:

    package myPath;
    ...
    %fields = (path => []);
    myPath->check4Clashes();
    ...
    sub path {
        my $self = shift;
        my @path = $self->SUPER::path;
        return join "/", @path;
    }

Because the member access functions are in a seperate package, you can still create functions of the same name in your package without 'loosing' the member access. Cool!


Other usefull functions

These functons are very usefull, but are more aprpreate to packages than members. They all work regardless of whether invoked as a class or object method.

Class variables
_ISA
In a scalar context, this function returns a reference to the ISA array for the package. In an array context it returns the contence of ISA for the package.

_fields
In a scalar context, this function returns a reference to the fields hash for the package. In an array context it returns the contence of fields for the package as a list of key/value pairs.

Text Formatting
_pretyArray
This function takes either an array or a reference to an array. It returns a string representation of the array.

_pretyHash
This function takes a leader string followed by either a hash or a reference to a hash. The leader sequene is prepended to each line of text. The function returns a single string representing the hash contence.


Inheriting from an MRP::BaseClass-derived object

This is as simple as extending MRP::BaseClass. Optionaly define %fields, @public_vars and @defaults, write an initialize function, and then call check4Clashes. Then in the new function, get an object for the imediate base class and call rebless for it.

 package myBox;
 use strict;
 use vars qw(@INC, %fields, @defaults);
 use myGraphable;
 BEGIN {
   @ISA = qw(myGraphable);
   %fields = (
     'x'=>undef, 'y'=>undef, 'w'=>undef, 'h'=>undef
   );
   @defaults = qw($color);
   myBox->check4clashes();
 }
 sub new {
   my $class = shift;
   my $self = new myGraphable;
   $self->rebless($class);
   return $self;
 }
 ... everything else goes here ...


Multiple inheritance

Much of the strife of multiple inheritance is taken care of for you. Simply use the multi-argument form of rebless to create the new object.

 package multiple;
 use myGlyph;
 use myPersistant;
 @ISA = qw(myGlyph myPersistant);
 sub new {
   my $class = shift;
   my $glyph = new myGlyph;
   my $persistant = new myPersistant;
   my $self = multiple->rebless($glyph,$persistant);
   return $self;
 }

... everything else here ...

Of course, you could include %fields, @public_vars and @defaults to give this package extra behaviour, and you may wish to define initialize.


Multiple inheritance involving non-MRP::BaseClass parents

check4Clashes stops searching a root of an inheritance hierachy when it can find no baseclasses that support _containsFields. This section describes how to implement _containsFields so that you can add your classes into this framework if you wish to. The premice is that you will only allow people to access member variables outside of the package through access functions, and that your stoorage method doesn't clash with mine.

_containsFields is responsible for comparing a classes fields with the set in the calling package. The MRP::BaseClass implimentation will work for cases where all of the classes that contribute fields are derived from MRP::BaseClass, and relies on my use of the %fields hash.

_containsFields assumes that the first argument is the package that it is being called in. The following arguments are taken to be a list of fields which to check are not found in members of the current package.

It should return either undef or a reference to an array of name clashes in the format package::variable. These clashes should be the combination of classes with this package and with all of the base classes.

So it would look something like _containsFields { my $class = shift; my @toCheck = @_;

    foreach @toCheck {
        check that they are not in me.
          If they are, add them to the list of clashes to return.
    }
    add all base class clashes to your list of clashes
    if there were name clashes return a reference to them
    otherwise return undef
  }

It is as simple as that. Currently, member variables are contained as keys within the $self hash reference. I provide access functions for all of the keys mentioned by name in %package::fields when check4Clashes is called. If anybody adds keys afterwards, member access functions are not generated for them. I can not be bothered to create a public:protected:private system. In the pod I just don't document private stuff.


Modifying the code generated for variable access and delegation support

Here are the parameters that are passed into the code-generating functions.

thingy
The class (possibly an object?) that the function is being generated for. You can use this to access other functions.

memberPackage
The package that the function must be compiled into. Normaly you would include package $memberPacage as the first line of the text.

delegate
The name of the field that is acting as a delegate

interface
An MRP::Interface object - the generated code must ensure that objects implement the correct interface before setting the field.

functions
The name of the function that the delegate is providing
name
The name that you must give the function

item
The name of the field that this function must provide access to.

Here are the funcitons themselves.

_delegateFunc($thingy, $memberPackage, $delegate, $function)
_delegateAccess($thingy, $memberPackage, $item, $interface, $interface.....)
_fieldScalarFunc($thingy, $memberPackage, $name, $interface, $item)
_packageScalarFunc($thingy, $memberPackage, $name, $item)
_fieldArrayFunc($thingy, $memberPackage, $name, $item)
_packageArrayFunc($thingy, $memberPackage, $name, $item)
_fieldHashFunc($thingy, $memberPackage, $name, $item)
_packageHashFunc($thingy, $memberPackage, $name, $item)
_packageAndField($thingy,$memberPackage,$item,$fieldFunc,$packageFunc)
Handles the case when you have a static variable and a field of the same name

_defaultField($thingy,$memberPackage,$item,$fieldFunc,$packageFunc)
Handles the case when you have a static variable that provides the default value for a field


AUTHOR

Matthew Pocock mrp@sanger.ac.uk

 MRP::BaseClass - My base class object