Beefy Boxes and Bandwidth Generously Provided by pair Networks
The stupid question is the question not asked
 
PerlMonks  

tie for Perlish, encapsulated objects

by Roy Johnson (Monsignor)
on Nov 23, 2005 at 20:13 UTC ( [id://511231]=perlmeditation: print w/replies, xml ) Need Help??

In the thread 'Set' Method Return Value, I recommended that members that can be set and got should be accessed directly (or tied to hide the accessors), rather than having a method-based API to set and get them. This idea was downvoted (from what I surmise) because it exposes the implementation.

But it doesn't.

With tie, a hash is just an API. And it's a good API for any object that has settable-gettables, because it's familiar and can be used in all the ways that Perl users know and love (subject to any restrictions you program in, of course). It also encapsulates your object completely: all access is necessarily through the API.

That's it. What follows is some example code of minimal objects implemented in the getter-setter way, and then using a hash API. You can see that the code is very similar. Both implementations use an array as the actual base type.

Update: I should add that the code below is not intended to be a blueprint for writing classes. It is only to illustrate the notion that a hash can be an API rather than an implementation. There are a number of issues to address (pointed out by respondants already) to make a workable, general-purpose foundation.

use warnings; use strict; package MyThing; sub new { my $class = shift; bless [], $class; } sub set_foo { my ($self, $newfoo) = @_; $self->[0] = $newfoo; } sub get_foo { $_[0]->[0]; } package HashAPI; sub TIEHASH { my $class = shift; bless [], $class; } sub STORE { my ($self, $key, $val) = @_; unless ($key eq 'foo') { die "No $key member\n"; } $self->[0] = $val; } sub FETCH { my ($self, $key, $val) = @_; unless ($key eq 'foo') { die "No $key member\n"; } $self->[0]; } sub new { my $class = shift; tie my(%obj), $class; bless \%obj, $class; } package main; my $clunker = MyThing->new(); my $slick = HashAPI->new(); $clunker->set_foo('Some value'); print $clunker->get_foo, "\n"; $slick->{foo} = 'Some value'; print $slick->{foo}, "\n";

Caution: Contents may have been coded under pressure.

Replies are listed 'Best First'.
Re: tie for Perlish, encapsulated objects
by perrin (Chancellor) on Nov 24, 2005 at 02:04 UTC
    Sorry, but this is a bad idea. SPOPS does it this way, and it leads to a lot of confusion. People look at the code and say "Why are you accessing this object as a hash?" It's just a lot of needless indirection for people trying to maintain your code. It's also measurably slower than just calling accessors.
Re: tie for Perlish, encapsulated objects
by Zaxo (Archbishop) on Nov 23, 2005 at 20:48 UTC

    I mainly agree, tie is an excellent way to encapsulate data and complicated behaviors. The encapsulation is not as tight as you suggest, though. The tied call returns the underlying blessed object. No B&D OO in Perl!

    Your example is perhaps a little too simple. The hardwired key in your setter and getter has to be rewritten for each class you want. That could be generalized by allowing the constructor to accept a list of keys and then generate the FETCH and STORE methods as per-instance CODE refs. The class FETCH() and STORE() would then just call the instance's stored method.

    That begins to sound a lot like a pseudohash, but with a completely different implementation.

    The alternative is to make FETCH() virtual by declaring but not defining it. The user could then subclass your Tie:: class and define it there, or else define it externally.

    After Compline,
    Zaxo

Re: tie for Perlish, encapsulated objects
by jdhedden (Deacon) on Nov 23, 2005 at 21:36 UTC
    This results in an inconsistent interface combining hash access:
    $obj->{field}
    with object methods:
    $obj->action()

    Remember: There's always one more bug.
      There should be a difference between calling methods and using members, IMO. They're different. But this is a meditation about the fact that a hash can be API rather than an implementation detail. I did not intend it to be taken as yet another One True Way to implement objects, and I'm sorry that, judging from the replies, I seem to have given that impression.

      Caution: Contents may have been coded under pressure.
        There should be a difference between calling methods and using members

        Why do you say that? I have been going towards the opposite conclusion and say that a primary purpose of getters and setters and such is to hide or homogenize the distinction.

        Be well,
        rir

        Most OO design writing I've seen says that accessor methods are supposed to hide the existence of member variables, in case the implementation of the class changes.
Re: tie for Perlish, encapsulated objects
by radiantmatrix (Parson) on Nov 25, 2005 at 20:44 UTC

    This just seems like a fancy wrapper to $instance->{attribute} notation. Yeah, you can make assignments to more complicated things, but you can do that with overload as well. I guess I just don't get it.

    I've fallen in the habit of implementing this type of thing for set and get operations:

    package SomePackage; use base 'Class::Base'; ## super-simple 'new' sub new { my $self = shift; bless { one => 1, two => 2 }, $self; } sub set { my $self = shift; my $attrib = shift; return $self->error("No such attribute '$attrib'") unless exists $self->{$attrib} my $value = shift; # check to make sure we're setting the same kind of reference, if i +t matters if (ref $self->{$attrib} && ref $value ne ref $self->{$attrib}) { return $self->error("Reference types do not match for '$attrib' +set.") } $self->{$attrib} = $value; return 1; # true on success } sub get { my ($self, $attrib) = @_; return $self->error("Attribute '$attrib' doesn't exist") unless exists $self->{$attrib}; my $value = $self->{$attrib}; return $value; } 1;

    This means I can do $instance->set('one', 'Hey there!'); and $instance->get('two');. If certain attributes have special rules to validate data, I can deal with those by altering 'set' slightly. For example:

    # set from above gets renamed _set_DEFAULT sub set { my ($self, $attrib, $value) = @_; if ( $self->can('_set_'.$attrib) ) { my $result; eval "\$result = \$self->_set_$attrib($value)"; return $result; } else { return $self->_set_DEFAULT($attrib, $value); } }

    Something like that is much cleaner and clearer in the implmentation. I like the idea that when I see $instance->{attrib} = 2, I know the code is messing about with something advanced, while $instance->set('attrib', 2) is playing by the rules.

    Besides, with Perl6's advancements in lvalue subs, it will be possible to do something like $instance->accessor('attrib') = 2, and we both win, so I don't see much point in debating the tie methodology.

    <-radiant.matrix->
    A collection of thoughts and links from the minds of geeks
    The Code that can be seen is not the true Code
    "In any sufficiently large group of people, most are idiots" - Kaa's Law
Re: tie for Perlish, encapsulated objects
by Perl Mouse (Chaplain) on Nov 23, 2005 at 21:02 UTC
    How would you do inheritance using the tied hashes?
    Perl --((8:>*
Ties are can be very, very confusing...
by Anonymous Monk on Nov 25, 2005 at 20:42 UTC
    Suppose you have this code fragment:
    $x=$x+1;

    and you know that $x is a tied object. Quick, what does the code do?

    Who knows?

    All I know is, it will call the appropriate fetch and store functions in the tied package, and something will happen. Without reading backwards to find the tie command, it's not obvious which functions in which package are being called.

    When I find out code has tied objects, I always cringe inside, because I know I'll have to audit every single line for potential tied side effects to be assured that nothing stupid is happening. I've seen some badly written code where the tied variables are tied in completely different modules from the modules where they are used, so it's a nightmare trying to figure out exactly what is going on.

    I'd rather just see a normal function call interface; just explictly call the equivalents of $class->TIESCALAR(), $object->FETCH().

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://511231]
Approved by itub
Front-paged by Old_Gray_Bear
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others meditating upon the Monastery: (4)
As of 2024-04-19 14:09 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found