Front page | perl.perl5.porters |
Postings from November 2022
Re: undeclared named subs/subrefs under strict
Thread Previous
|
Thread Next
From:
demerphq
Date:
November 24, 2022 12:27
Subject:
Re: undeclared named subs/subrefs under strict
Message ID:
CANgJU+VNHsc70qCKOvYi7T8y=gGzKG1-h_3TYXkX-9e9-z9W5Q@mail.gmail.com
On Tue, 22 Nov 2022 at 19:50, breno <oainikusama@gmail.com> wrote:
>
> Thank you for the thorough explanation, Yves. Some minor follow up:
>
> On Tue, Nov 22, 2022 at 6:34 AM demerphq <demerphq@gmail.com> wrote:
> (snip)
>>
>>
>> sub do_something_recursive_with_a_closure {
>> my ($self, @args)= @_;
>> local *recursive= sub { shift @args; recursive() if @args };
>> recursive();
>> }
>>
>>
>> In this case the recursive() sub doesn't exist except at run time. Or
>> even if it did, it wouldnt be the recursive() sub being called by this
>> code. Early binding would make that troublesome or impossible.
>
>
> That is kind of my point, that such code can be a bit confusing, especially when debugging. Isn't this why __SUB__ was created, to make it clearer / less error-prone?
Yes, __SUB__ was created to avoid needing to do this for a recursive
subroutine. But the point was that a callsite can be to a sub that
doesn't exist at the time its compiled.
FWIW, I suggested __SUB__ back in the day. :-)
> Please note I'm not arguing it *shouldn't* work. I'm just wondering whether there should be a warning/strict level that would advise that "this may not do what you think it does", one that could easily be dropped just like we do 'no strict "refs"', 'no warnings "uninitialized"', etc.
You can use Sub::StrictDecl
https://metacpan.org/pod/Sub::StrictDecl
However, maybe people don't like it as it imposes a level of
discipline many programmers are not used to. For instance you can't
write a recursive subroutine with a forward declaration. Perl
programmers generally dont like forward decls.
>>
>> > On a somewhat related note, how would one detect if a named subroutine/subref was declared or not (it's ok if it was declared but is empty) at runtime? Right now I'm doing:
>>
>> One way is to treat the package as a class, and ask if it "can" do
>> something. Assuming its not also a class and does not have an established
>> @ISA then it should be fine:
>>
>> if (UNIVERSAL::can("Package", "name")) { ... }
>
>
> That's really nice, but it won't cut it for my purposes. If you say something like: my $x = \&i_dont_exist, I want to check if $x points to an actual subroutine or to an undefined symbol.
I think you didn't read the docs. :-) UNIVERSAL::can() returns a subroutine.
my $sub = UNIVERSAL::can($package,$function_name);
So if such a sub or method exists, $sub will end up holding a
reference to it. Saying
my $x = \&i_dont_exist;
is just asking for trouble. The very statement itself will install a
sub into the symbol table on the assumption that code like this:
my $ref= \&foo;
sub foo { print "hello" }
$ref->();
should "just work". In fact, just *compiling* that statement, will
create an entry in the symbol table (although it won't have a sub
installed until run time).
Whereas using UNIVERSAL::can() or package->can() will DTRT provided
you are ok with it doing method resolution. It will also return the
callback.
Alternative you can use code like this:
perl -MDevel::Peek -le'sub check { print ">",exists($::{i_dont_exist})
? 1 : 0; my $glob= $::{i_dont_exist}; warn(($glob && *$glob{CODE}) ?
"has code" : "no code"); } check(); eval q(my $x=\&i_dont_exist);
check(); '
>0
no code at -e line 1.
>1
has code at -e line 1.
The special "stash" %:: or %main:: contains the symbol table for the
namespace. It contains globs for every symbol defined, and whether a
sub is installed can be determined by the CODE key of the glob. You
need to check BOTH that the glob exists, and that it contains a sub in
the CODE slot. Consider what happens if we remove the eval from the
above one liner:
perl -MDevel::Peek -le'sub check { print ">",exists($::{i_dont_exist})
? 1 : 0; my $glob= $::{i_dont_exist}; warn(($glob && *$glob{CODE}) ?
"has code" : "no code"); } check(); my $x=\&i_dont_exist; check(); '
>1
no code at -e line 1.
>1
has code at -e line 1.
Just compiling the code to copy the i_dont_exist sub causes the
'i_dont_exist' glob to exist.
>> I have to wonder tho, *why* do you want to do this? Usually the
>> correct way to see if a function is defined /is to call it/. :-) Why
>> do you want to know in advance? It feels like you are asking an XY
>
> (snip)
>
> I'm trying to implement that "feature" (detect if a named subref is there at any given moment without calling it) basically to help in linting / debugging / code reviews. I was bit by this a few days ago when I made a typo on a named ref that was imported. When I tried dumping the variable, I got "sub { ... }" so I thought it was ok. Only when I deparsed it I saw it as "sub;" and figured out what was wrong.
Well, maybe you should just try out Sub::StrictDecl. It is a bit
annoying to retrofit to existing code, as it enforces a strict
"declare before you use" model, and perl devs are lazy and often "use
before they declare", and rarely add forward decls as they aren't
needed much normally. So a given module might need some changes before
it will compile, but once you get caught up it is pretty nice to work
with.
Cheers,
yves
--
perl -Mre=debug -e "/just|another|perl|hacker/"
Thread Previous
|
Thread Next