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

I accidently wrote the below code which embeds a subroutine inside a subroutine. To my surprise it worked fine. It had't even occurred to me that this would be legal code and I could think of no reason to do it.

Ok I thought that's interesting but it must be in some way bad and I want it to look more normal so I'll remove the embedded sub. But whenever I seperate the below into 2 subs @files is out of scope in sub FileName. So I need a way to pass @Files to sub FileName. Sub FileName is being called recursively by find, I tried find (\&FileName(\@files),"/usr/local/apache/htdocs/service/"); but got a is not code ref error.

I've never been much good at recursion so some advise would be appreciated

GOT Sub in Sub

use File::Find; sub Get_Saves { my @files; sub FileName { unless (($_ eq "." )or ($_ eq "temp.svd")){ push @files, $_; } }##END FileName find (\&FileName,"/usr/local/apache/htdocs/service/"); return \@files, $#files+1; }##END Get_Saves

Want 2 separate subs

sub Get_Saves { my @files; @files=find (\&FileName(/@files),"/usr/local/apache/htdocs/service +/"); return \@files, $#files+1; }##END Get_Saves sub FileName { unless (($_ eq "." )or ($_ eq "temp.svd")){ push @files, $_; } return @files; }##END FileName

Replies are listed 'Best First'.
Re: Sub in a Sub & Recursion
by djantzen (Priest) on Jan 21, 2002 at 17:40 UTC

    This is some ... interesting code. You may want to consider using consistent naming conventions as well as more intuitive return types.

    In your first example, you're sharing a single variable within the scope of Get_Saves, @files. In the second example you're set up to use multiple copies since you're returning @files and assigning it to another @files variable as the result of the call to find. But at the same time you're trying to pass a reference to the @files in the scope of Get_Saves. These two mechanisms are in conflict.

    One version:

    sub Get_Saves { my @files; @files=find(\&FileName,"/usr/local/apache/htdocs/service/"); #sure + you want to hard code this? return \@files, $#files+1; # why return the size? }##END Get_Saves sub FileName { my @files; unless (($_ eq "." )or ($_ eq "temp.svd")){ # do you need to test +for '..' too? push @files, $_; } return @files; }##END FileName

    Another version:

    our @files; # globally accessible sub Get_Saves { @files=find (\&FileName,"/usr/local/apache/htdocs/service/"); return \@files, $#files+1; }##END Get_Saves sub FileName { unless (($_ eq "." )or ($_ eq "temp.svd")){ push @files, $_; } }##END FileName

    I don't believe that find (\&FileName(\@files)) will work because you're passing a reference to the subroutine, not invoking it.

Re: Sub in a Sub & Recursion
by demerphq (Chancellor) on Jan 21, 2002 at 18:20 UTC
    Havent read your code in detail, but a cursory look seems to indicate that you are trying something akin to the pascal nested subroutine.

    I brought this up (hypothetically) in an earlier thread, and tilly provided a good working translation into real perl using a closure.

    Have a look there for some ideas about closures.

    Sorry if this is OT, didnt have time to read your post in detail
    /me dons flame retardant suit

    Yves / DeMerphq
    --
    When to use Prototypes?

Re: Sub in a Sub & Recursion
by goldclaw (Scribe) on Jan 21, 2002 at 19:29 UTC
    Heres a good use for closeures. The way File::Find works there are no good way to pass extra arguments into the wanted subroutine without using closeures. So for your example you could do something like this:
    sub Get_Saves { my @files; #Just a small mod here. Call FileName since it now #returns a suitable closeure, and pass in the #reference to @files so that FileName knows #where to add stuff. @files=find(FileName(\@files),"/usr/local/apache/htdocs/service +/"); return \@files, $#files+1; }##END Get_Saves sub FileName { my $files_ref=shift; #Use reference to array return sub { unless (($_ eq "." )or ($_ eq "temp.svd")){ push @{$files_ref}, $_; } #removed this since returning anything from the #wanted function does not really make any sence(See #File::Find) #return @files } }

    A few years back a colleuge of mine was horrified of the File::Find module, since you basically had to use "global" variables to be able to pass arguments in to it. I was sure that couldnt be so, but I didnt know/understand closeures at the time. He ended up writing his own find routine, and I twisted my brain for days on how to do this with File::Find.

    The solution came to me months later when I learned about closeures. Unfortunately I had quit the company by then, so I couldnt tell him how he had wasted his time reimplementing File::Find. Anyway, I guess the moral is something like:Whenever File::Find doesnt seem to do all you want, start reading about closeures again.

    GoldClaw

Re: Sub in a Sub & Recursion
by derby (Abbot) on Jan 21, 2002 at 19:55 UTC
    hakkr,

    The first example works because the lexical @files is visible to the FileName subroutine (since both are defined within the Get_Saves subroutine block). This works because you have the scope correct.

    The second example will not work for several reasons: 1, File::Find::find does not return anything; 2, \&FileName(\@files) is not a reference to a subroutine (only \&FileName is); and three, the @files in Get_Saves is a different beastie than the @files in FileName (the first is a lexical and the second is a global.

    To use File::Find with the two seperate functions, you would need to use either global @files or a lexical @files defined outside of Get_Saves and FileName:

    #!/usr/bin/perl use strict; use File::Find; my @files; # could leave this out and just let FileName # auto-vificate global @files Get_Saves(); sub Get_Saves { find( \&FileName, "/usr/local/apache/htdocs/service/" ); } sub FileName { unless( ($_ eq ".") or ($_ eq "temp.svd")) push( @files, $_ ); # This @files is either the lexical declared above # or an auto-vifified global if nothing was declared # above. }

    update I really like goldclaw's Re: Sub in a Sub & Recursion but you would need to remove the "@files=" from the find since File::Find::find always returns nothing. Just do this:

    #where to add stuff. find(FileName(\@files),"/usr/local/apache/htdocs/service/"); return \@files, $#files+1;

    -derby

      Thanks,
      I've used the closure solution with derby's fix which has given me an insight into a practical use for closures. 'our' worked as well but I've not seen it used here much before and my instinct says to stay away from globalish things.
      Update Cheers for the recursion tips anon monk reads almost like a tutorial. Recursion is a way of thinking I'm determined to bend my head around

        It seems to me, that if you want to use a closure for such a thing, you'll want to use an anonymous sub instead of your FileName sub. Then the @files array will be shared as you expect it to be (e.g., each time Get_Saves is called, $sub will be bound to the new instance of @files).

        sub Get_Saves { my @files; my $sub = sub { unless (($_ eq "." )or ($_ eq "temp.svd")){ push @files, $_; } }; find ($sub,"/usr/local/apache/htdocs/service/"); return \@files, $#files+1; }

        As to how to make two separate subs, derby seems to have already answered that. You'd "need to use either global @files or a lexical @files defined outside of Get_Saves and FileName".

        Hope this helps

        dmm

        If you GIVE a man a fish you feed him for a day
        But,
        TEACH him to fish and you feed him for a lifetime
Re: Sub in a Sub & Recursion
by little (Curate) on Jan 21, 2002 at 16:42 UTC
    In your latter snippet you should sub FileName have to get the value or refernce passed to it using shift or @ARGV or @_ or ....
    As there is also TIMTOWDI you might want to read passing refs to subs

    Have a nice day
    All decision is left to your taste
Re: Sub in a Sub & Recursion
by Anonymous Monk on Jan 21, 2002 at 22:45 UTC
    To my surprise it worked fine.

    I'm surprised too. You should have gotten a 'Variable "@files" will not stay shared' warning.

    Minimalistic example:
    sub foo { my @files; sub bar { push @files, @_; }; bar(@_); return @files; } print join ' ', foo(123, 456); # prints '123 456'. print join ' ', foo('abc', 'def'); # prints nothing.
    So why doesn't it print anything the second time? It's because &bar will have its own @files array but &foo will get a new array each time. So &foo and &bar will only share @files the first time &foo is run.

    Another (expected) solution and its problems:
    my @files; sub foo { bar(@_); } sub bar { push @files, @_; } foo(123, 456); print join ' ', @files; # '123 456' foo('abc', 'def'); print join ' ', @files; # '123 456 abc def'

    We need to reset @files somewhere. OK. Let's add @files = () at the beginning of &foo.
    There! Now it works... or not. It can't handle recursion. Let's modify &foo some more to make it a recursing subroutine:
    sub foo { @files = (); bar(@_); shift; foo(@_) if @_; }

    Now the output will be '456' and 'def' instead of '123 456 456' and 'abc def def' respectively. So we can't do the @files = () assigment in &foo. We need a wrapper. But what if the wrapper mysteriously gets recursive? Evil indeed...

    My solution:
    Keep it simple, keep it clean, keep it working.

    The non-recursive solution to the original problem. (Of course rewritten as well, I'm just interested in showing the technique.)
    sub foo { my @files; my $bar = sub { push @files, @_; }; $bar->(@_); return @files; } print join ' ', foo(123, 456); # '123 456' print join ' ', foo('abc', 'def'); # 'abc def'

    Here the inner subroutine gets recompiled each time resulting in @files always being the same as the one &foo uses.

    Making it resursive:
    sub foo { my @files; my $bar = sub { push @files, @_; }; $bar->(@_); shift; return @files, @_ ? foo(@_) : (); } print join ' ', foo(123, 456); # '123 456 456' print join ' ', foo('abc', 'def'); # 'abc def def'

    Viola!

    I hope no one got confused by my rewritings from the original problem.

    Cheers.