A further level of abstraction and optimization can be provided by custom-compiling a subroutine optimized to perform the necessary task:
use strict;
use UNIVERSAL 'isa';
sub build_deep_sub {
my %options = @_;
my $sub = q/ sub / . ( $options{proto} ? "($options{proto})" : '' )
+. q/ {
/ . ( $options{init} ) . q/
my ( @results, %seen );
while ( scalar @_ ) {
my $target = shift;
/ . ( $options{guard} ? "next unless grep { $options{guard} } \$
+target;" : "" ) . q/
push @results, / . ( $options{map} ) . q/ $target;
/ . ( $options{post_order} ? 'push' : 'unshift' ) . q/ @_,
/ . ( $options{guard} ? " grep { $options{guard} }" : "" ) . q/
$seen{$target} ++ ? () :
isa($target, 'HASH') ? %$target :
isa($target, 'ARRAY') ? @$target :
isa($target, 'REF') ? $$target :
isa($target, 'SCALAR') ? $$target : ();
}
@results;
} /;
eval $sub or die "$@: $sub";
}
my $type_search = build_deep_sub( init => 'my $type = shift;', map =>
+'grep isa( $_, $type ), ', guard => 'ref($_)' );
my @loggers = $type_search->( 'Log', $target );
The speed improvement of eliminating a round of subroutine calls will outway the one-time cost of the string eval unless your data structure to be searched is very small.
A library based on this approach could pre-generate a number of generally useful subroutines:
*{'deep_walk'} = build_deep_sub();
*{'deep_map'} = build_deep_sub( init => 'my $test = shift;', map =>
+'map $test->($_),' );
*{'deep_grep'} = build_deep_sub( init => 'my $test = shift;', map =>
+ 'grep $test->($_),' );
*{'deep_grep_refs'} = build_deep_sub( init => 'my $test = shift;', m
+ap => 'grep $test->($_),', guard => 'ref($_)' );
*{'deep_grep_type'} = build_deep_sub( init => 'my $type = shift;', m
+ap => 'grep isa( $_, $type ), ', guard => 'ref($_)' );
(For what it's worth, the prototypes generated above don't seem to be quite right, but I'm sure that could be figured out with a bit of experimentation.)