tags:

views:

205

answers:

2

I've just started to learn about tie. I have a class named Link which I would like to do the following thing:

  • if fetched, return the link's address
  • if stored, store the new address
  • be able to call methods on it

So far, my code is :


package Link;

sub FETCH {
    my $this = shift;
    return $this->{"site"};
}

sub STORE {
    my ($self,$site) = @_;
    $self->{"site"}   = $site;
}

sub print_method {
    my $self = shift;
    print $self->{"site"};
}

sub TIESCALAR {
    my $class = shift;
    my $link  = shift;
    my $this  = {};
    bless($this,$class);
    $this->{"site"} = $link;
    return $this;
}

1;

And the code I'm using to check the functionality is:


use Link;

tie my $var,"Link","http://somesite.com";
$var->print_method;

When ran, the script will terminate with the following error: Can't call method "print_method" without a package or object reference at tietest.pl line 4..

If I understand its message correctly, $var->print_method resolves to some string upon which the method print_method is called. How could I benefit from tie, but also use the variable as an object?

EDIT: after experimenting a bit,I found out that if I return $self on fetch , I can call the methods , however , fetch won't return the address .

EDIT 2:the perl monks supplied me the solution : tied . tied will return a reference to the object VARIABLE .

By combining tied with my methods , I can accomplish everything I wanted .

+8  A: 

I suggest making a normal Perl object and then overloading stringification. You lose the ability to store a value through assignment, but retain the ability to get the value out by printing the object. Once you start wanting to call methods directly, an object is probably what you want.

package Link;

use strict;
use Carp;

use overload
(
  '""'      => sub { shift->site },
   fallback => 1,
);

sub new 
{
  my $class = shift;

  my $self = bless {}, $class;

  if(@_)
  {
    if(@_ == 1)
    {
      $self->{'site'} = shift;
    }
    else { croak "$class->new() expects a single URL argument" }
  }

  return $self;
}

sub site
{
  my $self = shift;
  $self->{'site'} = shift  if(@_);
  return $self->{'site'};
}

sub print_method
{
  my $self = shift;
  print $self->site, "\n";
}

1;

Example usage:

use Link;

my $link = Link->new('http://somesite.com');

print $link, "\n";   # http://somesite.com
$link->print_method; # http://somesite.com

If you really, really want assignment to work too, you can combine a normal object with overloaded stringification (Link, above) with tie:

package LinkTie;

use strict;
use Link;

sub FETCH
{
  my $this = shift;
  return $this->{'link'};
}

sub STORE
{
  my($self, $site) = @_;
  $self->{'link'}->site($site);
  return $site;
}

# XXX: You could generalize this delegation with Class::Delegation or similar
sub print_method
{
  my $self = shift;
  print $self->{'link'}->print_method;
}

sub TIESCALAR
{
  my $class = shift;
  my $self = bless {}, $class;
  $self->{'link'} = Link->new(@_);
  return $self;
}

1;

Example usage:

tie my $link,'LinkTie','http://somesite.com';
print $link, "\n";   # http://somesite.com
$link->print_method; # http://somesite.com

$link = 'http://othersite.com';

print $link, "\n";   # http://othersite.com
$link->print_method; # http://othersite.com

This is all quite hideous and a long way to go just to get the dubious ability to assign to something that you can also call methods on and also print as-is. A standard URI object with stringification is probably a better bet.

John Siracusa
+1 no reason to tie if all you want to do is stringify.
Axeman
yeah,but assigning to it , won't have the effect I'm looking for. Is what I asked impossible ?
Geo
+9  A: 

Tie is the wrong tool for this job. You use ties when you want the same interface as normal data types but want to customize how the operations do their work. Since you want to access and store a string just like a scalar already does, tie doesn't do anything for you.

It looks like you want the URI module, or a subclass of it, and perhaps some overloading.

If you really need to do this, you need to use the right variable. The tie hooks up the variable you specify to the class you specify, but it's still a normal scalar (and not a reference). You have to use the object it returns if you want to call methods:

my $secret_object = tie my($normal_scalar), 'Tie::Class', @args;
$secret_object->print_method;

You can also get the secret object if you only have the tied scalar:

my $secret_object = tied $normal_scalar;

I have an entire chapter on tie in Mastering Perl.

brian d foy