You've just made a Tie::ArrayXYZ module that ties arrays to your little XYZ protocol. It's great, but you need an extra method to your class. But, you can't call the method with the tied array, you have to call it with the object under the tied array. This results in ICKY code like:
tie @array, 'Tie::ArrayXYZ'; tied(@array)->method(@args);
or, if you've been prudent:
$obj = tie @array, 'Tie::ArrayXYZ'; $obj->method(@args);
But this is a primary flaw in the design of a tied class -- the fact that there is an object behind the variable should be hidden. This means that your user should be allowed to use the extra "methods" as regular functions.
method(@array, @args);
But how do you implement that? Well, you'll need to export "method" to the calling package, and prototype it to take the proper type as the first arg, and then do error-checking to make sure the first arg is actually tied, and that the object it is tied to inherits from your class.

Geez. Maybe that's transparent to the user, but what about the poor coder?

Enter my newest creation, which will probably appear on CPAN in the near future. It allows you to define additional methods on your tied objects as though they were ordinary ones, but then it exports them to the caller, and does all of the above, automatically. Watch:

package Tie::ArrayXYZ; use Tie::Functions qw( @ changeXYZ ); # ... sub CHANGEXYZ { my ($obj, $new_xyz) = @_; $obj->{XYZ} = $new_xyz; } 1;
That's all you need to do. Now, your user's code changes from:
use Tie::ArrayXYZ; tie @array, 'Tie::ArrayXYZ', $some_xyz; # ... tied(@array)->CHANGEXYZ($new_xyz);
to
use Tie::ArrayXYZ; tie @array, 'Tie::ArrayXYZ', $some_xyz; # ... changeXYZ(@array,$new_xyz);
Notice anything different? It looks like a regular function call, but here's what changeXYZ() looks like:
sub main::changeXYZ (\@@) { my $arg = shift; my $obj = tied @$arg; Carp::croak( "First arg to main::changeXYZ must be array tied to Tie::ArrayXYZ" ) unless defined($obj) and $obj->isa("Tie::ArrayXYZ"); return $obj->CHANGEXYZ(@_); }
It's dynamically generated. The name of the class, the name of the function, and the fact that we're expecting an array, are all determined from the use Tie::Functions line:
use Tie::Functions VARTYPE, FUNCTIONS;
The variable type ($, @, %, or *) determines the prototype and dereferencing. The functions are the representation of the method names you want the user to see. Currently, they must be case-insensitively identical to the method name, and the method name must be in all caps. This explains the correlation between 'changeXYZ' on the user's side, and 'CHANGEXYZ' on the module's side. The prototype of the function created also has a '@' as the secondary term, meaning that there can be any number of arguments after the required one. There might be plans to offer a more extensible prototyping system to the coder.

I hope this seems useful to someone. Here's the code:

package Tie::Functions; use Carp; my %trans = qw( $ scalar @ array % hash * glob ); sub import { shift; my $type = shift; my $pkg = caller(0); my $ppkg = caller(1); croak "Type must be '\$', '\@', '%', '*', not '$type'." unless $type eq '$' or $type eq '@' or $type eq '%' or $type eq '* +'; eval qq[ sub ${ppkg}::$_ (\\$type\@) { my \$arg = shift; my \$obj = tied $type\$arg; Carp::croak( "First arg to ${ppkg}::$_ must be $trans{$type} tied to $pkg" ) unless defined(\$obj) and \$obj->isa("$pkg"); return \$obj->\U$_\E(\@_); } ] for @_; } 1;

_____________________________________________________
Jeff[japhy]Pinyan: Perl, regex, and perl hacker.
s++=END;++y(;-P)}y js++=;shajsj<++y(p-q)}?print:??;

Replies are listed 'Best First'.
Re: Tied variables and functions for them
by theorbtwo (Prior) on Oct 03, 2001 at 08:58 UTC

    Hm. I like your idea, in some respects, but think the implementation sucks. The idea of exporting functions has no place mixing with object-orented programming, and tied stuff is the ultimate in OOP. (Note: this can either imply that I like both or hate both. That's quite on purpose.)

    The way you do this makes Tie::ArrayXYZ more difficult to subclass properly (perhaps not; I havn't looked that deeply. You use isa, which is a good sign), but more importantly, it makes it impossible to name your functions reasonably, since you can't have a methodfunction for the tied object and have a normal function (or even a mehtodfunction of a different tied object) at the same time, since your screwing with your users' namespace. IMHO, what you should be doing is making @tiedarray->mehtod DWIM, which would probably require a METHODCALL tie function.

    BTW, rereading this after preview before submit, I noticed that it sounds rather harsh. I didn't mean for it to come out that way, it's just been that kind of qw(day, month, year, lifetime)[rand 3].

    -- 
    James Mastros, AIM:theorbtwo, PM:theorbtwo, EMAIL:theorb@iname.com
Re: Tied variables and functions for them
by pixel (Scribe) on Oct 03, 2001 at 12:22 UTC

    I seem to remember a talk that Damian gave where he suggested that the Attributes::Handlers module could be used to simplify the interface to tied objects. I haven't looked into this yet, but it might be worth investigating.

    Blessed Be
    The Pixel

Re: Tied variables and functions for them
by dragonchild (Archbishop) on Oct 03, 2001 at 16:55 UTC
    Neat idea, japhy! However, I do have a design question.

    I always thought that the idea of tie-ing was to allow for coders to override the non-function accesses to the basic datatypes like array or hash. This way, neat things happen, but you do your normal stuff like $foo3 or $bar{baz}. I guess that I don't really understand why you would want to add another function to the standard datatypes. My thought would be that you should rethink your design in terms of a standard object instead of a tied object...

    ------
    We are the carpenters and bricklayers of the Information Age.

    Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

      I disagree. Tied variables are a great way to create objects that look and feel like standard Perl variables but that change their behaviours in interesting and useful ways. It's true that most of the time you'll want people to use the "traditional" interfaces, but there will be a small number of cases where the things you want to do fall outside of that interface. I don't see that as an argument for throwing away the simpler interface completely.

      Did you see my recent article at perl.com about tied hashes? There's an example in there of a hash with a fixed set of keys. When you tie the hash, you pass it a list of the valid keys. Most of the time you can then use the tied object as a normal hash. It's possible, however, that you might want to change the set of valid keys at some point in the application. There's no way to achieve that with the hash interface so you need to invent new object methods (note: Tie::Hash:FixedKeys doesn't do this yet, but I've been thinking of adding it). Most people will never need to use these functions, so I really don't want to make it harder for them to use the object by recasting it as a more traditional object class.

      --
      <http://www.dave.org.uk>

      "The first rule of Perl club is you don't talk about Perl club."

Re (tilly) 1: Tied variables and functions for them
by tilly (Archbishop) on Oct 03, 2001 at 22:44 UTC
    One big piece of advice. If you do this, I would want to see the exporting be controllable from user code. For exactly the same reason that Exporter says to use @EXPORT_OK instead of @EXPORT, and why it is unfortunate that some of the classes in the IO* hierarchy did an @EXPORT of huge lists of thing. (An export that can never now be changed because of backwards compatibility. Ick.)

    And a comment. The fact that a native data-type is not easily tied and then extended is one of the things I dislike in Perl, and is a necessary result of the decision to embed the type system in the syntax. (Don't get me wrong. I like Perl. But nobody can criticize like family, if you know what I mean.) I far prefer the approach that is being taken in Ruby of making all of the native data types objects, and then providing mix-ins which make it easy to produce your own classes that have all of the methods to work where a native datatype does.

    So you just create a class, create the same methods a tie would need, and then import a mixin, and your class now looks like a native data type. Now add more methods at will. You now have all of the power and flexibility of tie, with all of the extensibility of an object, and without having to build any special support for it into the language.

    tie is, after all, nothing more than a hack to make an object look like a native data type. Just make your native datatypes be objects, and you don't need the hack. Not needing the hack means that you don't get the bugs associated with having a hack like that. (Perl's tie implementation has a lot of bugs. For a random instance it does not cooperate with local. For others see bug reports by people like danger and tye.)

      Just as a quick note, this does hearken back to Why perl is more OO than C++ or Java....

      ------
      We are the carpenters and bricklayers of the Information Age.

      Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.