NAME
Lexical::Attributes - Proper encapsulation
SYNOPSIS
use Lexical::Attributes;
has $.scalar;
has $.key ro;
has (@.array, %.hash) is rw;
sub method {
$self -> another_method;
print $.scalar;
}
DESCRIPTION
NOTE: This is experimental software! Certain thing will change, specially if they are marked FIXME or mentioned on the TODO list.
This module was created out of frustration with Perl's default OO mechanism, which doesn't offer good data encapsulation. I designed the technique of Inside-Out Objects several years ago, but I was not really satisfied with it, as it still required a lot of typing. This module uses a source filter to hide the details of the Inside-Out technique from the user.
Attributes, the variables that belong to an object, are stored in lexical hashes, instead of piggy-backing on the reference that makes the object. The lexical hashes, one for each attribute, are indexed using the object. However, the details of this technique are hidden behind a source filter. Instead, attributes are declared in a similar way as lexical variables. Except that instead of my
, a Perl6 keyword, has
is used. Another thing is borrowed from Perl6, and that's the second sigil. Attributes have a dot separating the sigil from the name of attribute.
Attributes
To declare an attribute, use the Perl6 keyword has
. The simplest way to declare an attribute is:
has $.colour; # Gives the object a 'colour' attribute.
Now your object has an attribute colour. Note the way the attribute is written, in a Perl6 style, it has the sigil (a $
), a period, and then the attribute name. Attribute names are strings of letters, digits and underscores, and cannot start with a digit. Attribute names are case-sensitive. You can use this attribute in the same way as a normal Perl scalar (except for interpolation). Here's a sub that prints out the colour of the object:
sub print_colour {
print $.colour;
}
Array and hash attributes work in a similar way:
has @.array; # Gives the object an array attribute.
has %.hash; # Gives the object a hash attribute.
And you can use them in a similar way as you can with "normal" Perl variables:
sub first_element {
return $.array [0];
}
sub pop_element {
return pop @.array;
}
sub last_index {
return $#.array;
}
sub gimme_key {
my $key = shift;
return $.hash {$key};
}
sub gimme_all_keys {
return keys %.hash;
}
Note however that you cannot have a scalar and an array (or a scalar and a hash, or an array and a hash) with the same name. Using both has $.key;
and has @.key;
will result in a warning, and the second (and third, fourth, etc) declaration of the attibute will be ignored.
If you have several attributes you want to declare, you can use has
in a similar way as you can my
and local
. has
takes a list as argument as well (parenthesis are required):
has ($.key1, @.key2, %.key3);
Note that the declaration, that is, the has
keyword followed by an attribute, or a list of attributes, can be followed by an optional trait (as discussed below), must be followed by a semi-colon (after optional whitespace). The following will not work:
has $.does_not_work = 1;
Traits
Since inspecting and setting the attributes of an object is a commonly requested action, it's possible to give the attributes traits that will achieve this. Traits are given by following the has
declaration with the keyword is
and the name of the trait (with the keyword being optional). Examples include:
has $.get_set is rw;
has $.get ro;
has (@.array, %.hash) is priv; # Trait applies to both attributes.
The following traits can be given:
pr
orpriv
-
Using
pr
orpriv
has the same effect as not giving any traits, no accessor for this attribute is generated. ro
-
This trait generates an accessor for the attribute. Only the value of the attribute can be fetched. The name of the accessor will be the same as the name of the attribute. Any parameters given to the accessor will be ignored.
package MyObject; use Lexical::Attributes; has $.colour is ro; sub new {bless [] => shift} sub some_sub { ... # Some code that sets the 'colour' attribute. } 1; __END__ # Main program my $obj = MyObject -> new; $obj -> some_sub (...); print $obj -> colour; # Prints the colour.
Accessors for arrays and hashes return the array or hash (flattened to a list).
rw
-
This traits generates an accessor that can be used to fetch the value, or the set the value. If no parameters are given, the value is fetched. If values are given, the attribute will be given these values. If multiple arguments are given to an accessor setting a scalar attribute, the attribute will be set to the first argument, other arguments are ignored.
package MyObject; use Lexical::Attributes; has ($.scalar, @.array) is rw; sub new {bless [] => shift; __END__ # Main program my $obj = MyObject -> new; $obj -> scalar ("hello, world"); $obj -> array (qw /tic tac toe/); print $obj -> scalar; # Prints 'hello, world'. print join "-" => $obj -> array; # Prints 'tic-tac-toe'.
FIXME: With this interface, it's not possible to set an array (or hash) to an empty set.
Methods
In order for the module to access the attributes, it needs access to the variable holding the current object. It will assume this variable is called $self
. This is not likely to be a problem, as it seems to be quite common to name the variable holding the current object $self
.
To further add the programmer, every subroutine, with the exception of the ones listed below, will have my $self = shift;
added to the beginning of their body. This is what most OO modules start with anyway. Excluded are:
new
-
Subroutines named
new
are typically constructors - for those having a$self
doesn't make sense. _name
-
Subroutines whose name start with an underscore are considered private. In fact, they well could not be method, but an ordinary, class-level, subroutine.
{ something
-
Also, subroutines who have any non-white space after their opening brace and before the following newline will be left untouched. This allows you to flag a subroutine should be left as is with for instance, a comment character.
Examples:
# Don't need to declare $self.
sub my_method {
$.attribute + $self -> other_method;
}
# Return first argument, even if that's the current object
sub _private {
return $_ [0];
}
# $_ [0] is the class name, as 'new' subroutines are exempt.
sub new {
bless [] => shift;
}
Note that if you use a method that doesn't get the assignment to $self
added, and you use a $.attribute
type of attribute, you must put an assignment to $self
before the use of the attribute.
DESTROY
Since the attributes are stored in lexical hashes, attributes do not get garbage collected via a reference counting mechanism when the object goes out of scope. In order to clean up attribute data, action triggered by the call of DESTROY
is needed. Hence, this module will insert a DESTROY
subroutine, or modify an existing DESTROY
subroutine. There are three cases.
If there is no
DESTROY
subroutine found, aDESTROY
will be added. This new subroutine will clean up attribute data, and nothing else. It will not call aDESTROY
method in an inherited class. FIXME: CallSUPER::DESTROY
if there is one.There is a
DESTROY
subroutine, and it doesn't contain the token__DESTROY__
. Then it will put the code to clean up the attributes on the same line as the opening brace, assuming the current object to be in$_ [0]
. It won't modify@_
, and it won't create a$self
. FIXME: This should probably change.There's a
__DESTROY__
token. Then this token will be replaced by the code that cleans up the attributes. This is useful if you want to make use of the attributes inside aDESTROY
function -- without a__DESTROY__
token, the attributes will be cleaned up at the beginning of the__DESTROY__
function. Note that any__DESTROY__
will be replaced by attribute cleaning code Then this token will be replaced by the code that cleans up the attributes. This is useful if you want to make use of the attributes inside aDESTROY
function -- without a__DESTROY__
token, the attributes will be cleaned up at the beginning of the__DESTROY__
function. It is assumed that$self
exists at this point. (If the opening brace of the body is followed by just whitespace, $self will be auto-declared as mentioned above.) Note that any__DESTROY__
will be replaced by attribute cleaning code - even if placed inside another method. It's safe to use it inside strings though.sub DESTROY { ... do something with attributes ... __DESTROY___ ... attributes are now undefined ... }
Inheritance
Inheritance just works. Classes using this technique require nothing from their super class implementation, and demand nothing from the classes that will inherit them. Super classes can use this technique, or traditional hash based objects, or something else entirely. And it's the same for classes that will inherit our classes.
Interpolation
One cannot interpolate attributes. At least, not yet. This will be fixed.
Overloading
Overloading of objects should work in the same way as other types of objects.
TODO
- o
-
Attributes should interpolate.
- o
-
Rethink the generated methods for setting arrays and hashes. Not being able to set arrays or hashes to empty arrays or hashes is a real pain.
- o
-
If generating a
DESTROY
subroutine, check whether aDESTROY
subroutine is inherited, and call this subroutine if exists. - o
-
Modifying an existing
DESTROY
subroutine could be done better. - o
-
Compiling a module is slow. This is probably caused by FILTER_ONLY being slow.
- o
-
Consider more traits. Methods for pop/push/shift/unshift for arrays, and keys/values/each for hashes would be useful. So are getting/setting keys by index.
AUTHOR
Abigail, abigail@abigail.nl
HISTORY
$Log: Attributes.pm,v $
Revision 1.2 2005/03/03 00:58:40 abigail
Support for $#.array
Revision 1.1 2005/02/25 00:24:02 abigail
First checkin
LICENSE
This program is copyright 2004 - 2005 by Abigail.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
INSTALLATION
To install this module type the following:
perl Makefile.PL
make
make test
make install