Overload object operation in Perl - perl

I want to use overloaded operators in a method which modifies an object. I also want to achieve it without duplicating the code.
To illustrate the problem, I will show a simplified version of what I am trying to do. In my original code, the add method overloads + and complicated_calculation method tries to update the object.
The add method creates a new Number object to avoid an expression like $n + 1 modifying the object.
package Number;
use overload
'0+' => 'get_value',
'+' => 'add';
sub new {
my ($class, $value) = #_;
my $self->{value} = $value;
return bless($self, $class);
}
sub get_value {
my ($self) = #_;
return $self->{value};
}
sub set_value {
my ($self, $value) = #_;
$self->{value} = $value;
}
# Actual class has more attributes and the logic of addition includes branches.
sub add {
my ($self, $other) = #_;
print "add $other\n";
return Number->new($self->get_value + $other);
}
sub complicated_calculation {
my ($self) = #_;
# Do something complicated.
$self += 10;
}
package main;
my $n = Number->new(1);
print $n + 1 . "\n";
$n++;
print $n . "\n";
$n->complicated_calculation;
print $n . "\n";
Will output
add 1
2
add 1
2
add 10
2
I want the result of complicated_calculation method (12) to be printed, but 2 is printed instead. The result of the complicated_calculation method is set to an object created by the add method, instead of to the object which called it.
I can make the complicated_calculation method update the object using an add_in_place method to add a number in-place, but this requires duplicated code in add and add_in_place which I was taught to avoid.
In the actual application the Number class will have many more attributes, and the code for addition will be much longer.
package Number;
use overload
'0+' => 'get_value',
'+' => 'add',
'+=' => 'add_in_place',
'fallback' => 1;
sub new {
my ($class, $value) = #_;
my $self->{value} = $value;
return bless($self, $class);
}
sub get_value {
my ($self) = #_;
return $self->{value};
}
sub set_value {
my ($self, $value) = #_;
$self->{value} = $value;
}
# Actual class has more attributes and the logic of addition includes branches.
sub add {
my ($self, $other) = #_;
print "add $other\n";
return Number->new($self->get_value + $other);
}
sub add_in_place {
my ($self, $other) = #_;
print "add_in_place $other\n";
$self->set_value($self->get_value + $other);
}
sub complicated_calculation {
my ($self) = #_;
# Do something complicated.
$self += 10;
}
package main;
my $n = Number->new(1);
print $n + 1 . "\n";
$n++;
print $n . "\n";
$n->complicated_calculation;
print $n . "\n";
Will output
add 1
2
add_in_place 1
2
add_in_place 10
12
I feel that there should be a better way and would like to have some advice from you guys.

First of all, you must always use strict and use warnings at the top of every Perl program file you write. This applies especially when you are asking for help with your code, as it is the first line of defence against bugs and really should be your first resort before troubling others.
This is happening because the add method is called to implement the += operator, which returns a new Number object as a result. That results in the value of $self within complicated_calculation being changed to refer to the new Number object that, correctly, has a value of 12. But the original value -- $n in the main code -- still points to an object with the value of 2.
To get it to work, you could arrange that complicated_calculation returns the new object, and the calling code assigns it to $n. Just changing that statement to
$n = $n->complicated_calculation
will get it working.
However, it is a little strange to write stuff like that as a method. The code in the Number class should be focused on making the object behave correctly, so all the methods should be operators. If you were writing complicated_calculation as a subroutine in the main package then you would be fine with
$n += 10;
print $n;
as the copying of $n would then work correctly and transparently. It is only when you are writing a method that reassigning $self makes no sense, because it then no longer refers to the object the calling code is using.
If you really consider complicated_calculation to be an operator, then it should mutate the object in-place rather than relying on overload to provide the mechanism. If you changed it to
sub complicated_calculation {
my ($self) = #_;
$self->{value} += 10;
}
then everything would work as it should.
Update
I strongly believe that you should write everything in terms of add_in_place, which should be a private method for use only internally by the class.
Both add and complicated_calculation can be very simply rewritten, and there is no longer any need to write $n = $n->complicated_calculation as the method modifies the object in-place.
This example code for the module demonstrates.
package Number;
use strict;
use warnings;
use 5.010;
use overload
'0+' => 'get_value',
'+' => 'add';
sub new {
my ($class, $value) = #_;
bless { value => $value };
}
sub get_value {
my ($self) = #_;
$self->{value};
}
sub set_value {
my ($self, $value) = #_;
$self->{value} = $value;
}
sub add {
my ($self, $other) = #_;
print "add $other\n";
Number->new($self->get_value)->add_in_place($other);
}
sub add_in_place {
my ($self, $other) = #_;
print "add_in_place $other\n";
$self->{value} += $other;
$self;
}
sub complicated_calculation {
my ($self) = #_;
$self->add_in_place(10);
}

Related

How to return two values in getter method in Perl?

use strict;
use warnings;
package LineSegment;
sub new
{
my $class = shift;
my ($ax, $ay, $bx, $by) = #_;
my $self = {"ax"=>$ax,
"ay"=>$ay,
"bx"=>$bx,
"by"=>$by,
};
bless ($self, $class);
return $self;
}
sub getA{
#Issue on get A
my $self = shift;
return ($self->{ax}, $self->{ay});
}
sub getB{
#Issue on get B
my $self = #_;
return ($self->{bx}, $self->{by});
}
sub setA{
#Can print correct value. Is the return statement where it goes wrong?
my($self, $ax, $ay) = #_;
$self->{ax} = $ax if defined($ax);
$self->{ay} = $ay if defined($ay);
print "Setting ax: $self->{ax}\n";
print "Setting ay: $self->{ay}\n";
return ($self->{ax}, $self->{ay});
}
sub setB{
#Can print correct value. Is the return statement where it goes wrong?
my($self, $bx, $by) = #_;
$self->{bx} = $bx if defined($bx);
$self->{by} = $by if defined($by);
return $self->{bx}, $self->{by};
}
1;
I am trying to create a class called LineSegment. ax and ay are a
point and so are bx and by. I cannot get getA or getB to return what I
want. They only return the second value, which would be ay for getA
and by for getB. I want it to return both values (ax, ay) or (bx,by).
How do I get it to do this? In my setA and setB methods, the values
will print. However, could I be returning them wrong in setA and setB?
Or does my problem lie in my getter methods?
Here is my main:
print "Starting Line Segment\n";
use LineSegment;
$line = new LineSegment(10,20,30,40);
$line->setA(15,10);
$a = $line->getA();
print "Point A is: $a\n";
Here is my Point class:
use strict;
use warnings;
#class name
package Point;
#constructor
sub new
{
my $class = shift;
my($x, $y) = #_;
my $self = {"x"=>$x,
"y"=>$y,
};
bless ($self, $class);
return $self;
}
sub getX{
my($self) = #_;
return $self->{x};
}
sub setX{
my ($self, $x) = #_;
$self->{x} = $x if defined($x);
return $self->{x};
}
sub setY{
my ($self, $y) = #_;
$self->{y} = $y if defined($y);
return $self->{y};
}
sub random{
my $self = shift;
my $range = 50;
$self->{x} = int(rand($range));
$self->{y} = int(rand($range));
return ($self->{x}, $self->{y});
}
1;
Updated main:
use strict;
use warnings;
use Point;
use LineSegment;
my $line = LineSegment->new(Point->new()->random, Point->new()->random);
my $pointA = $line->getA;
my $pointB = $line->getB;
printf "Point = (%d,%d)\n", $pointA->getX, $pointA->getY;
As Tanktalus has pointed out, you are returning a list of two values and expecting to be able to treat it as a single Point object. A list in scalar context evaluates to the last element of the list, so you are getting just the Y coordinate
I've written some functioning code below. One thing that may confuse you is the hash slice syntax #{$self}{qw/ _x _y /} = #_ which is the same as
$self->{_x} = $_[0];
$self->{_y} = $_[1];
You should remember to use strict and use warnings at the top of every Perl source file. You should also avoid using $a and $b as they are used internally by Perl. Longer, more descriptive identifiers are better anyway
If I alter your Point.pm so that its constructor takes parameters (I have also fixed your random method) like this
Point.pm
use strict;
use warnings 'all';
package Point;
sub new {
my $class = shift;
my $self = { };
#{$self}{qw/ _x _y /} = #_ if #_;
bless $self, $class;
}
sub getX{
my $self = shift;
return $self->{_x};
}
sub getY{
my $self = shift;
return $self->{_y};
}
sub setX {
my $self = shift;
$self->{_x} = $_[0] if #_;
return $self->{_x};
}
sub setY {
my $self = shift;
$self->{_y} = $_[0] if #_;
return $self->{_y};
}
use constant RANGE => 50;
sub random {
my $self = shift;
$self->{_x} = int rand RANGE;
$self->{_y} = int rand RANGE;
return $self;
}
1;
and write LineSegment.pm like this
LineSegment.pm
use strict;
use warnings 'all';
package LineSegment;
sub new {
my $class = shift;
my $self = { };
#{$self}{qw/ _pA _pB /} = #_ if #_;
bless $self, $class;
}
sub getA {
my $self = shift;
return $self->{_pA};
}
sub getB {
my $self = shift;
return $self->{_pB};
}
sub setA {
my $self = shift;
$self->{_pA} = $_[0] if #_;
return $self->{_pA};
}
sub setB {
my $self = shift;
$self->{_pB} = $_[0] if #_;
return $self->{_pB};
}
1;
then I can write a program which does what I think you want like this
main.pl
use strict;
use warnings 'all';
use Point;
use LineSegment;
my $line = new LineSegment(
Point->new(10, 20),
Point->new(30, 40),
);
$line->setA( Point->new(15, 10) );
my $point = $line->getA;
printf "Point = (%d,%d)\n",
$point->getX,
$point->getY;
output
Point = (15,10)
my ($ax, $ay) = $line->getA();
getA() is returning a list of variables, you need to receive it into a list of variables. An array would work as well, but this is probably clearer.
But that's not really what you want. What you want to do is to have a line segment be made up of two Point objects (which you may have to create as well), and each Point object store its own x and y coordinates. And then you can return the points as objects, and query their x and y coordinates, e.g.:
my $a_point = $line->getA();
print "Point A is (", $a_point->getX(), ",", $a_point->getY(), ")";
(You can also have the Point class override stringification, but I suspect that's more than you want to think about just yet.)
Apologies for not catching this the first time, but not only are single-letter variable names poor taste in general, $a and $b are particularly bad in perl because they're reserved for the sort function. So I've renamed it here.
With your update, your Point class is missing the getY method. Your main script becomes:
use strict;
use warnings;
use LineSegment;
print "Starting Line Segment\n";
my $line = new LineSegment(10,20,30,40);
$line->setA(15,10);
my $p = $line->getA();
print "Point A is: (", $p->getX(), ",", $p->getY(), ")\n";
and your LineSegment.pm becomes:
package LineSegment;
use strict;
use warnings;
use Point;
sub new
{
my $class = shift;
my #points;
if (#_ == 4)
{
#points = (
Point->new($_[0], $_[1]),
Point->new($_[2], $_[3]),
);
}
else
{
#points = #_;
}
my $self = \#points;
bless ($self, $class);
return $self;
}
sub getA{
#Issue on get A
my $self = shift;
return $self->[0];
}
sub getB{
#Issue on get B
my $self = shift;
return $self->[1];
}
sub setA{
#Can print correct value. Is the return statement where it goes wrong?
my $self = shift;
my $point = $_[0];
if (#_ > 1)
{
$point = Point->new(#_);
}
$self->[0] = $point;
}
sub setB{
my $self = shift;
my $point = $_[0];
if (#_ > 1)
{
$point = Point->new(#_);
}
$self->[1] = $point;
}
1;
This may be a bit overkill, but the right answer is to only pass in/around Point objects in your LineSegment, and let the caller create the Point objects instead of massaging them in here. In my experience, this makes the whole thing clearer.
You have complete answers by Borodin and Tanktalus
showing how to write this class, with other comments. They also emphasize that a segment class should fully utilize a point class.
This is an important point. We encapsulate a certain aspect of our problem in a class. Then we want to use that class for other aspects of the problem, and this is crucial in the object-oriented approach. It usually requires iterations in design and coding, to get those classes right.
This post demonstrates the process by adding a method for the length of a segment, what prompts addition of other methods. I also add a few other pieces to your classes
A couple of utility methods are added in the Point class that are helpful for the length method, and that belong there in general. This is typical -- we desire new functionality and realize that the other classes should provide a part (or all) of it.
Defaults are added to the constructors. Once new is called the objects should be initialized and ready to go, if possible. Your Point::random method is used for this.
A setter and getter are combined in one method, which sets data when called with parameters
Some comments follow the code.
Point.pm
package Point;
use strict;
use warnings;
sub new {
my $class = shift;
my $self = { };
bless $self, $class; # now we can call methods on $self
if (#_) {
#{$self}{qw(_x _y)} = #_; # initialize via parameters
} else {
$self->random(); # initialize using random()
}
return $self;
}
sub x {
my $self = shift;
$self->{_x} = $_[0] if $_[0]; # set if parameter was passed
return $self->{_x};
}
sub y {
my $self = shift;
$self->{_y} = $_[0] if $_[0];
return $self->{_y};
}
sub coords {
my $self = shift;
#{$self}{qw(_x _y)} = #_ if #_;
return $self->{_x}, $self->{_y};
}
sub distance {
my ($self, $pt) = #_;
my ($x1, $y1) = $self->coords();
my ($x2, $y2) = $pt->coords();
return sqrt( ($x1 - $x2)**2 + ($y1 - $y2)**2 );
}
sub random {
my $self = shift;
my $range = $_[0] // 50;
$self->{_x} = int rand $range;
$self->{_y} = int rand $range;
return $self;
}
1;
The random method takes an optional range, so both $pt->random() and $pt->random(10) set random coordinates for $pt. It has default 50, set using defined-or operator, //. Since it returns the object itself you can chain methods, like
my $pt = Point->new(10, 20);
my #coords = $pt->random()->coords();
print "#coords\n";
or, since new itself also returns the object, even
my #coords = Point->new()->random(10)->coords();
This wouldn't be of much use though as we now don't get the object.
LineSegment.pm
package LineSegment;
use strict;
use warnings;
use Point;
sub new {
my $class = shift;
my $self = { };
bless $self, $class;
if (#_) { #{$self}{qw(_pA _pB)} = #_ }
else { #{$self}{qw(_pA _pB)} = (Point->new, Point->new) }
return $self;
}
sub pA {
my $self = shift;
$self->{_pA} = $_[0] if $_[0];
return $self->{_pA};
}
sub pB {
my $self = shift;
$self->{_pB} = $_[0] if $_[0];
return $self->{_pB};
}
sub pts {
my $self = shift;
#{$self}{qw(_pA _pB)} = #_ if #_;
return #{$self}{qw(_pA _pB)};
}
sub len {
my $self = shift;
return $self->{_pA}->distance($self->{_pB});
}
1;
The default in the constructor calls Point's default constructor for each point, if no arguments were passed to initialize the segment object.
The len() method doesn't need coordinates, since we added distance() method to Point. It is natural and needed in a point class and this is better than having LineSegment compute. Often we need to calculate in the class, of course. Think of mid_point (of a segment), intersection (between two segments), etc.
main.pl
use warnings 'all';
use strict;
use feature 'say';
use Point;
use LineSegment;
my $line = LineSegment->new(
Point->new(10, 20),
Point->new(30, 40),
);
my $pt_A = $line->pA( Point->new(15, 10) );
my $pt_B = $line->pB;
printf "Point A = (%d,%d)\n", $pt_A->coords();
printf "Point B = (%d,%d)\n", $pt_B->coords();
printf "Length of the segment: %.3f\n", $line->len();
my #coords = $pt_A->random(10)->coords();
say "Random point, set on existing object: #coords";
my $segm = LineSegment->new();
my #ends = $segm->pts();
print "Segment created with defaults, ends: ";
printf "(%d,%d) ", $_->coords() for #ends;
say '';
This prints
Point A = (15,10)
Point B = (30,40)
Length of the segment: 33.541
Random point, set on existing object: 3 8
Segment created with defaults, ends: (34,19) (16,14)
What is notably missing here are checks of various kinds. However, once that becomes important one should probably start looking toward Moose or the similar but much lighter Moo.
A comment on new LineSegment() syntax used in the question.
A constructor in Perl is just a method, but the one that blesses the object into the class (package). The name new is indeed common but that is merely a convention. Thus the "normal" way to call a constructor is like any other method, ClassName->new().
One can use new ClassName, which is called "indirect object notation" (or syntax). However, here is what perlobj itself has to say about it (original emphasis)
Outside of the file handle case, use of this syntax is discouraged as it can confuse the Perl interpreter. See below for more details.
Also see this post and its links, for example. Just use ClassName->new().

How can I do function partial application in Perl?

Is there any way to achieve partial application in Perl?
Suppose, I want to do something like:
sub each_file($arr, $op) {
$op->($_) for #{$arr};
...
}
sub each_line($op, $file) {
...
}
each_file($arr, each_line($op));
I want to partially apply each_line() to only $op, so it'll become a new function can be passed to $each_file, how do I express this in idiomatic Perl?
You can do this in Perl with two approaches combined:
A function which returns a function reference
Closures
Example:
sub each_file {
my ($arr, $line_fn) = #_;
$line_fn->($_) for #{$arr};
...
}
sub each_line {
my ($op, $file) = #_;
...
}
sub make_line_processor {
my ( $op ) = #_;
# This is closed over $op, which effectively becomes
# a constant for the returned function
my $fn = sub {
return each_line( $op, #_ );
};
return $fn;
}
# To call it:
each_file( $arr, make_line_processor($op) );
This can be an even more useful technique in cases where you don't want $op directly, but some expensive-to-fetch derivation of it. In which case you would calculate the derived value just once (in the make_line_processor function) and close over that instead.
# given some $op as implied by your code snippet
each_file($arr, sub { each_line($op, shift) });
# shift op will be applied when anonymous sub { … } is called
(Your code snippet doesn't make it entirely clear what you intend $op to be when you make the call to each_line. It's usually better to present small working programs.)
You can roll this functionality up into a class. Then you can overload the subroutine dereference operator to make it look like your class is really a code reference.
package Partial;
use overload '&{}' => \&call;
sub new {
my $class = shift;
my $code = shift;
bless {code => $code, args => \#_}, $class;
}
sub call {
my ($self) = #_;
return sub{ $self->{code}->(#{$self->{args}}, #_) }
}
You can then use it like this:
sub printArgs {
print join ", ", #_;
print "\n";
}
my $partial = Partial->new(\&printArgs, 'foo', 'bar');
$partial->('baz', 'bat');
# prints foo, bar, baz, bat

How do I insert new fields into $self in Perl, from a File::Find callback

In a Perl object, I'm trying to add a new field into $self from within a File::Find wanted() sub.
use File::Find;
sub _searchForXMLDocument {
my ($self) = #_;
if($_ =~ /[.]+\.xml/) {
$self->{_xmlDocumentPath} = $_;
}
}
sub runIt{
my ($self) = #_;
find (\&_searchForXMLDocument, $self->{_path});
print $self->{_xmlDocumentPath};
}
_searchForXMLDocument() searches for an XML Document within $self->{_path} and is supposed to append that XML path to $self->{_xmlDocumentPath} but when I try to print it, it remains uninitialized. How do I add the field in $self?
Use of uninitialized value in print at /home/scott/workspace/CCGet/XMLProcessor.pm line 51.
You aren't calling _searchForXMLDocument() in an OO manner, so your $self object isn't being passed to it. This should do the trick now. Use a closure for your method and you have access to $self;
sub runIt{
my ($self) = #_;
my $closure = sub {
if($_ !~ m/[.]+\.xml/) {
$self->{_xmlDocumentPath} = $_;
}
};
find(\&$closure, $self->{_path});
print $self->{_xmlDocumentPath};
}
The first argument to find() needs to carry two pieces of information: the test condition, and the object you're working with. The way to do this is with a closure. The sub { ... } creates a code ref, like you get from \&_searchForXMLDocument, but the closure has access to lexical variables in the enclosing scope, so the current object ($self) is associated with the closure.
sub _searchForXMLDocument {
my ($self) = #_;
if($_ =~ /[.]+\.xml/) {
$self->{_xmlDocumentPath} = $_;
}
}
sub runIt{
my ($self) = #_;
find (sub { $self->_searchForXMLDocument (#_) }, $self->{_path});
print $self->{_xmlDocumentPath};
}
I think you're looking for something like this:
package XMLDocThing;
use strict;
use warnings;
use English qw<$EVAL_ERROR>;
use File::Find qw<find>;
...
use constant MY_BREAK = do { \my $v = 133; };
sub find_XML_document {
my $self = shift;
eval {
find( sub {
return unless m/[.]+\.xml/;
$self->{_xmlDocumentPath} = $_;
die MY_BREAK;
}
, $self->{_path}
);
};
if ( my $error = $EVAL_ERROR ) {
die Carp::longmess( $EVAL_ERROR ) unless $error == MY_BREAK;
}
}
...
# meanwhile, in some other package...
$xmldocthing->find_XML_document;
You pass a closure to find and it can access $self from the containing scope. File::Find::find has no capacity to pass in baggage like objects.

Define the method in the constructor of class in perl

I am reading code snippets like below:
sub new {
my $pkg = shift;
my $args = shift;
my #keys = keys %$args;
my $self = bless \%{$args}, $pkg;
$self->{'__properties'} = \#keys;
my $class = ref($self);
foreach my $meth (#keys) {
if (! $self->can($meth)) {
no strict "refs";
*{ $class . "::" . $meth } = sub {
my $instance = shift;
return $instance->{$meth};
};
}
}
return $self;
}
In the foreach loop, it seems that it creates some methods according to the parameters. There are two lines which I don't understand.Could someone help me? What's the * and {} used for?
no strict "refs";
*{ $class . "::" . $meth }
Best Regards,
This creates a symbol table alias.
The right side contains a reference to a function, so Perl will alias it to the subroutine $meth in the package $class.
See Symbol Tables in perlmod.
As eugene y have already explained, those lines manipulate the symbol table. In practical terms, they do so in order to create read-only accessor methods in the class based on whatever arbitrary list of attributes get passed into the constructor:
#!/usr/bin/env perl
use strict;
use warnings;
use 5.10.0;
package SomeClass;
sub new {
my $pkg = shift;
my $args = shift;
my #keys = keys %$args;
my $self = bless \%{$args}, $pkg;
$self->{'__properties'} = \#keys;
my $class = ref($self);
foreach my $meth (#keys) {
if (!$self->can($meth)) {
no strict "refs";
*{$class . "::" . $meth} = sub {
my $instance = shift;
return $instance->{$meth};
};
}
}
return $self;
}
package main;
my $foo = SomeClass->new({foo => 5}); # Creates SomeClass::foo
say $foo->foo; # 5
my $bar = SomeClass->new({foo => 3, bar => 7}); # Creates SomeClass::bar
say $bar->foo; # 3
say $bar->bar; # 7
say $foo->bar; # undef - ::bar was added to all instances of SomeClass
say $foo->baz; # Boom! No such method.
Personally, I think this is questionable OO practice (a class should generally have a known set of attributes instead of potentially adding new ones each time an instance is constructed), but that's what it does...

Deferring code on scope change in Perl

I often find it useful to be able to schedule code to be executed upon leaving the current scope. In my previous life in TCL, a friend created a function we called defer.
It enabled code like:
set fp [open "x"]
defer("close $fp");
which was invoked when the current scope exited. The main benefit is that it's always invoked no matter how/where I leave scope.
So I implemented something similar in Perl but it seems there'd be an easier way. Comments critiques welcome.
The way I did it in Perl:
create a global, tied variable which holds an array of subs to be executed.
whenever I want to schedule a fn to be invoked on exit, I use local to change the array.
when I leave the current scope, Perl changes the global to the previous value
because the global is tied, I know when this value change happens and can invoke the subs in the list.
The actual code is below.
Is there a better way to do this? Seems this would be a commonly needed capability.
use strict;
package tiescalar;
sub TIESCALAR {
my $class = shift;
my $self = {};
bless $self, $class;
return $self;
}
sub FETCH {
my $self = shift;
return $self->{VAL};
}
sub STORE {
my $self = shift;
my $value = shift;
if (defined($self->{VAL}) && defined($value)) {
foreach my $s (#{$self->{VAL}}) { &$s; }
}
$self->{VAL} = $value;
}
1;
package main;
our $h;
tie($h, 'tiescalar');
$h = [];
printf "1\n";
printf "2\n";
sub main {
printf "3\n";
local $h = [sub{printf "9\n"}];
push(#$h, sub {printf "10\n";});
printf "4\n";
{
local $h = [sub {printf "8\n"; }];
mysub();
printf "7\n";
return;
}
}
sub mysub {
local $h = [sub {printf "6\n"; }];
print "5\n";
}
main();
printf "11\n";
Well, your specific case is already handled if you use lexical filehandles (as opposed to the old style bareword filehandles). For other cases, you could always use the DESTROY method of an object guaranteed to go to zero references when it goes out of scope:
#!/usr/bin/perl
use strict;
use warnings;
for my $i (1 .. 5) {
my $defer = Defer::Sub->new(sub { print "end\n" });
print "start\n$i\n";
}
package Defer::Sub;
use Carp;
sub new {
my $class = shift;
croak "$class requires a function to call\n" unless #_;
my $self = {
func => shift,
};
return bless $self, $class;
}
sub DESTROY {
my $self = shift;
$self->{func}();
}
ETA: I like brian's name better, Scope::OnExit is a much more descriptive name.
Instead of using tie for this, I think I'd just create an object. You can also avoid the local that way too.
{
my $defer = Scope::OnExit->new( #subs );
$defer->push( $other_sub ); # and pop, shift, etc
...
}
When the variable goes out of scope, you have a chance to do things in the DESTROY method.
Also, in the example you posted, you need to check that the values you store are code references, and it's probably a good idea to check that the VAL value is an array reference:
sub TIESCALAR { bless { VAL => [] }, $_[0] }
sub STORE {
my( $self, $value ) = #_;
carp "Can only store array references!" unless ref $value eq ref [];
foreach { #$value } {
carp "There should only be code refs in the array"
unless ref $_ eq ref sub {}
}
foreach ( #{ $self->{VAL}} ) { $_->() }
$self->{VAL} = $value;
}
You may want to try out B::Hooks::EndOfScope
I Believe this works:
use B::Hooks::EndOfScope;
sub foo {
on_scope_end {
$codehere;
};
$morecode
return 1; # scope end code executes.
}
foo();
I think you want something like Scope::Guard, but it can't be pushed. Hmmm.
Thanks.
Trivially,
sub OnLeavingScope::DESTROY { ${$_[0]}->() }
used like:
{
...
my $onleavingscope = bless \sub { ... }, 'OnLeavingScope';
my $onleavingscope2 = bless \\&whatever, 'OnLeavingScope';
...
}
(The extra level of having a reference to a reference to a sub is necessary only to work around an optimization (that's arguably a bug) when using a non-closure anonymous sub.)