Beefy Boxes and Bandwidth Generously Provided by pair Networks
Problems? Is your data what you think it is?
 
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
Replies are listed 'Best First'.
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 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 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 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 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 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 chilling in the Monastery: (4)
As of 2015-07-28 03:51 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    The top three priorities of my open tasks are (in descending order of likelihood to be worked on) ...









    Results (252 votes), past polls