Beefy Boxes and Bandwidth Generously Provided by pair Networks
more useful options
 
PerlMonks  

Why I hate File::Find and how I (hope I) fixed it

by mirod (Canon)
on Aug 30, 2001 at 17:19 UTC ( #109068=perlmeditation: print w/ replies, xml ) Need Help??

OK, so every time someone here asks how to walk a directory tree and process files we all religiously (!) chant the File::Find mantra.

The truth is that I have always hated File::Find. It just feels old.

My main gripe is that the wanted function, which is called for each file found, does not accept arguments. So if I really need arguments, which happens quite often, then I have to use good ole globals. This is definitely _not_ what I'd call good coding practice. Plus how do I put this code in a module?

So here is my solution, using closures to generate various functions from a single "template" one. The interesting part is really the make_wanted function, that takes a code reference and a list of arguments, and generates a function (with takes no arguments) that will call the code reference with the arguments (which will have the value they had when make_wanted was called):

#!/bin/perl -w use strict; use File::Find; # create various wnated functions my $wanted= make_wanted( \&wanted_1, 'toto', 'tata'); find( $wanted, '.'); print "\n"; $wanted= make_wanted( \&wanted_1, 'foo', 'bar', 'baz'); find( $wanted, '.'); print "\n"; $wanted= make_wanted( \&wanted_2, 'toto', 'tata'); find( $wanted, '.'); print "\n"; $wanted= make_wanted( \&wanted_2, 'foo', 'bar', 'baz'); find( $wanted, '.'); print "\n"; # a regular function, can access its arguments and the File::Find vari +ables sub wanted_1 { my @args= @_; print "wanted_1( ", join( ', ', @args), ") on $_\n" if( m/\.xml$/) +; } sub wanted_2 { my @args= @_; print "wanted_2( ", join( ', ', @args), ") on $_\n" if( m/\.txt$/) +; } # the closure generator # creates a function that calls the function passed as first argument # passing it the arguments make_wanted is called with sub make_wanted { my $wanted= shift; # get the "real" wanted fu +nction my @args= @_; # "freeze" the arguments my $sub= sub { $wanted->( @args); }; # generate the anon sub return $sub; # return it }

Comment on Why I hate File::Find and how I (hope I) fixed it
Download Code
Re (tilly) 1: Why I hate File::Find and how I (hope I) fixed it
by tilly (Archbishop) on Aug 30, 2001 at 18:11 UTC
    In fact you have hit on the classic way that you extend a functional system. Instead of passing around more information or creating global data, just move that action into creating a closure which binds the arguments up front for later use.

    For an example with many of these closures running around in parallel, see Re (tilly) 1: 5x5 Puzzle. (Look specifically at ret_toggle_square.) And a more general form of your make_wanted function would be:

    # Takes an anon function and a list of arguments. Returns # a closure which will call the anon function with those # arguments prepended to the argument list. sub bind_args { my ($sub, @args) = @_; return sub {$sub->(@args, @_);}; }

      Suppose you wanted to do something like this to a family of related objects. How would you assign the anonymous function to be a method of each class? (I can do that trick in JavaScript using ObjectName.prototpye.methodName, but how would I do that in Perl?)

      For example, what I would like to do is:

      ... sub ObjectToString { my $name = shift; return sub { my $s = $name . $self->getParametersAndValues(); return $s; } } ... package Fu; sub toString = ObjectToString("Fu"); ... package Bar; sub toString = ObjectToString("Bar"); ... package Baz; sub toString = ObjectToString("Baz"); ...

      Then I would use it like this:

      my ($foo, $bar, $baz); $foo = Fu->new(); # do stuff with foo... print $foo->toString(); $bar = Bar->new(); # do stuff with bar... print $bar->toString(); ...

      The obvious answer here is to use Inheritance™, but what if, for some reason, I don't want to use inheritance? Can I still accomplish what I want to?

        You would want to do a typeglob assignment. If you assign an anonymous function to a typeglob, you define that function. Like this:
        package Stringify; sub objectToString { my $name = shift; return sub { $name . (shift)->getParametersAndValues(); }; } # Time passes package Fu; *toString = Stringify::objectToString("Fu");
        But I should note that you can make this nicer with overload and an autogenerated method. For instance:
        package Stringify; use strict; use Carp; sub import { my $pkg = caller(); my $code = qq( # A trick for custom error reporting - see perlsyn. \n# line 1 "'Autogenerated for $pkg by Stringify'" package $pkg; use overload '""' => sub { __PACKAGE__ . (shift)->getParametersAndValues(); }; ); eval($code); if ($@) { confess("Cannot autogenerate stringification for package '$pkg': $ +@ The code is:\n$code"); } } 1;
        would allow you to simply:
        package Fu; use Stringify; # Use objects as strings, and they stringify
        If you want you could create an autogenerated function called toString and pass a reference to that function to overload.
Re: Why I hate File::Find and how I (hope I) fixed it
by blakem (Monsignor) on Aug 30, 2001 at 21:14 UTC
    merlyn has an article coming out in which he attempts to turn File::Find inside out. (i.e. with structure similiar to a filehandle iterator, rather than a functional callback) I don't know when its scheduled for publication, but you should keep an eye out for it. I think it address several of the concerns you raise here, in an an entirely different way.

    -Blake

Re: Why I hate File::Find and how I (hope I) fixed it
by clemburg (Curate) on Aug 30, 2001 at 21:46 UTC

    You can do better than just take fixed arguments:

    #!/usr/bin/perl -w use strict; use File::Find; sub make_wanted { my ($function, $get_argument_function) = @_; return sub { $function->($get_argument_function->()); } } sub delete_file_with_user_confirmation { my ($file_name, $user_input) = @_; if ($user_input =~ /yes/i) { unlink($file_name) or die "Error: no unlink for $file_name: $!"; } } sub get_user_input_for_file { # show full path to user print "Delete ", $File::Find::name, " (say 'yes' to delete)? "; my $user_input = <STDIN>; # but remember we chdir()ed into $File::Find::dir return $_, $user_input; } my @dirs = @ARGV ? @ARGV : ("."); find(make_wanted(\&delete_file_with_user_confirmation, \&get_user_input_for_file), @dirs);

    Christian Lemburg
    Brainbench MVP for Perl
    http://www.brainbench.com

Re: Why I hate File::Find and how I (hope I) fixed it
by Rudif (Hermit) on May 14, 2006 at 22:39 UTC
    mirod, you wrote

    So if I really need arguments, which happens quite often, then I have to use good ole globals. This is definitely _not_ what I'd call good coding practice. Plus how do I put this code in a module?

    I posted a snippet, OOFileFind, which is a simple-to-use answer to your plea. Please comment.

    Rudif

Re: Why I hate File::Find and how I (hope I) fixed it
by revdiablo (Prior) on May 14, 2006 at 23:04 UTC

    Quick pointer. You might want to take a look at File::Finder, which also does its business by generating wanted routines for you.

    Update: funny, I just noticed I replied to a node that's almost 5 years old.

Re: Why I hate File::Find and how I (hope I) fixed it
by Jenda (Abbot) on May 21, 2008 at 02:15 UTC

    Sorry for a very late reply to an old post.

    I do not see how is

    # create various wnated functions my $wanted= make_wanted( \&wanted_1, 'toto', 'tata'); find( $wanted, '.'); print "\n";
    better than
    find( sub{ wanted_1( 'toto', 'tata')}, '.'); print "\n";

    If you did want to play around I'd suggest something a bit different.

    use File::Find; sub callback (&) { my $sub = shift(); return sub { my @args = @_; return sub { $sub->(@args); } } } my $wanted = callback { my ($some, $params) = @_; print "wanted( $some, $params); \$_ = $_\n"; # ... }; find( $wanted->(1,2), 'd:\temp\copy'); find( $wanted->(20,19), 'd:\temp\copy');
    or even better
    #... *wanted = callback { my ($some, $params) = @_; print "wanted( $some, $params); \$_ = $_\n"; # ... }; find( wanted(1,2), 'd:\temp\copy'); find( wanted(20,19), 'd:\temp\copy');
    How do you like this one?

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlmeditation [id://109068]
Approved by root
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others about the Monastery: (7)
As of 2014-09-02 03:01 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    My favorite cookbook is:










    Results (18 votes), past polls