Using symbolic references to invoke package methods - good or bad practice?

J

jhumanski

At my place of employment, a philosophical discussion is going on
within the development group. Say you have the following package
definition:

package MyPackage;

use strict;
....
sub new {
my $class = shift;

my $self = bless {}, $class;
$self->_init();
return $self;
}

sub _init {
my $self = shift;

foreach my $property ('func1', 'func2', 'func3', 'func4') {
$self->$property();
}
}

sub func1 {...}
sub func2 {...}
sub func3 {...}
sub func4 {...}

In this example, the private method _init is invoking package methods
using symbolic references.

Many in the development team don't have a problem with this. They
consider it a clean and extendible solution. While they recognize
that symbolic references in general are bad, they justify this
solution by stating that the use of the symbolic references is
controlled and, hence, with little risk.

I am in the minority. I am looking for justification (other than the
standard mantra that symbolic references are bad) that this practice
should be discouraged. I have googled this topic but have not found
any specific examples that state why this practice is bad.

I am looking for this group's opinion. Any examples that demonstrate
why this is bad (or good) practice are appreciated.
 
P

Peter Makholm

jhumanski said:
I am in the minority. I am looking for justification (other than the
standard mantra that symbolic references are bad) that this practice
should be discouraged. I have googled this topic but have not found
any specific examples that state why this practice is bad.

You should not be generalizing solely based on a mantra. You need to
understand the reason for the mantra, that is you need to understand why
symbolic references are a bad idea.

Then you can discusse if the same reasons holds for method calls.

One reason not to like symbolic references is that they are only working
for package variables (globals) and silently ignores the existence of
lexical variables. Globals are as such discouraged for it own reasons,
and ignoring lexical variables can mean some action on a distance where
the meaning of code can change in a radical way due to seemingly
unrelated code changes.

This problem with symbolic references does not hold for methods as
lexical methods does not exists in the same way.


There are other problems with symbolic references. For some of them it
could be argued that it also applies to symbolic method calls other
problems can be dismissed.

In the end it is up to your team to decide if the advantages outweights
the problems.

//Makholm
 
R

Rainer Weikusat

jhumanski said:
At my place of employment, a philosophical discussion is going on
within the development group. Say you have the following package
definition:

package MyPackage;

use strict;
...
sub new {
my $class = shift;

my $self = bless {}, $class;
$self->_init();
return $self;
}

sub _init {
my $self = shift;

foreach my $property ('func1', 'func2', 'func3', 'func4') {
$self->$property();
}
}

sub func1 {...}
sub func2 {...}
sub func3 {...}
sub func4 {...}

In this example, the private method _init is invoking package methods
using symbolic references.

This is a standard, documented feature of Perl, cf

The right side of the arrow typically is the method name, but
a simple scalar variable containing either the method name or
a subroutine reference can also be used.
[perlobj(1)]

[...]
I am looking for justification (other than the standard mantra that
symbolic references are bad) that this practice should be
discouraged.

If you have no idea why it should be discouraged, why should it be
discouraged?
 
U

Uri Guttman

j> package MyPackage;

j> sub new {
j> my $class = shift;

j> my $self = bless {}, $class;
j> $self->_init();
j> return $self;
j> }

j> foreach my $property ('func1', 'func2', 'func3', 'func4') {
j> $self->$property();

j> In this example, the private method _init is invoking package methods
j> using symbolic references.

to be accurate those are not symbolic references. methods are always
looked up in the symbol table (late binding) when they are called (there
is some caching for speed). lexical vars are handled at compile time and
so can be made strict which disallows symrefs in the proper sense.

the technique of using late binding of methods is actually a good one
and it can be very powerful. it can be seen as the method version of a
dispatch table which is for procedural calls.

j> Many in the development team don't have a problem with this. They
j> consider it a clean and extendible solution. While they recognize
j> that symbolic references in general are bad, they justify this
j> solution by stating that the use of the symbolic references is
j> controlled and, hence, with little risk.

your issue is not whether it is a good idea but conflating late binding
of method calls with symbolic references. they are not the same beast at
all and so they shouldn't be compared.

j> I am in the minority. I am looking for justification (other than the
j> standard mantra that symbolic references are bad) that this practice
j> should be discouraged. I have googled this topic but have not found
j> any specific examples that state why this practice is bad.

j> I am looking for this group's opinion. Any examples that demonstrate
j> why this is bad (or good) practice are appreciated.

it is used in many modules on cpan whereas symrefs are not commonly used
(except when actually munging the symbol table which should be their
only use). you may not find examples easily but they are out there. this
is a perfectly fine perl idiom so don't fret over it and just use it.

uri
 
T

Tim McDaniel

I *wish* "use strict" would do more compile-time checking for subs.
But it doesn't flag anything for

foreach my $property (\&func1, \&func2, \&func5) {
...
sub func1 { print "func1\n"; }
sub func2 { print "func2\n"; }
sub func3 { print "func3\n"; }

until runtime, when it goes to call the missing func5.

Personally, I still prefer the explicit function reference, if for no
other reason that, even if you only look at
foreach my $property (\&func1, \&func2, \&func5) {
it's clearly a list of sub references and it's likely that they will
be called, but for
foreach my $property ('func1', 'func2', 'func5') {
you have to look down to see how $property is being used to know what
they are.

But I'm unusual in liking to be explicit about things: initializations
and uses and such. I like to use fully-qualified names like
ThisMod::ThatMod::SomeSub() even if there has been importing, just to
make it clear where it's from. Or
my @result = ();
just to make it clear that I've considered it and I do really need it
to be an empty array, or
{
my @working = ();
...
}
to make it clear where the variable dies.
 
R

Rainer Weikusat

I *wish* "use strict" would do more compile-time checking for subs.
But it doesn't flag anything for

foreach my $property (\&func1, \&func2, \&func5) {
...
sub func1 { print "func1\n"; }
sub func2 { print "func2\n"; }
sub func3 { print "func3\n"; }

until runtime, when it goes to call the missing func5.

Personally, I still prefer the explicit function reference, if for no
other reason that, even if you only look at
foreach my $property (\&func1, \&func2, \&func5) {
it's clearly a list of sub references and it's likely that they will
be called

That won't work in this case because it was about method calls, see
contrived example below:

-------------------
package Parent;

sub new {
return bless([], $_[0]);
}

sub blah
{
print("Blah!\n");
}

package Child;

our @ISA = qw(Parent);

sub blubb
{
print("Blubb!\n");
}

package main;

my $c = Child->new();

# call the method
#
$c->$_() for (qw(blubb blah));

# this doesn't work
#
eval {
$c->$_() for (\&blubb, \&blah);
};
print("$@");

# neither does this
#
eval {
$c->$_() for (\&Child::blubb, \&Child::blah);
};
print("$@");
 
T

Tim McDaniel

That won't work in this case because it was about method calls, see
contrived example below:

Summary of the example: Package Parent has blah(). Package Child ISA
Parent and adds blubb(). Instantiate Child->new().

In package main, plain \&blubb and \&blah don't work because they are
equivalent to \&main::blubb and \&main::blah, which don't exist.
\&Child::blubb and \&Child::blah don't work because &Child::blah
doesn't exist.

He didn't mention \&Parent::blubb and \&Parent::blah, but they
wouldn't work either, because &Parent::blubb doesn't exist and
(if Child were to redefine blah) &Parent::blah would bypass Child's
override.

But calling the literal strings 'blubb' and 'blah' work fine, and they
follow inheritance.

Well, blah! That's a "blah!" directed at Perl and the entire
situation, not at Rainer, who provided a most help example. Thank
you.
 
T

Tim McDaniel

Well, yes? Don't treat methods as though they were functions.

Well, when you see "sub foo", it kinda makes you think of functions
somehow ...
Are you looking for ->can? This resolves the method and returns a
coderef: it's effectively the equivalent of \&foo for methods. (And,
just like \&foo, it will under some circumstances return a stub,
which may or may not turn out to resolve to a real method when you
call it.)

I had written a paragraph asking about that but deleted it before
posting. Congratulations on the telepathy!

For anyone else who hadn't heard of it, a little Web search shows that
it's in built-in class UNIVERSAL, so "man UNIVERSAL" or
http://perldoc.perl.org/UNIVERSAL.html

The doc mentions some concerns with AUTOLOAD, as you alluded to
above. I assume that, like any deep pointer, $obj->can has the
limitation that it's good only for the current state of $obj, so you
won't get a pointer with serious mojo that works even if $obj is later
changed to be some other type of object. CLASS->can works anyway.
 
R

Rainer Weikusat

Ben Morrow said:
Quoth (e-mail address removed):
[...]

But calling the literal strings 'blubb' and 'blah' work fine, and they
follow inheritance.

Are you looking for ->can? This resolves the method and returns a
coderef: it's effectively the equivalent of \&foo for methods. (And,
just like \&foo, it will under some circumstances return a stub, which
may or may not turn out to resolve to a real method when you call
it.)

As addition to the already posted code (tested on 5.10.1):

-------------
# nor this
#'
eval {
$c->can($_)->() for qw(blubb blah);
};
print("$@");

# nor this
#'
eval {
$c->$_() for map { Child->can($_); } qw(blubb blah);
};
print("$@");
-------------

NB: At least with Perl 5.10.1, using map in this way together with for
is not a good idea: At the implementation level, this is a two pass
algorithm which first iterates over all of the 'input' values in order
to create the list returned by map and then loops over this list,
executing the actual loop body.
 
T

Tim McDaniel

Ben Morrow said:
Quoth (e-mail address removed):
[...]

But calling the literal strings 'blubb' and 'blah' work fine, and they
follow inheritance.

Are you looking for ->can? This resolves the method and returns a
coderef: it's effectively the equivalent of \&foo for methods. (And,
just like \&foo, it will under some circumstances return a stub, which
may or may not turn out to resolve to a real method when you call
it.)

As addition to the already posted code (tested on 5.10.1):

-------------
# nor this
#'
eval {
$c->can($_)->() for qw(blubb blah);
};
print("$@");

# nor this
#'
eval {
$c->$_() for map { Child->can($_); } qw(blubb blah);
};
print("$@");

To expand on that:

The example had package Parent with sub blah, package Child ISA Parent
and adding sub blubb, "my $c = Child->new();". Both of the two evals
above result in the error messages

Undefined subroutine &Child::blah called at ...

Which I don't understand, given that "man UNIVERSAL" on my current
system says

"$obj->can( METHOD )"
"CLASS->can( METHOD )"
"eval { VAL->can( METHOD ) }"

"can" checks if the object or class has a method called
"METHOD". If it does, then it returns a reference to the sub.
If it does not, then it returns undef. This includes methods
inherited or imported by $obj, "CLASS", or "VAL".
 
T

Tim McDaniel

Quoth (e-mail address removed):
To expand on that:

The example had package Parent with sub blah, package Child ISA Parent
and adding sub blubb, "my $c = Child->new();". Both of the two evals
above result in the error messages

Undefined subroutine &Child::blah called at ...

What code are you running, exactly? If I run

#!/usr/bin/perl

{
package Parent;
sub new { bless [], $_[0] }
sub blah { warn "blah" }
}

{
package Child;
our @ISA = "Parent";
sub blubb { warn "blubb" }
}

my $c = Child->new;

eval {
$c->$_() for map { Child->can($_); } qw(blubb blah);
};
warn "err: $@";

I get

blubb at can line 12.
blah at can line 6.
err: at can line 20.

Just to be sure something weird wasn't going on, I even checked with
5.10.1.

5.10.1 here, and the test program in 102.pl is as follows: it's the
example from a few articles back with Rainer's. I know so
little about packages and classes and such in Perl that I can't start
to see what might be significant about differences. You wrap the
backages in blocks and the original does not.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

package Parent;

sub new {
return bless([], $_[0]);
}

sub blah
{
print("Blah!\n");
}

package Child;

our @ISA = qw(Parent);

sub blubb
{
print("Blubb!\n");
}

package main;

my $c = Child->new();

# call the method
#
$c->$_() for (qw(blubb blah));

# this doesn't work
#
eval {
$c->$_() for (\&blubb, \&blah);
};
print("$@");

# neither does this
#
eval {
$c->$_() for (\&Child::blubb, \&Child::blah);
};
print("$@");

# nor this
#'
eval {
$c->can($_)->() for qw(blubb blah);
};
print("$@");

# nor this
#'
eval {
$c->$_() for map { Child->can($_); } qw(blubb blah);
};
print("$@");

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The output:

$ perl local/test/102.pl
Blubb!
Blah!
Undefined subroutine &main::blubb called at local/test/102.pl line 32.
Blubb!
Undefined subroutine &Child::blah called at local/test/102.pl line 39.
Blubb!
Undefined subroutine &Child::blah called at local/test/102.pl line 46.
Blubb!
Undefined subroutine &Child::blah called at local/test/102.pl line 53.
 
T

Tim McDaniel

This isn't supposed to work: the coderef returned by can does not
include an implicit invocant, you have to provide one explicitly.


This works just fine

It took me a bit of looking at the second to decode the first
comment. Is this what it means?

$c->can('blubb') or whatever returns a reference to a sub --
specifically a reference to a class method. To use any class method,
you need to apply it to something, like an instance of the class. So
$c->($c->can('blubb'))->() should work (and I think you need not put
in the last "->").

Given that the subs in the example code I was working from (just
posted in anothe rfork of this thread) do not refer to @_, do these
particular subs really need to be invoked for an object?
 
R

Rainer Weikusat

Ben Morrow said:
This isn't supposed to work: the coderef returned by can does not
include an implicit invocant, you have to provide one explicitly.

There is no such thing as 'an invocant' in perl. A class is a package
and a method is nothing but a subroutine located in a specific way (by
looking at the package the 'invocant' was blessed into in order to
determine where to start search for a sub of the given name). Provided
such subroutine was found, it is then called with the 'invocant' as
$_[0] and the remaining arguments following after it. Since can is
supposed to do a method search, there's no need for doing another
method search: The result is a sub reference which can be called like
any other sub reference. Since neither of the two looks at @_, there's
no need to pass any arguments to it.

That was the technical explanation. Replacing the (correct) code I was
using with the kind of code you believe to be correct, namely,

-----------
# nor this
#'
eval {
for (qw(blubb blah)) {
$m = $c->can($_);
$c->$m();
}
};
print("$@");
----------

leads to the exact same result:

Blubb!
Undefined subroutine &Child::blah called at a.pl line 48.

NB: The $c->$m() can also be written as $m->($c).

[...]
$c->$_() for map { Child->can($_); } qw(blubb blah);
[...]
NB: At least with Perl 5.10.1, using map in this way together with for
is not a good idea: At the implementation level, this is a two pass
algorithm which first iterates over all of the 'input' values in order
to create the list returned by map and then loops over this list,
executing the actual loop body.

It is in principle a little less efficient than a streaming
implementation, yes, though for a list of two elements (or, indeed, 200)
this is extremely unlikely to be visible in practice. Trying to avoid
temporary lists means effectively avoiding 'for' altogether, and it's
far too useful for that.

You seem to have missed the point of my text: I wasn't writing about
'temporay lists' but about a two-pass algorithm: The for ... map is
compiled to code which executes two loops instead of one (at least on
Perl 5.10.0). And this implies that the mapping step should rather be
done as part of the loop body. It would be nice if perl could do this
conversion (or something which accomplished the same thing)
implicitly.
 
R

Rainer Weikusat

Ben Morrow said:
From: Ben Morrow <[email protected]>
Subject: Re: Using symbolic references to invoke package methods - good or bad practice?
Newsgroups: comp.lang.perl.misc
Date: Mon, 23 Apr 2012 15:39:52 +0100




No it doesn't.

It does. The code is the one I originally posted with some appended/
modified parts. In this case the reason for the error message was
that it contained a \&Child::blah before the can invocation. This
effectively kills the overload mechanism for blah because it creates
an 'undefined' Child::blah sub which exists in the symbol table of
Child. Trying to invoke the method in the usual way also won't work
afterwards.

[...]
I understood perfectly: the map runs one loop, which builds a temporary
list, and the for runs another which consumes it. I see the temporary
storage required as more of a 'problem' than the two loops:

the two loops perform the same steps as one would have, they just do
them in a different order.

[...]

They don't. That was what I was writing about. The additional steps
are the operations required to perform the second loop. This implies
that using map in this way has twice the looping overhead of a loop
which does the transformation 'inline'.
 
R

Rainer Weikusat

Ben Morrow said:
From: Ben Morrow <[email protected]>
Subject: Re: Using symbolic references to invoke package methods - good or bad practice?
Newsgroups: comp.lang.perl.misc
Date: Mon, 23 Apr 2012 15:39:52 +0100




No it doesn't.

It does. The code is the one I originally posted with some appended/
modified parts. In this case the reason for the error message was
that it contained a \&Child::blah before the can invocation. This
effectively kills the inheritance mechanism for blah because it creates
an 'undefined' Child::blah sub which exists in the symbol table of
Child. Trying to invoke the method in the usual way also won't work
afterwards.

[...]
I understood perfectly: the map runs one loop, which builds a temporary
list, and the for runs another which consumes it. I see the temporary
storage required as more of a 'problem' than the two loops:

the two loops perform the same steps as one would have, they just do
them in a different order.

[...]

They don't. That was what I was writing about. The additional steps
are the operations required to perform the second loop. This implies
that using map in this way has twice the looping overhead of a loop
which does the transformation 'inline'.
 
R

Rainer Weikusat

Ben Morrow said:
Quoth Rainer Weikusat said:
Ben Morrow <[email protected]> writes:
[...]
They don't. That was what I was writing about. The additional steps
are the operations required to perform the second loop. This implies
that using map in this way has twice the looping overhead of a loop
which does the transformation 'inline'.

Oh, I see; yes, I suppose that's true, but if you've got to the point of
worrying about the overhead of the for or map loop itself you shouldn't
be writing in Perl any more. If you need that level of efficiency, you
need to do it in C.

Once upon a time the past, there was a guy called Rasumus Lehrdorf who
had a homepage. He tried to pep it up a little by adding various CGI
scripts written in Perl to it but somehow, he couldn't get his Perl
code to perform adequately. So, he then set off and wrote a simple HTML
preprocessor in C which he christened the Personal HomePage tools
and published them on the net (and the rest - as they say - is history
....).

I use Perl serious system programming tasks and because of this, I
absolutely do care about the performance of the Perl code I write: I'm
fine with burning cycles in exchange for some tangible benefit, eg,
aggressively partition code into subroutines in order to make it
easier to maintain _intermittently_ (I'm not living inside some huge
body of code written by no-one-knows-who where I wander around in
order to explore it so that I can occasionally make minor changes
without breaking something. The typical situation is rather that I
need to add a new, not entirely trivial feature to something I haven't
seen for 20 months and preferably quickly). I'm not at all keen on
burning cycles just because I can do so easily.

In this particular case, using a somewhat silly simple example,

------------
#!/usr/bin/perl

use Benchmark;

sub wmap {
my @out;

push(@out, $_) for map { $_ + 1; } @_;
return @out;
}

sub nmap {
my @out;

push(@out, $_ + 1) for @_;
return @out;
}


timethese(-10,
{
wmap => sub { wmap(1 .. 5); },
nmap => sub { nmap(1 .. 5); }});
-------------

the variant using map needs to execute four more ops per input element
and the other, the actual counts (perl -MO=Concise,-exec) being 13 and
9, respectively, and the corresponding quotient appears also in the
measured execution speed: The subroutine which has about 1.44... times
more elementary operation runs at about 1/(1.4) of the speed of the
other.
 

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

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,483
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top