Perl Objects

P

Paul

Hi all:

I was wondering if someone could help me find a *better* or more
*correct* solution to my problem.

The problem:

I have a module that parses XML data using XML::parser. This module
is written in Object oriented fashion and defines the object in the
normal way...

sub new {
my $invocant = shift;
my $class = ref($invocant) || $invocant;

# No args by default, args is an array ref
$self = { acode => '',
file => '',
@_
};

bless $self, $class;
return $self;
}

THe instance method parseTIGRFile is called to process the XML data
file. Within this method, XML::parser is called and a handler is
passed to deal with XML start tags (actually there are 3 handlers, but
the question/answer is the same for all 3 handlers)....

sub parseTIGRFILE {
my $self = shift;
......
$parser = new XML::parser(Handlers => { Start => \&handle_start,
End => \&handle_end,
Char => \&handle_char,
} );
......
}

sub handle_start {
my ($expat, $elem, @atvals)= @_;
......
}

Here is the problem... the handlers cannot not take arguments (unless
I am wrong about this... ???), so I cannot pass $self to the handler.
While the data is being parsed, I want it stored in the object's
instance variables $self->{MY_VAR}, but the handler doesn't have any
way to *see* $self.

My current solution... I have defined $self globally.
my $self;
sub new {
....
bless $self, $class;
return $self;
}

Since the parseTIGRFile method is an instance method, this
appropriately sets the instance variables (I have tested this).
However, I fear there may be a consequence to globalizing $self that I
am unaware of.

So... can anyone tell me:
1. Is my current solution acceptable?
2. Is there a better alternative?

Thanks in advance for your help... and please let me know if more
information is required.

Paul
 
B

Bart Lateur

Paul wrote:

[problem description snipped, as it's quite long]
So... can anyone tell me:
1. Is my current solution acceptable?

Only if you ever use only one instance of this module at a time. That
doesn't agree with the philosophy of OO.
2. Is there a better alternative?

I can think of a few... the safest would be to use closures for the
handlers. Let me show you how:

sub new {
my $invocant = shift;
my $class = ref($invocant) || $invocant;

# No args by default, args is an array ref
$self = bless { acode => '',
file => '',
@_
}, $class;
$self->{handlers} = {
Start => sub {
# normal handle_start sub body. You can now access $self
...
},
End => sub {
# idem for handle_end
...
},
Char => sub {
# idem for handle_char
...
}
};
return $self;
}


and to invoke the parser, use these values in %{$self->{handlers}} for
XML::parser.

A problem is that now you have circular references: the handler subs
refer to $self, and self refers to the handlers. You need an explicit
method to destroy these circles, by undeffing $self->{handlers} for
example, before you let got of the object.

A way to avoid that, is to use weak references, see WeakRef or
Scalar::Util on CPAN. Only, it's not clear to me how or if that is
possible...

I have an idea for weak referencing $self inside the subs, but I'm not
too sure it's actually going to work. It is that you make a copy of
$self in new(), make one of the two a weak reference, and return the
other. If the other is deleted, the subs will go too. I would hope that
might work.

Another approach: the handlers when called, get a reference to their
parser. You could put $self into a field of that object, and preferably
make it a weak reference.

I thought I knew of one more, but I seem to have forgotten it. Oh well.
 
D

Dave Weaver

sub parseTIGRFILE {
my $self = shift;
......
$parser = new XML::parser(Handlers => { Start => \&handle_start,
End => \&handle_end,
Char => \&handle_char,
} );
......
}

sub handle_start {
my ($expat, $elem, @atvals)= @_;
......
}

Here is the problem... the handlers cannot not take arguments (unless
I am wrong about this... ???), so I cannot pass $self to the handler.
While the data is being parsed, I want it stored in the object's
instance variables $self->{MY_VAR}, but the handler doesn't have any
way to *see* $self.

Use closures:

[untested]

$parser = new XML::parser(Handlers => {
Start => sub { $self->handle_start( @_ ) },
End => sub { $self->handle_end ( @_ ) },
Char => sub { $self->handle_char ( @_ ) },
} );
...

sub handle_start {
my ($self, $expat, $elem, @atvals) = @_;
...
}
 
P

Paul

Thanks Bart. I didn't think my solution agreed with OOP philosophy.
As a Java programmer, I sometimes have a little trouble with OOP in
Perl!

I implemented your first solution (using closures). This seems to be
working quite well. To break the circular reference I simply added a
DESTROY method that undefined the handlers (I think/hope this was
enough).

sub DESTROY {
my $self = shift;
$self->{handlers} = undef;
}

Thanks again for your response, it was quite helpful.

Paul
 
P

Paul

Hi Dave,

Thanks! I implemented and tested this out... works like a charm!
Use closures:

[untested]

$parser = new XML::parser(Handlers => {
Start => sub { $self->handle_start( @_ ) },
End => sub { $self->handle_end ( @_ ) },
Char => sub { $self->handle_char ( @_ ) },
} );
...

sub handle_start {
my ($self, $expat, $elem, @atvals) = @_;
...
}

Paul
 
S

Steve Grazzini

Paul said:
I implemented your first solution (using closures). This seems
to be working quite well. To break the circular reference I
simply added a DESTROY method that undefined the handlers (I
think/hope this was enough).

Actually, this will still leak:
sub DESTROY {
my $self = shift;
$self->{handlers} = undef;
}

since DESTROY won't be called while the circular reference
exists. That might not matter in a short-running script, but
if you need the object to be destroyed earlier, use a weak
reference as Bart suggested.

use Scalar::Util qw(weaken);

sub new {
my $class = shift;
my $self = {};

# add closures referring to $self

my $ref = $self;
weaken $self;
bless $ref, $class;
}
 
P

Paul

Thank you Steve... This program and the XML files are rather large, so
it is important to me to control memory leaks.

Thanks to everyone for the excellent information.

Paul
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,780
Messages
2,569,611
Members
45,280
Latest member
BGBBrock56

Latest Threads

Top