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

I'd like to be able to write code like use foo (bar => \&baz) but not have to put the use() call after the contents of sub baz {}. Typical perl source code has its use() calls at or near the top, any "main" code and then subroutine definitions. The use of subroutine references in use() code is interesting because use() itself is evaluated at BEGIN-time which is during parsing which means that all perl source code which is farther down in the file hasn't yet been parsed and isn't available for use. So it isn't valid to take a reference to something that hasn't yet been defined and suddenly normal perl code standards don't work.

How do I make this code sane?

use Alzabo::MethodMaker( schema => 'greenpartydb', class_root => 'GreenPartyDB::Database', all => 1, name_maker => \&name_maker ); sub name_maker { ... }

Replies are listed 'Best First'.
Re: Code references as use() parameters
by broquaint (Abbot) on Apr 09, 2003 at 15:38 UTC
    Can't say I'm proud but ...
    package pmsopw_249244; sub import { push @pmsopw_249244::USES, [scalar caller, @_[1 .. $#_]]; } sub real_import { $_->() for @_[1 .. $#_]; } INIT { for (@pmsopw_249244::USES) { my($pkg, @args) = @$_; eval (qq[{ package $pkg; pmsopw_249244->real_import(\@args); }]); warn "ack - $@" if $@; } } q{ the end is nigh ...};
    And the code that uses the module
    use pmsopw_249244 (\&func); sub func { print "I've been called\n"; } __output__ I've been called
    That might not be stellar code but hopefully it gives you some ideas.
    HTH

    _________
    broquaint

Re: Code references as use() parameters (well)
by tye (Sage) on Apr 09, 2003 at 17:05 UTC

    That works fine. You can take a reference to a subroutine by name before the subroutine exists. Perl does some interesting tricks to allow this. The problem you will run into is if you want to dereference it while still inside of import().

    I can think of several ways around this. The very straight-forward:

    require Alzabo::MethodMaker; Alzabo::MethodMaker->import( name_maker => \@name_maker );
    or you could make use of the undocumented fact that require actually returns the final "true" value in the module. So write your module like this:
    package Alzabo::MethodMaker; sub import { ... } \&import;
    and use it like this:
    (require Alzabo::MethodMaker)->( name_maker => \&name_maker, );
    or you could require the subroutine to be declared in-line:
    use Alzabo::MethodMaker( name_maker => sub { ... }, );
    You could use the poorly documented INIT as broquaint demonstrated. INIT is so poorly documented that I'm not sure of the consequences of this approach but the demonstration makes me worry that the INIT will be run too late if you need methods created before the main script has finished compiling. Certainly some will say this is very unlikely, but then, some would say that the order-of-definition problem that you are running into is very unlikely.

    That might be the most appealing solution because it hides so much from the module user so be sure to carefully document when the modules actually get "made" if you go this route.

    You could export a routine that makes the methods:

    use Alzabo::MethodMaker qw( makeMethods ); makeMethods( name_maker => \&name_maker, );
    You could document that Alzabo::MethodMaker should appear last in the source code rather than the more traditional first (I can certainly see potential value for a MethodMaker module being able to see what other methods have already been defined).

    In any case, good luck.

                    - tye
Re: Code references as use() parameters
by Tomte (Priest) on Apr 09, 2003 at 15:27 UTC

    Putting the subs you need (that is baz) in a module of theire own and useing this before using alzabo isn't an option? Or using an anonymous sub?

    Or am I just plain silly?

    regards,
    tomte


    Hlade's Law:

    If you have a difficult task, give it to a lazy person --
    they will find an easier way to do it.

Re: Code references as use() parameters
by MarkM (Curate) on Apr 09, 2003 at 16:25 UTC

    The real problem isn't that the subroutine reference is taken before it exists, as this actually works due to the magic of Perl:

    package A; sub import { *b = $_[1] }; BEGIN { $INC{"A.pm"} = undef } # Stop 'use' from loading A.pm package main; use A \&c; print A::b(), "\n"; # prints "YO\n" sub c { "YO" }

    I suspect your problem is that Alzabo::MethodMaker is trying to execute name_maker() before it is defined (otherwise, you wouldn't have this question, as the above code should 'work'). The only way around this is to define name_maker() first, or to delay execution of Alzabo::MethodMaker::import using one of the means suggested by other people.

      I'd like to be clear here that Alzabo::MethodMaker is only an example and that I intended to ask for ideas for handling this problem in the general case.

        I think I was clear that in the general case, as long as the subroutine is not invoked as part of the 'use', there is no problem. If the subroutine is invoked, then it needs to have already been defined. Only taking the reference of an as-yet-non-existent subroutine is not dangerous.

        Actually, there is one danger, and it has to do with prototypes. If the prototypes differ (when the function actually is defined), Perl will croak.

Re: Code references as use() parameters
by Improv (Pilgrim) on Apr 09, 2003 at 15:22 UTC
    I don't have a direct solution, but one workaround would be to create a function Alzabo::parameterize() which would stuff that stuff away into the Alzabo:: namespace at runtime, and have all your methods call a function to make sure that parameters have been filled in before use.
Re: Code references as use() parameters
by nothingmuch (Priest) on Apr 09, 2003 at 15:41 UTC
    perhaps converting use from BEGIN { require Module; import Module } to CHECK { require Module; import Module } can do the trick. But the drawbacks are obvious.

    Depending on when the coderef is used perhaps you can pass a soft reference. But then you have to no strict refs within the used module, and any if (ref $whatever) tests will fail.

    -nuffin
    zz zZ Z Z #!perl
Re: Code references as use() parameters
by John M. Dlugosz (Monsignor) on Apr 09, 2003 at 19:21 UTC
    Re: So it isn't valid to take a reference to something that hasn't yet been defined

    Actually, while working on Exporter::VA I discovered that it does in fact work. Taking the reference early defines a place-holder for it, and later defining it fills in that place holder. Taking a reference to the thing after it's been defined properly gives the same reference as taking such a reference "too early".

    BEGIN { print \&baz, "\n" } sub baz {} print \&baz;
    What you can't do is call baz from the begin block. But you can certainly stash away a reference to it for later.

    I do exactly that in Exporter::VA. The import function can see a ref as a parameter, even though the implicit BEGIN block is causing this to run before the thing exists. To make this work well, at import() time it only remembers the reference, and does not try to update the contents (its changes will be clobbered by your initialization later) or rely on it being set-up just yet. Later, a function that import() put into my package is executed, and it can refer to that reference now.

    So, you need to make your use statement just set-up, and "commit" the action later. In this case, sticking an END block into the calling package might do the trick.

    —John

Re: Code references as use() parameters
by Jenda (Abbot) on Apr 09, 2003 at 22:28 UTC

    Are you going to need the procedure anymore or is it just for this use statement? If it's just to the use you can do it like this:

    use Alzabo::MethodMaker( schema => 'greenpartydb', class_root => 'GreenPartyDB::Database', all => 1, name_maker => sub { .... } );

    Jenda
    Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.
       -- Rick Osborne

    Edit by castaway: Closed small tag in signature

Re: Code references as use() parameters
by BrowserUk (Patriarch) on Apr 09, 2003 at 16:50 UTC

    Wouldn't a simple pre-declaration in a BEGIN block solve the problem?

    You could even put this with or inside the subroutine body to keep things together.

    #! perl -slw use Try qw[ \&callback ]; sub callback{ BEGIN{ sub callback; } #do stuff }

    Examine what is said, not who speaks.
    1) When a distinguished but elderly scientist states that something is possible, he is almost certainly right. When he states that something is impossible, he is very probably wrong.
    2) The only way of discovering the limits of the possible is to venture a little way past them into the impossible
    3) Any sufficiently advanced technology is indistinguishable from magic.
    Arthur C. Clarke.
      Wouldn't a simple pre-declaration in a BEGIN block solve the problem?
      Nope as the sub still has to be parsed e.g
      BEGIN { foo(); } sub foo { BEGIN { sub foo } print "in foo()"; } __output__ Undefined subroutine &main::foo called at 249927.pl line 2. BEGIN failed--compilation aborted at 249927.pl line 3.
      So you need to delay the execution of the coderef until after compile-time unless you can force the compilation of the given sub.
      HTH

      _________
      broquaint

        Right. I brainfarted again.


        Examine what is said, not who speaks.
        1) When a distinguished but elderly scientist states that something is possible, he is almost certainly right. When he states that something is impossible, he is very probably wrong.
        2) The only way of discovering the limits of the possible is to venture a little way past them into the impossible
        3) Any sufficiently advanced technology is indistinguishable from magic.
        Arthur C. Clarke.