Called as method or subroutine?

E

ed

Hi is there a way to make a sub/method compatible with
the two following calling methods?

1. MyClass::mySub();
2. MyClass->mySub();

Using the first method gives me one less argument in my argument list.
How do you deal with both situations?
Is there an easy way to find out if you're being invoked a method or
subroutine?

tia,
--ed
 
S

Steve Grazzini

ed said:
Hi is there a way to make a sub/method compatible with
the two following calling methods?

1. MyClass::mySub();
2. MyClass->mySub();

Not a good way.

You'd have to look at the number of arguments and the value
of the first argument and take a guess.

These are going to look awfully similar, though:

Class->sub;
Class::sub("Class");
 
M

Martien Verbruggen

Well, you could check the first argument and see if it's equal to
__PACKAGE__. But that's not a good solution, because it breaks
inheritance.

The notation MyClass::mySub() is not subject to inheritance in any
way. In other words, the above two calls cannot be equivalent, because
of Perl's magical treatment of that arrow. If you use that arrow, Perl
will search through @ISA. If you don't, then it won't.

The only way in which these two calls can ever be reasonably the same
is if the first argument were ignored when it's called as
MyClass->mySub(), and that can be done by simply discarding the first
argument when it's equal to __PACKAGE__.

sub MyClass::mySub
{
shift if $_[0] eq __PACKAGE__;
# do stuff...
}

Of course, if someone does:

MyClass::mySub("MyClass");

and expect that that argument won't get discarded, they will get a
nasty surprise.

I'd agree with Eric that it's a bad idea. it's misleading to call
something that can only be a regular sub with the arrow notation,
which strongly hints at class method implementation details. If you
really want this method to act as a class method, then you should
not call it without the arrow notation. Perl will not look for the
method in any superclasses unless you use that arrow, and if you
always need to call it with the arrow, then you don't need them to be
"equivalent".

Martien
 
T

Tassilo v. Parseval

Also sprach Martien Verbruggen:
The notation MyClass::mySub() is not subject to inheritance in any
way. In other words, the above two calls cannot be equivalent, because
of Perl's magical treatment of that arrow. If you use that arrow, Perl
will search through @ISA. If you don't, then it won't.

Which, as far as I understand, was Eric's point. The comparison with
__PACKAGE__ should not be done in a class-method because otherwise the
package of this method cannot be used as a superclass. This distinction
between class-method and plain function (or lack thereof) is one of the
true Perl-oddities. CGI.pm fell into this trap:

ethan@ethan:~$ perl -lMCGI=b1
print CGI->b1("CGI");
print b1("CGI");
print b1("cgi");
<b1>CGI</b1>
<b1></b1>
<b1>cgi</b1>

Lincoln fixed that in the latest release, though.
The only way in which these two calls can ever be reasonably the same
is if the first argument were ignored when it's called as
MyClass->mySub(), and that can be done by simply discarding the first
argument when it's equal to __PACKAGE__.

sub MyClass::mySub
{
shift if $_[0] eq __PACKAGE__;
# do stuff...
}

Of course, if someone does:

MyClass::mySub("MyClass");

and expect that that argument won't get discarded, they will get a
nasty surprise.

Yes, as the above CGI-bug shows.

Tassilo
 
E

Eric J. Roode

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

The only way in which these two calls can ever be reasonably the same
is if the first argument were ignored when it's called as
MyClass->mySub(), and that can be done by simply discarding the first
argument when it's equal to __PACKAGE__.

sub MyClass::mySub
{
shift if $_[0] eq __PACKAGE__;
# do stuff...
}

And this is where it breaks inheritance. If I later write a new class
MyOtherClass and expect to inherit mySub, and someone invokes it as:

MyOtherClass->mySub(@arguments);

the package name will not be removed.

- --
Eric
$_ = reverse sort qw p ekca lre Js reh ts
p, $/.r, map $_.$", qw e p h tona e; print

-----BEGIN PGP SIGNATURE-----
Version: PGPfreeware 7.0.3 for non-commercial use <http://www.pgp.com>

iQA/AwUBPx+1tGPeouIeTNHoEQKIeACfQHGZkS3VywQ/ctUIiGldcn70JsEAoJJN
58XdGCDNN/ZhqA2grkziZLoP
=9109
-----END PGP SIGNATURE-----
 
S

Steve Grazzini

Eric J. Roode said:
Martien Verbruggen said:
The only way in which these two calls can ever be reasonably the same
is if the first argument were ignored when it's called as
MyClass->mySub(), and that can be done by simply discarding the first
argument when it's equal to __PACKAGE__.

sub MyClass::mySub
{
shift if $_[0] eq __PACKAGE__;
# do stuff...
}

And this is where it breaks inheritance. If I later write a new class
MyOtherClass and expect to inherit mySub, and someone invokes it as:

MyOtherClass->mySub(@arguments);

the package name will not be removed.

You could use

shift if UNIVERSAL::isa($_[0], __PACKAGE__);

But that still doesn't handle Class::sub("Class").

And what's that supposed to be? :)
 
J

Jay Tilton

: Hi is there a way to make a sub/method compatible with
: the two following calling methods?
:
: 1. MyClass::mySub();
: 2. MyClass->mySub();

The File::Spec::Functions module may be worth study. It wraps the
methods from the File::Spec class into plain subroutines that can be
exported to other packages. It's a pretty simple thing to do, and it
doesn't require the methods to have any magical awareness of whether
they're being called as subroutines.
 
U

Uri Guttman

JT> : Hi is there a way to make a sub/method compatible with
JT> : the two following calling methods?
JT> :
JT> : 1. MyClass::mySub();
JT> : 2. MyClass->mySub();

JT> The File::Spec::Functions module may be worth study. It wraps the
JT> methods from the File::Spec class into plain subroutines that can be
JT> exported to other packages. It's a pretty simple thing to do, and it
JT> doesn't require the methods to have any magical awareness of whether
JT> they're being called as subroutines.

and i proposed a module for a TPF grant that would allow a module author
do have that feature with little effort. it would allow an OO module to
have a default object and it would export subs into the user space which
would do call the same methods on that default object. this is similar
to what cgi,pm does but it would be generic and support any OO
module. it would also allow prefixes or renaming of the subs by the user
code.

the grant was accepted but i have since been put on the TPF committee so
i can't take the funding. if someone wants to take this project on, i
will help out with the design (as i have done most of it already) and
they can earn some grant money. the grant was approved but not formally
issued. you have to have serious understanding of OO perl, closures,
exporting to do this. it is not for newbies or the faint of heart. :)

email me if you are interested.

uri
 
M

Martien Verbruggen

Also sprach Martien Verbruggen:
[snip]
The notation MyClass::mySub() is not subject to inheritance in any
way. In other words, the above two calls cannot be equivalent, because
of Perl's magical treatment of that arrow. If you use that arrow, Perl
will search through @ISA. If you don't, then it won't.

Which, as far as I understand, was Eric's point.

The point that I was trying to make is that with a call like
MyClass::mySub() it is not even really possible to talk about
inheritance, since it's not a method call, but rather a regular
subroutine call, and therefore never subject to Perl's inheritance
mechanisms. You do need the arrow for that.

If Eric was indeed saying the same thing, then I apologise for
misreading what he said. The only thing I read in his post was that
using a hardcoded package name in a method breaks inheritance, because
you'll always be working in the __PACKAGE__ class, as opposed to the
class that was actually passed via the arrow, in calls like
Class->method();

Martien
 
T

Tassilo v. Parseval

Also sprach Martien Verbruggen:
The point that I was trying to make is that with a call like
MyClass::mySub() it is not even really possible to talk about
inheritance, since it's not a method call, but rather a regular
subroutine call, and therefore never subject to Perl's inheritance
mechanisms. You do need the arrow for that.

Hmmh, just a sec. Plain functions are subject to inheritance in Perl
(they also trigger AUTOLOAD if it exists):

ethan@ethan:~$ perl -l
package t;
require CGI;
@ISA = qw/CGI/;
package main;
print t::h1("test");
__END__
<h1>test</h1>

That makes class-methods and function look even more indistinguishable.

Tassilo
 
M

Martien Verbruggen

Also sprach Martien Verbruggen:


Hmmh, just a sec. Plain functions are subject to inheritance in Perl
(they also trigger AUTOLOAD if it exists):

ethan@ethan:~$ perl -l
package t;
require CGI;
@ISA = qw/CGI/;
package main;
print t::h1("test");
__END__
<h1>test</h1>

That makes class-methods and function look even more indistinguishable.

$ cat /tmp/foo.pl
use strict;
use warnings;

package Foo;
sub foo { print "Foo::foo called\n"; }

package Bar;
@Bar::ISA = qw/Foo/;

package main;
Bar->foo();
eval { Bar::foo() };
print "$@" if $@;
eval { Bar::foo("Bar") };
print "$@" if $@;
eval { Bar::foo("Foo") };
print "$@" if $@;
# Indirect syntax:
Bar::foo Bar;

$ perl /tmp/foo.pl
Foo::foo called
Undefined subroutine &Bar::foo called at /tmp/foo.pl line 12.
Undefined subroutine &Bar::foo called at /tmp/foo.pl line 14.
Undefined subroutine &Bar::foo called at /tmp/foo.pl line 16.
Foo::foo called

I don't know what extra work CGI.pm does, but to me that looks like @ISA
does not come into play unless the arrow notation is used (ignoring the
horror of indirect calling).

Martien
 
S

Steve Grazzini

Martien Verbruggen said:
$ cat /tmp/foo.pl
use strict;
use warnings;

package Foo;
sub foo { print "Foo::foo called\n"; }

If you had defined Foo::AUTOLOAD() then all the examples would
have succeeded -- albeit with this warning:

% perldoc perldiag
Use of inherited AUTOLOAD for non-method %s() is deprecated
(D deprecated) As an (ahem) accidental feature, "AUTOLOAD" subrou-
tines are looked up as methods (using the @ISA hierarchy) even when
the subroutines to be autoloaded were called as plain functions
(e.g. "Foo::bar()"), not as methods (e.g. "Foo->bar()" or
"$obj->bar()").

This bug will be rectified in future by using method lookup only
for methods' "AUTOLOAD"s. However, there is a significant base of
existing code that may be using the old behavior. So, as an
interim step, Perl currently issues an optional warning when non-
methods use inherited "AUTOLOAD"s.

The simple rule is: Inheritance will not work when autoloading
non-methods. The simple fix for old code is: In any module that
used to depend on inheriting "AUTOLOAD" for non-methods from a base
class named "BaseClass", execute "*AUTOLOAD = \&Base-
Class::AUTOLOAD" during startup.

In code that currently says "use AutoLoader; @ISA =
qw(AutoLoader);" you should remove AutoLoader from @ISA and change
"use AutoLoader;" to "use AutoLoader 'AUTOLOAD';".
 
M

Martien Verbruggen

If you had defined Foo::AUTOLOAD() then all the examples would
have succeeded -- albeit with this warning:

I know. And as that warning explains, that's by accident, and should
never be relied on. Even if it was not accidental, that still doesn't
mean that a normal subroutine call is subject to inheritance.

So my point remains: A normal subroutine call is not subject to the
inheritance mechanism in the way a method call is. AUTOLOAD's accidental
features don't alter that point.

Martien
 
T

Tassilo v. Parseval

Also sprach Martien Verbruggen:
On 26 Jul 2003 06:13:12 GMT,


$ cat /tmp/foo.pl
use strict;
use warnings;

package Foo;
sub foo { print "Foo::foo called\n"; }

package Bar;
@Bar::ISA = qw/Foo/;

package main;
Bar->foo();
eval { Bar::foo() };
print "$@" if $@;
eval { Bar::foo("Bar") };
print "$@" if $@;
eval { Bar::foo("Foo") };
print "$@" if $@;
# Indirect syntax:
Bar::foo Bar;

$ perl /tmp/foo.pl
Foo::foo called
Undefined subroutine &Bar::foo called at /tmp/foo.pl line 12.
Undefined subroutine &Bar::foo called at /tmp/foo.pl line 14.
Undefined subroutine &Bar::foo called at /tmp/foo.pl line 16.
Foo::foo called

Hmmh, ok, no inheritance involved here. What I find irritating is that
AUTOLOAD is still involved. The search-sequence of Perl for a method is
usually from left to right @ISA, then UNIVERSAL::method(). Only after
these searches failed AUTOLOAD is triggered (albeit with a warning).
UNIVERSAL::AUTOLOAD() is last in this chain.

When reading the explanation to this warning in perldiag.pod, the
intended behaviour still sounds fishy to me. The only thing that will be
removed in future is the inherited AUTOLOAD, but not the invocation of
AUTOLOAD of the package from which a non-existing function was
requested. Provided that functions do not obey inheritance, they
shouldn't involve AUTOLOAD either IMHO.
I don't know what extra work CGI.pm does, but to me that looks like @ISA
does not come into play unless the arrow notation is used (ignoring the
horror of indirect calling).

In case of CGI, @ISA does come into play in some way:

ethan@ethan:~$ perl
package t;
require CGI;
package main;
t::test();
__END__
Undefined subroutine &t::test called at - line 4.

I haven't been able to figure out why CGI.pm is special. I used require
on purpose to not invoke CGI::import(). I suppose CGI.pm is always a bad
candidate when trying to exhibit Perl's standard behaviour.

Tassilo
 
T

Tassilo v. Parseval

Also sprach Bart Lateur:

Hrrmpfh, no, apparently he did not. I saw it in the changelog but
testing the latest release gives me:

ethan@ethan:~$ perl -lMCGI=b1
print b1("CGI");
__END__
<b1 />

So he replaced one bug with another one.
It's too sad Perl provides no official way to distinguish whether a sub
has been called as a function, or as a method. Like by setting a special
variable with the same dynamic behaviour as @_, for example.

As the sub-thread on inheritance-or-not has shown, perl knows to
distinguish these cases internally. So perhaps this information could be
retrieved using some XS-trickery. The op-tree looks different, too:

ethan@ethan:~$ perl -MO=Terse
t::test();
t->test();
__END__
LISTOP (0x8121f78) leave [1]
OP (0x8122068) enter
COP (0x8121f38) nextstate
UNOP (0x8121f18) entersub [1]
UNOP (0x8121ef0) null [141]
OP (0x812be50) pushmark
UNOP (0x8125368) null [17]
SVOP (0x8159220) gv GV (0x8128ac0) *t::test
COP (0x8121fc0) nextstate
UNOP (0x8122020) entersub [2]
OP (0x8122048) pushmark
SVOP (0x8121fa0) const PV (0x8128b50) "t"
SVOP (0x8122000) method_named PVIV (0x8128b20) "test"
- syntax OK

Not sure whether retrieving this information might invoke inspecting
this op-tree, though. In this case, there'd be no way to turn this
functionality in a little module (other than a compiler-backend).
And with every pointing towards CGI.pm on how it handled this: it's not
a good example, IMO.

Traditionally, CGI.pm is given as example when one needs a justification
for contorting Perl's object-model to its outer bounds. :)

Tassilo
 

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,769
Messages
2,569,581
Members
45,056
Latest member
GlycogenSupporthealth

Latest Threads

Top