samtregar has asked for the wisdom of the Perl Monks concerning the following question:

Given the full name of a subroutine (Package::name), is it possible to tell if it's a constant? Solutions involving B::* or XS are fine.
For example, I'd like this code to print "ok" twice given an implementation if is_constant():

use constant foo => 1; sub bar { $_[0] } print "ok\n" if is_constant('main::foo'); print "ok\n" unless is_constant('main::bar');

One possibility would be to override constant::import, but that has a number of drawbacks and wouldn't catch "home-grown" constant subs.

Replies are listed 'Best First'.
•Re: Is it possible to distinguish contstants from normal subs?
by merlyn (Sage) on May 19, 2002 at 02:01 UTC
    A constant is nothing more than a subroutine with an empty prototype. Nothing magical. You can distinguish a subroutine with an empty prototype from one that is not prototyped or has a different prototype, but you cannot distinguish a "constant" from a subroutine with an empty prototype, in any form.

    -- Randal L. Schwartz, Perl hacker

      The way I read the perldoc, a constant is "a subroutine with an empty prototype" which never changes its return value! There is a clear difference: compare

      sub foo () { 17 }
      with
      sub bar () { rand(10) }
      Deparsing use of both functions in code shows a clear difference in the way Perl treats each one.

      That's disappointing. Perl certainly distinguishes (DB::sub isn't called when they're used, for example) so why shouldn't Perl programmers be able to?

      Dumb idea: could I compile a bit of code and then examine it with B::Utils to figure out if the sub in question is a constant? Hmmmm, don't see why not...

      -sam

        If it's optimized out, probably not. I'd try an experiment with B::Deparse to see what the optree looks like.
Re: Is it possible to distinguish contstants from normal subs?
by samtregar (Abbot) on May 19, 2002 at 04:58 UTC
    Here's one that works. I'd be happy to replace it with something cleaner. C'mon PerlMonks, I know you can do it! Here's what I've got:

    #!/usr/bin/perl -w use strict; sub is_constant { no strict 'refs'; my $name = shift; my $code = *{$name}{CODE}; # must have an empty prototype to be a constnat my $proto = prototype($name); return 0 unless defined $proto and not length $proto; # attempt to redefine - this will cause a warning for a # real constant that starts with "Constant" my $is_constant; local $SIG{__WARN__} = sub { $is_constant = 1 if $_[0] =~ /^Consta +nt/ }; eval { *{$name} = sub { "TEST" } }; # set it back eval { *{$name} = $code; }; # all done return $is_constant; } use Test::More tests => 6; use constant CONSTANT1 => 1; use constant CONSTANT2 => [ 'foo', 'bar' ]; sub CONSTANT3 () { '...' } sub NONCONSTANT1 () { $_[0]++ } sub NONCONSTANT2 ($) { "fooey" } sub NONCONSTANT3 { 1; } ok(is_constant("main::CONSTANT1")); ok(is_constant("main::CONSTANT2")); ok(is_constant("main::CONSTANT3")); ok(!is_constant("main::NONCONSTANT1")); ok(!is_constant("main::NONCONSTANT2")); ok(!is_constant("main::NONCONSTANT3"));

    It passes my tests despite leaveing me with a not-so-clean feeling.

    -sam

      While working this code into my module (Devel::Profiler) I've found that prototype($name) isn't very reliable. Sometimes it returns the correct prototype of a constant and sometimes it returns undef. On the other hand, prototype($code) always works.

      I find this a bit strange. Maybe this is a bug in Perl (5.6.1)? Or maybe a bug in me? You make the call!

      -sam