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

hello, i'm trying to do
$foo->baz('abe'=>&linc, map {"$_" => sub {return '<H1>'.$_.'</H1>'}; } '1'..'5'
but it s not working. I check my logs and apparently $_ is undefined.

How'd that happen?

I fixed it by saying

map { $a = $b = $_; $a => sub {'<H1>'.${b}.'</H1>'} } 1..5,
which kinda worked but not really ($b didn't change). finally after i've said my($a,$b) it all worked out as planned.

I remember this being a scope issue (it's documented), but that doesn't explain what happened to $_?

Can somebody confirm/deny/shed some light on this?
(i'm running Win32 ActivePerl v5.6.1 build 631)

Replies are listed 'Best First'.
(jeffa) Re: funky $_ with map {}
by jeffa (Bishop) on Mar 02, 2002 at 13:59 UTC
    Yes, this is a scope issue. Consider this:
    my %hash = map {$_ => sub {return "<H1>$_</H1>"} } (1..5); $_ = 'foo'; print &{$hash{2}};
    Do you really need anonymous subs to solve your problem? Would this work instead?
    my %hash = map {$_ => "<H1>$_</H1>" } (1..5);
    Seems much more simple and usable to me ...

    jeffa

    L-LL-L--L-LL-L--L-LL-L--
    -R--R-RR-R--R-RR-R--R-RR
    B--B--B--B--B--B--B--B--
    H---H---H---H---H---H---
    (the triplet paradiddle with high-hat)
    
      no that wouldn't work.

      I wouldn't be using sub's unless I needed to use subs.

      I thought it was kind of ghetto to have to resort to eval to get $_ to be have properly (yeah I knew it was global, but it should've been local to block, and once the sub's were compiled, IMHO, it shouldn't need to stay shared).

      Thanks anyway

        Eval is probably not as bad as you think. If the little extra compilation overhead is a significant portion of your program's run time, you probably shouldn't be building this coderef laden hash anyway.

        Local creates a temporary copy of the variable, but it stays global, so there's no reference count increased when you use it in the anonymous sub. When the scope ends, the temporary copy is simply discarded.

        my creates a new lexical, our is a lexically scoped "use vars", local creates a temporary copy of the global that will be used while the lexical scope lasts.
        Only lexicals can stay shared.
        (I have probably made some mistakes or mis-assumptions. If you see any, please do correct me)

        Lbh ebgngrq guvf grkg naq abj lbh pna ernq vg. Fb jung? :) -- Whreq

Re: funky $_ with map {}
by Juerd (Abbot) on Mar 02, 2002 at 15:12 UTC
    $_ is a global, not a lexical, and doesn't "stay shared" in a lexically scoped sub ("closure"). You can solve it by using eval:
    map { $_ => eval "sub { '<H1>$_</H1>' }" } 1..5
    Don't be fooled by the single quotes: they are within a double quoted string that is passed to eval.

    Lbh ebgngrq guvf grkg naq abj lbh pna ernq vg. Fb jung? :) -- Whreq

Re: funky $_ with map {}
by jlongino (Parson) on Mar 02, 2002 at 20:18 UTC
    From Programming PERL 5 2e, 3.2.153 Sort:
    Do not declare $a and $b as lexical variables (with my). They are package globals (though they're exempt from the usual restrictions on globals when you're using use strict).
    Unless you understand all the possible scoping implications of using $a and $b this way, I'd suggest you avoid using them at all except in matters of sort.

    As it turns out, it doesn't cause a problem in this isolated snippet of code, but I recommend you use the solution offered by jeffa.

    --Jim

      The usage of $a and $b in this context wouldn't hurt him, since there isn't a sort being evaluated. However, it's just as easy to use less-confusing names, or use one of the other methods offered.

      --rjray

Re: funky $_ with map {}
by shotgunefx (Parson) on Mar 02, 2002 at 21:13 UTC
    Why not change the anonymous sub to call a function that will return an anonymous sub?
    #!/usr/bin/perl my @answers = map { return_tag ($_,'H1') } 1..5; foreach my $answer (@answers){ print $answer->(),"\n"; } sub return_tag { my ($arg,$tag) = @_; # Will be caught in closure. return sub { "<$tag>".$arg."</$tag>" }; }


    -Lee

    "To be civilized is to deny one's nature."
Re: funky $_ with map {}
by Anonymous Monk on Mar 03, 2002 at 03:52 UTC

    i bet $_ gets a special instance when inside a sub{}.

    whaddyathink?

    how about:

    map { $_ => eval qq(sub{"<h1>$_</h1>"}) } 1..6
    

    here the $_ gets evaluated inside map{} before being inside the sub{}

    hmm?

      $_ is less special than you think. It wouldn't have worked with a global named $foo either. The eval-solution you gave has already been given (by yours truly).

      use vars qw($foo); { $foo = 'foo'; sub xyzzy { print "$foo\n"; } } $foo = 'bar'; xyzzy(); # Prints bar now.

      But:
      { my $foo = 'foo'; sub xyzzy { print "$foo\n"; } } my $foo = 'bar'; xyzzy(); # Prints the 'foo' that stayed shared.


      Update - s/\$f\b/\$foo/, thanks Kanji.

      ++ vs lbh qrpbqrq guvf hfvat n ge va Crey :)
      Nabgure bar vs lbh qvq fb jvgubhg ernqvat n znahny svefg.
      -- vs lbh hfrq OFQ pnrfne ;)
          - Whreq