Beefy Boxes and Bandwidth Generously Provided by pair Networks
Come for the quick hacks, stay for the epiphanies.
 
PerlMonks  

Anonymous subroutines

by Bod (Parson)
on Dec 10, 2023 at 23:00 UTC ( [id://11156237]=perlquestion: print w/replies, xml ) Need Help??

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

Recently, I've noticed a few places where anonymous subroutines have been declared for no apparent reason:

my $function = sub { # ...do stuff... }; # Call the sub $function->('param');
I would write the same subroutine as such:
sub function { # ...do stuff... } # Call the sub function('param);
The above two code snippets appear to do exactly the same thing. But is there a subtle difference lurking there that I have overlooked? Is there a reason to write the first form instead of the second?

I don't think I have ever written a stand-alone anonymous subroutine like this. The only place I generally use anonymous subs is as a 'value' in a hash. Typically, to pass to Template to keep logic separated from display like this:

my $vars = { 'date' => $date, 'format' => sub { return ucfirst lc $_[0]; }, }; $template->process('example.tt', $vars);

Replies are listed 'Best First'.
Re: Anonymous subroutines (why and what for)
by LanX (Saint) on Dec 10, 2023 at 23:32 UTC
    There are various possible reasons

    In general anonymous subs ...

    • don't pollute the namespace - i.e. block a name (sic) in a package
    • are private * and scoped when assigned to my var
    • don't need to be \& referenced before being passed around ²
    • are "created" on the fly at run time °
    • can be generated multiple times with binding to different closure vars
    • can be nested *
    • can easily be used as "lambdas" with map-like functions(&) ³
    • (add more techniques from functional programming)
    • (add more techniques from metaprogramming)
    • ...

    Without more details it's hard to tell, why you saw, what you saw.

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

    °) well "available", they are only compiled once at compile time, but their time window/life span is dynamic

    *) even in older Perl versions

    ²) e.g. as callbacks

    ³) like in List::Util et.al.

    Update

    Disclaimer: The listed points are not exclusive and have overlaps. Neither do I expect them to be exhaustive and to cover all cases.

      I believe this is an excellent summary. Thanks, LanX!

      One context in which I've seen anonymous subs used a lot is where you have code chunks being defined within, and called by, a framework. Execution of the code takes the form of eval'ing the code, so it has lexical context just like a sub. If, in such a code chunk, you want to define a sub so you can call it multiple times (or any other reason), you have to do it as an anonymous sub, since you can't do it any other way (at least not without getting a "won't stay shared" warning). (But your other comment is important too.)

        Thanks!

        I'm not sure I've ever worked with such kind of a "code template system" ° but I wanted to add metaprogramming anyway. (Done)

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

        °) maybe apart the monastery's codebase ;)

Re: Anonymous subroutines (Closures)
by eyepopslikeamosquito (Archbishop) on Dec 11, 2023 at 07:32 UTC
        > I think the FAQ should be fixed

        perlfaq#114

        > Even in python2 it's possible, just "hackier"

        The fact that you need to use a list instead of an integer makes it more of impossible than possible. ;-)

        map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
Re: Anonymous subroutines
by GrandFather (Saint) on Dec 11, 2023 at 03:02 UTC

    One interesting possibility. Consider:

    use strict; use warnings; sub MakeCounter { my $seed = 0; my $func = sub {return $seed++;}; return $func; } my $counter1 = MakeCounter(); my $counter2 = MakeCounter(); for (1 .. 3) { $counter1->(); $counter2->() for 1 .. 3; } printf "Count 1: %d, Count 2: %d\n", $counter1->(), $counter2->();

    Prints:

    Count 1: 3, Count 2: 9
    Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond
Re: Anonymous subroutines
by cavac (Parson) on Dec 11, 2023 at 13:11 UTC

    I've only seen (and used) this in a potential test&debug setting, like so

    my $function = sub { ... }; if($isDebug) { $function = sub { ... } }

    Depending on the scope and visibility of $function, it might be possible to replace this "function pointer" from the outside. This could be used to either change the algorithm, make custom callbacks or, again, for debugging. For example, it might be useful to provide pre-selected "random" dice rolls when testing a game engine.

    my $getDiceRoll = sub { return int(rand() * 6) + 1; }; my $dicefh; if($regressionTest) { $getDiceRoll = sub { # READ NEXT DICE ROLL FROM dicerolls.txt if(!defined($dicefh)) { open($dicefh, '<', 'dicerolls.txt') dir die($!); } my $roll = <$dicefh>; chomp $roll; return $roll; } }

    The nice thing about this, compared to the usual way of adding a bunch of IFs into the sub is that, well, you don't. Depending on what the function does and how often it it called, this may have relevance to the performance of the function.

    Just imagine a graphics library that provides a getpixel() function. If it has to check every time you read out a pixel if it's in indexed or RGB mode while working on a big image, this can get awfully slow (see Autoclicker! Cheating is winning, too! for a practical example where performance matters). One way to potentially speed up things would be to overwrite the getpixel() function with the version optimised for the image that is currently being worked on. It's a bit higher on startup cost, but could save time overall when reading large portions of the pixel data.

    Edit: Bugfix second code snippet. Thanks, johngg for the extra set of eyes!

    PerlMonks XP is useless? Not anymore: XPD - Do more with your PerlMonks XP
Re: Anonymous subroutines
by Fletch (Bishop) on Dec 11, 2023 at 13:08 UTC

    It's been ages so I'm fuzzy on the exact details but there was a common problem encountered with mod_perl (where people initially would frequently use the CGI compatibility wrapper) where people would have named subs nested and then got warnings about "variable $foo won't stay shared at ####" because the inner sub would get bound to one my variable while the outer would have a new one reallocated on its pad. Again, fuzzy but something like this (not that this is at all useful code) <handwave>

    #!/usr/bin/env perl use 5.034; sub outer { my $foo = $_[0]; sub inner { say "foo: $foo"; } inner(); } outer( "bar" ); outer( "quux" ); sub outer2 { my $foo = $_[0]; my $inner = sub { say "inner2: $foo"; }; $inner->(); } outer2( "bar" ); outer2( "quux" ); __END__ foo: bar foo: bar inner2: bar inner2: quux

    Having the inner sub be an anonymous sub makes it a proper closure.

    Edit: And the reason this was a frequent problem under mod_perl was that the CGI compatibility wrapper (Apache::Registry I think?) would wrap up the entire CGI script into an anonymous sub that it would run during the content phase so any named subs in the script were then nested subs.

    The cake is a lie.
    The cake is a lie.
    The cake is a lie.

      See also The-'lexical_subs'-feature and try
      • my sub inner {...

      Which is part of the standard language now. :)

      > because the inner sub would get bound to one my variable

      Basically, it's a 1-to-n problem, because closures are supposed to be created many times.

      But named subs are only once assigned globally and bound to a type-glob in the current package (think our ) at compile time (think BEGIN {...} )

      Nowthe compiler sees the use of lexical variables from the outer scope, which potentially change with every invocation. (The former are not destroyed) and emits the "won't stay shared" warning.

      An anonymous closure sub OTOH would be assigned each time and have a deep-binding to the current outer scope.

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

Re: Anonymous subroutines
by karlgoethebier (Abbot) on Dec 11, 2023 at 21:14 UTC

    Simple change of behavior or create new behavior:

    #!/usr/bin/env perl use strict; use warnings; use feature qw(say); my $gizmo = frobnicate( sub { reverse split "", (shift) }, sub { uc(shift) } ); say $gizmo->("nose"); sub frobnicate { my ( $foo, $bar ) = @_; sub { $foo->( $bar->(@_) ) } }

    «The Crux of the Biscuit is the Apostrophe»

Re: Anonymous subroutines
by stevieb (Canon) on Dec 11, 2023 at 11:30 UTC

    Update: I misinterpreted the OP as antagonistic; my response below is based on that misinterpretation. I redact the emotion, but I'll leave the content intact.

    Recently, I've noticed a few places where anonymous subroutines have been declared for no apparent reason:

    I urge you to take a step back when you look at code. Never assume. Just because you think something has been declared "for no apparent reason", doesn't mean that's factual.

    I'm not going to argue this specific case, I just want to argue that just because you think something shouldn't be done a certain way doesn't mean someone doesn't have a reason for doing it that way.

    An intermediate-or-above Perl hacker could list multiple cases where an anon sub could be useful in this use case.

    TIMTOWTDI

    I learned a long time ago in my early years that when I saw something that didn't make sense, I asked questions instead of making statements that things were done "for no apparent reason".

    This is the first time I've ever admonished someone in my 20 years of being here, but I'm going to do so in a kind, respectful way...

    I don't think I have ever written a stand-alone anonymous subroutine like this. The only place I generally use anonymous subs

    *I*, *i*, *I*. See the repetition? Doesn't matter what you have done, reality is that you seem to have judged the code without understanding the full impact before comparing it to your own code. In that, you presume that your code is superior to that of the code you are judging, therefore you discard that code as inferior.

    When I open my mind, I allow things that I couldn't have imagined to infiltrate. This way, I ensure I don't put code or people down due to judgement, I allow ideas to permeate my own, it promotes wide discussion and idea sharing and makes for better software.

    Tolerance and acceptance first. If there's a "what the fsck?", ask questions. Don't condemn prior to investigation.

    -stevieb

      I'm not sure what behaviour you are admonishing here. The OP did not say "for no reason", but "for no apparent reason" - it seems clear that by this they mean "for no reason apparent to me". They saw some code using a construct that they considered unusual, so they asked about it. I see no condemnation or judgement here, only a request for enlightenment.

        Spot on hv!

        The question was asked because I don't understand why one would write a sub like that. Having seen it twice in two very different settings from two different coders, I am either missing a reason to do it that way or it is how some people used to write code at one time. Both instances where I saw it are not recent bits of code.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://11156237]
Approved by LanX
Front-paged by LanX
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others imbibing at the Monastery: (4)
As of 2024-09-13 17:33 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    The PerlMonks site front end has:





    Results (21 votes). Check out past polls.

    Notices?
    erzuuli‥ 🛈The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.