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

I'm wondering if there is a way to import a sub from another package as a lexical sub without using non-core modules.

The motivation is that I like to ship CPAN modules with the fewest dependencies possible for the sake of minimalism. A common desire when authoring the module is to use functions from other packages, but not litter my method namespace with those functions. The "best practice" way to do that is:

use Scalar::Util 'looks_like_number'; use namespace::clean; ... looks_like_number($x) ...
but namespace::clean is not a core module. The most minimal of all options is to just reference the function by its full name:
use Scalar::Util (); ... Scalar::Util::looks_like_number($x) ...
but that is tedious and makes me unhappy while writing the code.

I have the option of lexical subs:

use v5.20; use experimental 'lexical_subs'; use Scalar::Util (); my sub looks_like_number { &Scalar::Util::looks_like_number } ... looks_like_number($x) ...
but I'm introducing a tiny bit of runtime inefficiency for the sake of more pleasant authoring.

What I really want is:

use v5.20; use experimental 'lexical_subs'; use Scalar::Util (); my sub looks_like_number = \&Scalar::Util::looks_like_number; ... looks_like_number($x) ...
but this gives me "Illegal declaration of subroutine looks_like_number".

Is there any way in pure-perl with only core modules to achieve a lexical sub which *is* the same coderef as the fully qualified global name?

Replies are listed 'Best First'.
Re: Pure perl lexical sub import
by Corion (Patriarch) on Dec 21, 2024 at 06:13 UTC

    If you don't want to take code from Exporter, you can do it like this:

    BEGIN { *looks_like_number = \&Scalar::Util::looks_like_number; }
    #!perl use 5.020; use Scalar::Util (); BEGIN { *looks_like_number = \&Scalar::Util::looks_like_number; } say looks_like_number(42);
      I think this will still suffer from littering his method namespace.
Re: Pure perl lexical sub import
by LanX (Saint) on Dec 21, 2024 at 12:40 UTC
    I had/have a project allowing to "export" lexicals.

    Basically a source filter which isn't filtering but only injecting code when import() is called.

    (Yes it's safe, since there is no filtering/parsing involved)

    It never made it to CPAN but can be found on GitHub.

    https://github.com/LanX/Filter-Inject

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    see Wikisyntax for the Monastery

      Can you give a short synopsis of what is happening there? Where does the injection take place?
        Does this meet your requirements?

        Test: cat t/01-use.t

        package __test__; use v5.22; use warnings; use Test::More; use lib "../lib"; sub dummy {} { my $line = __LINE__; use lexical 'Scalar::Util' => qw/looks_like_number/; is(__LINE__, $line+2, "line numbers are not messed up "); is( defined(&looks_like_number) ,1 ,"sub exists"); is( looks_like_number("42"), 1, "42 is number" ); isnt( looks_like_number("XX"), 1, "XX isn't number" ); is(defined &__test__::dummy, 1, "namespace testable"); isnt( defined(&__test__::looks_like_number) ,1 ,"namespace is clea +n"); } isnt( defined(&looks_like_number) ,1 ,"lex sub doesn't exist out of sc +ope"); diag( "Testing lexical $lexical::VERSION, Perl $], $^X" ); done_testing;

        test run

        perl /home/lanx/perl/prj/lexical/t/01-use.t ok 1 - line numbers are not messed up ok 2 - sub exists ok 3 - 42 is number ok 4 - XX isn't number ok 5 - namespace testable ok 6 - namespace is clean ok 7 - lex sub doesn't exist out of scope # Testing lexical 0.01, Perl 5.038002, /usr/bin/perl 1..7

        Module: cat lib/lexical.pm

        package lexical; use v5.22; use strict; use warnings; use Filter::Util::Call ; =head1 NAME lexical - Lexical use of exported functions =head1 VERSION Version 0.01 =cut our $VERSION = '0.01'; =head1 SYNOPSIS Pragma to import subs as lexical_subs into the current scope use lexical "Scalar::Util" => qw/looks_like_number/; print looks_like_number("a15"); =head1 EXPORT No classic exports, the namespace isn't polluted =head1 SUBROUTINES/METHODS =head2 import =cut sub import { my ($my_pkg, $module, @imports) = @_; eval "use $module"; # TODO quick & dirty my $code = q(use experimental 'lexical_subs','refaliasing';); $code .= << "__CODE__" for @imports; # TODO only subs ATM my sub $_; \\&$_ = \\&${module}::$_; __CODE__ upject($code); } sub upject { my $injection = shift; # --- exit if undef return unless defined $injection; # --- adjust line number to disguise injection my ($file,$line) = (caller(2))[1,2]; $line++; $injection .= qq{\n# line $line "$file"\n}; #warn $injection; # --- add source filter filter_add ( sub { my $status = filter_read_exact(1); # read one char into $_ if ( $status > 0) { $_ = $injection .";".$_; # prepend code once filter_del(); # delete source filter } $status ; } ); } =head1 AUTHOR Rolf Michael Langsdorf, C<< <lanx at cpan.org> >> # yadda yadda ... 1; # End of lexical

        Cheers Rolf
        (addicted to the Perl Programming Language :)
        see Wikisyntax for the Monastery

        > Can you give a short synopsis of what is happening there?

        This project does much more than you want, it's realizing macros (well almost) with a use macro() syntax at compile time.

        See my presentation from 2019 in Riga: https://perlcon.eu/talk/97

        > Where does the injection take place?

        Basically is the import() using a source-filter to inject code into the line after the use .

        When using that pseudo-module an INC-hook looks for the macro-function.

        If you come up with a reasonable name (I already kind of burned Filter::Inject ;) I could publish a module to CPAN doing lexical-imports by wrapping the original module.

        use Scoped "MODULE" => ARGUMENTS; ¹

        possible names:

        • Scoped
        • Lexically
        • My::Use
        • My::Import
        • Mine

        I'm also not sure if this should rather be lower-cased to indicate a pragma²

        Cheers Rolf
        (addicted to the Perl Programming Language :)
        see Wikisyntax for the Monastery

        ¹) in your case:

        use lexical Scalar::Util => qw/looks_like_number/;

        ²) I think lower-case is reasonable here, since if is a pragma too.

Re: Pure perl lexical sub import
by LanX (Saint) on Dec 23, 2024 at 01:20 UTC
    > but this gives me "Illegal declaration of subroutine looks_like_number".

    For completeness, I ran into the same problem, but feature refaliasing is your friend.

    According to the docs it was introduced with v5.22

    use experimental 'lexical_subs','refaliasing'; my sub looks_like_number; \&looks_like_number = \&Scalar::Util::looks_like_number;

    see also my proof of concept in Re^3: Pure perl lexical sub import (POC)

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    see Wikisyntax for the Monastery

Re: Pure perl lexical sub import
by Anonymous Monk on Dec 22, 2024 at 16:19 UTC

    Are "old-school" lexical subs an option? That is:

    my $looks_like_number = \&Scalar::Util::looks_like_number;
    ...
    ... $looks_like_number->( $x ) ...
    

    I personally like this approach because it gives me an easy out if the function is unavailable:

    my $looks_like_number = Scalar::Util->can( 'looks_like_number' ) || sub { ... };
    
      This is a fairly reasonable solution. Adds a few characters, but still better than the fully qualified name.
Re: Pure perl lexical sub import
by Arunbear (Prior) on Dec 22, 2024 at 19:12 UTC
    Another option using two packages (inspired by Re^8: In praise of Perl's object system. (OO namespaces))
    use v5.36; package Foo::Internal { use Scalar::Util 'looks_like_number'; sub check_it ($self, $num) { say 'Hoorah!' if looks_like_number($num); } } package Foo { sub new ($class) { bless {} => $class; } no warnings 'once'; *check_it = \&Foo::Internal::check_it; } my $foo = Foo->new; $foo->check_it(42); $foo->looks_like_number(42);
    Then
    % perl internal.pl Hoorah! Can't locate object method "looks_like_number" via package "Foo" at in +ternal.pl line 22.

      So you mean basically have a scope where they're imported be a different package than what eventually holds the object methods, by copying out the functions into that other package?

      I suppose it works, but I'm having trouble imagining how I would structure things so that it is less ugly than just calling the external function by its full name in the intended package.

        There'd be one package which is your actual clean namespace meant for end users, and another package containing the implementation details.

        It only looks ugly because I was tired and didn't use Exporter. So with Exporter it would be like this

        :::::::::::::: Foo/Internal.pm :::::::::::::: use v5.36; package Foo::Internal { use Exporter 'import'; our @EXPORT = qw[check_it]; use Scalar::Util 'looks_like_number'; sub check_it ($self, $num) { say 'Hoorah!' if looks_like_number($num); } } 1; :::::::::::::: Foo.pm :::::::::::::: use v5.36; package Foo { use Foo::Internal; sub new ($class) { bless {} => $class; } } 1; :::::::::::::: clean.pl :::::::::::::: use v5.36; use Foo; my $foo = Foo->new; $foo->check_it(42); $foo->looks_like_number(42);
Re: Pure perl lexical sub import
by LanX (Saint) on Dec 22, 2024 at 23:54 UTC
Re: Pure perl lexical sub import
by LanX (Saint) on Dec 21, 2024 at 13:02 UTC
    Another approach may be to manipulate the Scratchpad of the scope calling import().

    I've never tried to use PadWalker in conjunction with lexical subs, but it might be worth a try.

    NB: since PadWalker isn't core this wouldn't qualify as "pure Perl" anymore.

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    see Wikisyntax for the Monastery

      I was hoping for some magic trick using perl syntax, like aliasing the lexical function in a 'for' loop while localizing it, or maybe something involving an attribute that would trigger MODIFY_CODE_ATTRIBUTES and then use experimental ref assignment to overwrite the CV that the ref is pointing to, or something.

      If PadWalker was core that would be one thing, but an XS module would be even further out of the question for avoiding dependencies.

      I did some experiments and I doubt PadWalker can be of help here :/

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      see Wikisyntax for the Monastery

Re: Pure perl lexical sub import
by ikegami (Patriarch) on Dec 21, 2024 at 17:23 UTC
      A variation on that is I can say "undef *NAME" for each imported function at the end of the module, but I have to remember to keep that list updated.