Beefy Boxes and Bandwidth Generously Provided by pair Networks
There's more than one way to do things
 
PerlMonks  

Recursive subroutines and closures

by ibm1620 (Friar)
on Sep 20, 2022 at 23:38 UTC ( #11147042=perlquestion: print w/replies, xml ) Need Help??

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

When I write a recursive subroutine, I usually have to write two subroutines: A(), which sets things up properly and then calls B(), which is the actual recursive subroutine and is not intended to be called by anyone except itself and A().

I wrote a simple routine make_backup($file) to make a backup of a file by copying it to $file.bak, first renaming any previous backups by appending another ".bak". In doing so I accidentally located the recursive subroutine inside the first subroutine (which I didn't know was possible).

use v5.36; use File::Copy; # Copy $path to $path.bak, preserving all earlier backups sub make_backup ($path ) { my $path_bak; if (-e $path) { $path_bak = _preserve_previous_backup($path); copy $path_bak, $path; } return $path_bak; # undef if path didn't exist sub _preserve_previous_backup ( $path ) { my $path_bak = "${path}.bak"; if (-e $path_bak) { _preserve_previous_backup($path_bak ); } move $path, $path_bak; return $path_bak; } }
And it worked. I thought this was a nifty way to encapsulate the "non-public" subroutine, until I realized what I'd done was to create a closure, which I'm unfamiliar with at this point.

I do like the idea of nesting the recursive piece inside the outer sub in this manner, and it does appear to work properly, but from what I've learned so far, this is not what "closures" are meant for. Could I get some enlightened commentary on the most-likely-unwise thing I'm doing?

Replies are listed 'Best First'.
Re: Recursive subroutines and closures
by haukex (Archbishop) on Sep 21, 2022 at 06:28 UTC

    Note because _preserve_previous_backup doesn't actually use ("close over") any of the variables from make_backup, you haven't really created a closure - instead, this actually has the potential for accidentally using variables from the outer sub, which can lead to issues like the ones GrandFather showed. To add to what jwkrahn noted, in this case you can just as well place _preserve_previous_backup in the main body of your code, the leading underscore already indicates that this is not a part of the public API and one should only call it when one knows what one is doing. And while the lexical subroutines noted by AnomalousMonk are indeed a thing (as of Perl 5.18), personally I actually use anonymous subs for this kind of thing. Just for example, consider this pseudocode:

    sub process_files ($ext, @files) { # let's say we split @files in two sets my @foo_files = ...; my @bar_files = ...; # but part of the processing is the same my $new_name = sub ($name, $some_param) { ... # some longer/complex code to munge the filename # note how this uses at least one var from the outer scope return $new_filename . $ext; }; # different processing for the two sets for my $file (@foo_files) { my $fn = $new_name->($file, "foo"); ... } for my $file (@bar_files) { my $fn = $new_name->($file, "bar"); ... } }

    If an anonymous sub needs to call itself recursively, one backwards-compatible way to define it is my $code; $code = sub { ... $code->() ... };, or, as of Perl 5.16, there is the __SUB__ function. Anonymous subs can also be used in a module if one really wants to hide the code from access outside the module; like other lexical variables, my $private_func = sub { ... }; in a module won't be accessible from the outside, though my personal opinion is that this is a bit overkill, and prefixing the sub name with an underscore should be enough - part of the Perl philosophy of "It would prefer that you stayed out of its living room because you weren't invited, not because it has a shotgun."

Re: Recursive subroutines and closures (updated)
by AnomalousMonk (Archbishop) on Sep 21, 2022 at 05:41 UTC

    OTOH, consider (ta-daaa) Lexical Subroutines:

    Win8 Strawberry 5.30.3.1 (64) Wed 09/21/2022 1:32:21 C:\@Work\Perl\monks >perl use 5.018; # lexical subroutines use strict; use warnings; Outer(1); # Inner(2); # Undefined subroutine &main::Inner ... sub Outer { my ($param) = @_; my sub Inner { my ($innerVar) = @_; print "In inner: $innerVar\n"; } Inner($param); } ^Z In inner: 1
    I haven't used lexical subs very much myself, so I can't offer advice about possible pitfalls. And closures are always a bit tricky. If you really want to have fun with closures, see Dominus's Higher-Order Perl (free download).

    Update: And the second of GrandFather's examples:

    Win8 Strawberry 5.30.3.1 (64) Wed 09/21/2022 1:46:46 C:\@Work\Perl\monks >perl use 5.018; # lexical subs use strict; use warnings; Outer(1); # Inner(); # still not there Outer(1); sub Outer { my ($param) = @_; my sub Inner { print "In inner: $param\n"; ++$param; } Inner(); } ^Z In inner: 1 In inner: 1


    Give a man a fish:  <%-{-{-{-<

      Thanks for the link to Higher-Order Perl. Interested to learn more about closures....
Re: Recursive subroutines and closures
by GrandFather (Saint) on Sep 21, 2022 at 01:50 UTC

    Consider:

    use strict; use warnings; Outer(1); Inner(2); sub Outer { my ($param) = @_; sub Inner { my ($innerVar) = @_; print "In inner: $innerVar\n"; } Inner($param); }

    Prints:

    In inner: 1 In inner: 2

    So maybe Inner isn't as encapsulated as you might hope? Now consider:

    use strict; use warnings; Outer(1); Inner(); Outer(1); sub Outer { my ($param) = @_; sub Inner { print "In inner: $param\n"; ++$param; } Inner(); }

    Prints:

    Variable "$param" will not stay shared at 11147042.pl line 12. In inner: 1 In inner: 2 In inner: 3

    In this case Inner has closed over $param from the enclosing Outer scope with results that may not be quite what you expected! Note that the second call to Outer doesn't recreate the closure and that the copy of $param used by Inner is that used on the first call to Outer. There are times when this is exactly what you want to happen, but not very often. Placing Inner after Outer makes the code clearer to my eye and without actually closing over variables as in the second example, there doesn't seem to be much advantage in nesting Inner in Outer.

    Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond
      Yes, when I added a second argument to make_backup and attempted to read it within _preserve_previous, I got the mysterious Variable will not stay shared warning, which led me to realize I was accidentally creating a closure.
Re: Recursive subroutines and closures
by jwkrahn (Monsignor) on Sep 21, 2022 at 03:53 UTC
    In doing so I accidentally located the recursive subroutine inside the first subroutine (which I didn't know was possible).

    While one subroutine is technically inside another subroutine, they are both scoped inside the same package, and so both are available anywhere inside that package.

Re: Recursive subroutines and closures
by madtoperl (Friar) on Sep 21, 2022 at 13:47 UTC
    I believe you can use File::Backup module to take backup of files easily.

Log In?
Username:
Password:

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

How do I use this? | Other CB clients
Other Users?
Others taking refuge in the Monastery: (3)
As of 2022-12-01 20:30 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    Notices?