tphyahoo has asked for the wisdom of the Perl Monks concerning the following question:
Monks, I am tired of shooting myself in the foot with $hashref->{typo} type errors that OO methodology was supposed to prevent. I started to use fields; # damnit like nothingmuch said. But that generates warnings that the underlying pseudohash implementation will be removed from perl soon. This was supposed to be fixed, and is not supposed to matter, but it's taking a long time.
Instead of use::fields, Adrianh and Abigail advocate inside out objects: Class::InsideOut - yet another riff on inside out objects.. This is because, says adrianh on the damnit thread, with fields you have problems when you don't control the base class -- though I don't understand exactly what adrianh meant by that.
And oh, another way to do it, apparently, is Attribute::Property, as juerd advocates on the "damnit" conversation.
Well, I am not trying to stir up trouble here, or trolling for repeats of what was already said in the above conversations. But if anybody has anything to add, or guidance on what I should do to escape the $foot->{shooting} typos, please chime in.
For now I am using fields with deprecation warnings turned off. If this is bad... if this will cause baby mice to eat my children... or even if there is just an easier way that I could get used to before growing too attached to use::fields... tap on the door of my cell and point me to enlightment, and I will try and return the favor some day.
Cheers.
Re: Should I use Fields, InsideOuts, or Properties?
by BrowserUk (Patriarch) on Jul 06, 2005 at 11:13 UTC
|
A couple of options:
- Use blessed arrays and constants for your objects. Smaller, faster and safer.
- Initialise your blessed hashes with the required fields and set them readonly manually.
use Internals qw(SetReadOnly);;
$fred = bless {},'fred';;
@$fred{ qw[ jack john bill ] } = ();;
SetReadOnly $fred;;
print Dumper $fred;;
$VAR1 = bless( {
'john' => undef,
'jack' => undef,
'bill' => undef
}, 'fred' );
$fred->{bill} = 3;;
print Dumper $fred;;
$VAR1 = bless( {
'john' => undef,
'jack' => undef,
'bill' => 3
}, 'fred' );
$fred->{derf} = 'error';;
Attempt to access disallowed key 'derf' in a restricted hash at (eval
+11) line 1, <STDIN> line 9.
print Dumper $fred;;
$VAR1 = bless( {
'john' => undef,
'jack' => undef,
'bill' => 3
}, 'fred' );
Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
"Science is about questioning the status quo. Questioning authority".
The "good enough" maybe good enough for the now, and perfection maybe unobtainable, but that should not preclude us from striving for perfection, when time, circumstance or desire allow.
| [reply] [d/l] |
|
BTW, for those who don't know, use fields in 5.9 and up is basically the same as the second solution - it creates a hash with locked keys.
| [reply] [d/l] |
|
Thanks for mentioning this.
Reading the thread you linked to, adrianh commented "Once you start refactoring classes and moving object fields between classes, adding fields to classes, etc. I find array based objects becomes a complete PITA." and you seem to agree with him. So, I am reading your example as a way to do things very cleanly in some simple cases, but not as a replacement for fields, which, despite its annoyances, does play well with inheritance.
| [reply] |
|
It's true that for both methods you need to code your constructors with a view to inheritance, but you have to do that when using fields also.
For the manually lock hash, you just have to test whether you are constructing a new instance or adding to an existing class, and in the latter case, unlock the hash, add you fields and re-lock. I admit that doesn't provide the _\w* is private behaviour.
For the blessed arrays, inheritance is messy. Hence my agreement with adrianh.
Basically, I have still to find the perfect combination for OO in Perl, but I don't have cause to make a great deal of use of it.
Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
"Science is about questioning the status quo. Questioning authority".
The "good enough" maybe good enough for the now, and perfection maybe unobtainable, but that should not preclude us from striving for perfection, when time, circumstance or desire allow.
| [reply] |
Re: Should I use Fields, InsideOuts, or Properties?
by adrianh (Chancellor) on Jul 06, 2005 at 12:42 UTC
|
Instead of use::fields, Adrianh and Abigail advocate inside out objects: Class::InsideOut - yet another riff on inside out objects.
You might want to look at Abigail's Lexical::Attributes which wraps up inside-out objects in a nice Perl 6-ish source filter.
This is because, says adrianh on the damnit thread, with fields you have problems when you don't control the base class -- though I don't understand exactly what adrianh meant by that
Hopefully this makes it clear :-)
And oh, another way to do it, apparently, is Attribute::Property, as juerd advocates on the use fields damnit conversation.
Any accessor generating system, like A::P or Class::MakeMethods will help if you have the discipline to only access the object by method.
You'll only get runtime errors on typos though, not compile-time (as you do with inside-out objects).
But if anybody has anything to add, or guidance on what I should do to escape the $foot->{shooting} typos, please chime in.
Get better at typing? :-)
Another approach would be to try a development technique that makes typos like this more immediately obvious. I find that doing TDD means that mistakes like this show up immediately in a failing test, making the pain levels drop to about zero for this kind of "invisible" mistake.
| [reply] |
Re: Should I use Fields, InsideOuts, or Properties?
by larard (Monk) on Jul 06, 2005 at 14:25 UTC
|
Not to add to your choices, but whenever I am faced with this I use Hash::Util's lock_keys method as follows....
package Dog;
use Hash::Util qw(lock_keys);
use Data::Dumper;
sub new
{
my $pkg = shift;
my $ref = {};
bless $ref, $pkg;
lock_keys(%$ref,qw(intelligence));
$ref->{intelligence} = shift;
$ref;
}
sub is_stupid {
my $self = shift;
return $self->{inteligence} < 3;
}
my $lassie = Dog->new(10);
print Dumper($lassie);
print "Lassie is ", $lassie->is_stupid ? "dumb" : "smart";
In the case of subclassing this, it should be easy enough to call unlock_keys, do what you need, and then lock it again.
This novice wasn't aware of the other methods you mention for achieving this, and would be interested in hearing what other monks might say of the methods described here.
Of course this only gives one runtime checks, but those are sufficient for my purposes. | [reply] [d/l] |
Re: Should I use Fields, InsideOuts, or Properties?
by hakkr (Chaplain) on Jul 06, 2005 at 14:11 UTC
|
Encapsulation of an object means you should never access attributes directly but use accessors and mutators(getters and setters).
One exceedling cool way to do this which I think I can credit to someone from this site(please correct me if not) is with Autoload.
If you put the following code into your class it automagically creates get_attributename() and set_attributename() methods dynamically at runtime. So you dont have to manually write 2 methods for every attribute. If you make a typo when accessing an attribute you get a nice error message saying that no method exists.
sub AUTOLOAD {
# AUTOLOAD object accessor/mutator method
no strict "refs"; # allow me access to the symbol table
my ($self,$newval) = @_;
return if $AUTOLOAD =~ /::DESTROY$/o; # let perl handle its own cl
+ean up
if ($AUTOLOAD =~/.*::get(_\w+)/ && $self->_accessible($1, 'read'))
+{
my $attr_name = $1;
*{$AUTOLOAD} = sub{ return $_[0]->{$attr_name}; }; # Creates a
+n encapsulated method in the symbol table
return $self->{$attr_name};
}
# determine set or get method
if ($AUTOLOAD =~/.*::set(_\w+)/ && $self->_accessible($1, 'write')
+){
my $attr_name = $1;
*{$AUTOLOAD} = sub{ return $_[0]->{$attr_name}=$_[1]; };
return $self->{$attr_name}=$newval;
}
# no method for this object attribute
die "No such method!: $AUTOLOAD";
}
| [reply] [d/l] |
|
Encapsulation of an object means you should never access attributes directly but use accessors and mutators(getters and setters).
This is only partially true. Encapsulation means that you have private data which is not accessible from the "outside" (or more specifically, you can control the accessibility). What you are doing with AUTOLOAD is no better then just accessing the underlying hash directly.
IMO accessors and mutators should only be created when a truly valid need exists for them. Objects are meant to be a collection of (encapsulated) data and behaviors, but if all your behaviors are simply accessors/mutators, then you might as well just have a struct/record-type.
| [reply] |
|
This reminds me of perltoot, which also discusses me autoload ways to accomplish accessor/mutator magic; much obliged, I had forgotten to look here.
FWIW, your example turns off strict, but in perltoot, seems to get the job done with use strict still on. Not sure if this is because of a difference in the examples.
| [reply] |
Re: Should I use Fields, InsideOuts, or Properties?
by dmitri (Priest) on Jul 06, 2005 at 14:55 UTC
|
$hashref->{typo}
do this:
use constant TYPO => 'typo';
$hashref->{(TYPO)}; # OK
$hashref->{(TIPO)}; # Compile-time error, just what you want
| [reply] [d/l] [select] |
|
That's (a) disgusting, and (b) largely useless, since it won't catch $hashref->{tipo};.
| [reply] [d/l] |
|
| [reply] |
Re: Should I use Fields, InsideOuts, or Properties?
by Smoothhound (Monk) on Jul 06, 2005 at 14:09 UTC
|
Monks, I am tired of shooting myself in the foot with $hashref->{typo} type errors that OO methodology was supposed to prevent
Although I'm missing the point slightly, my $hashref->{typo} style errors have greatly decreased since I discovered M-/ in emacs. :)
| [reply] [d/l] |
Re: Should I use Fields, InsideOuts, or Properties?
by mugwumpjism (Hermit) on Jul 06, 2005 at 22:24 UTC
|
As others may have pointed out, the problem with pseudohashes is that if you do ever want to override what happens when a property is retrieved or set, you need to use tie.
Class::Tangram has been designed with OO considerations like this in mind.
With Class::Tangram, accessing properties becomes method calls; you don't worry about spelling hash properties wrong, because when you define extra accessors and mutators, you may call the generated ones via ::SUPER;
package MyClass;
use base qw(Class::Tangram);
our $fields = { int => [ qw(a b c d e) ] };
sub set_b {
my $self = shift;
my $new_b = shift;
$self->SUPER::set_b($new_b + 3);
}
package main;
my $foo = MyClass->new( a => 1, b => 2 );
print $foo->a; # prints 1
print $foo->b; # prints 5
Note that the set_b method would have been called regardless of whether you called it directly, or used the alternate ways to set the attribute;
$foo->b(4); # note: prints warning
$foo->set_b(4);
This is all achieved without ugly AUTOLOAD kludges.
$h=$ENV{HOME};my@q=split/\n\n/,`cat $h/.quotes`;$s="$h/."
."signature";$t=`cat $s`;print$t,"\n",$q[rand($#q)],"\n";
| [reply] [d/l] [select] |
|
| [reply] |
|
Class::Tangram has been designed with OO considerations like this in mind.
I know practically nothing about Class::Tangram, but I have to say this: it's got to be the most ineptly named module in all of CPAN. The fact that anyone uses it at all, given its name, is a marvel, and an exceptionally effective endorsement. To paraphrase the Smucker's slogan: with a name like Tangram, it has to be good.
| [reply] |
Re: Should I use Fields, InsideOuts, or Properties?
by flogic (Acolyte) on Jul 06, 2005 at 18:58 UTC
|
I've my own backwards method around this problem. Basiaclly I have a module to create lvalue accessors for my attributes. So $foo->baris the same as $foo->{bar}.
Then if I need to, I have more code to make the scalar a tied value so I get runtime type checking. It's kinda cool but probably also fairly slow. With some work the same method could probably also handle public and private attributes. | [reply] [d/l] [select] |
|
|