2010-09-30 8 views
11

Supponiamo di avere una libreria di utilità (other) contenente una subroutine (sort_it) che voglio utilizzare per restituire dati ordinati in modo arbitrario. E 'probabilmente più complicato di questo, ma questo illustra i concetti chiave:

#!/usr/local/bin/perl 

use strict; 

package other; 

sub sort_it { 
    my($data, $sort_function) = @_; 

    return([sort $sort_function @$data]); 
} 

Ora cerchiamo di utilizzarlo in un altro pacchetto.

package main; 
use Data::Dumper; 

my($data) = [ 
     {'animal' => 'bird',   'legs' => 2}, 
     {'animal' => 'black widow',  'legs' => 8}, 
     {'animal' => 'dog',    'legs' => 4}, 
     {'animal' => 'grasshopper',  'legs' => 6}, 
     {'animal' => 'human',   'legs' => 2}, 
     {'animal' => 'mosquito',  'legs' => 6}, 
     {'animal' => 'rhino',   'legs' => 4}, 
     {'animal' => 'tarantula',  'legs' => 8}, 
     {'animal' => 'tiger',   'legs' => 4}, 
     ], 

my($sort_by_legs_then_name) = sub { 
    return ($a->{'legs'} <=> $b->{'legs'} || 
      $a->{'animal'} cmp $b->{'animal'}); 
}; 

print Dumper(other::sort_it($data, $sort_by_legs_then_name)); 

Questo non funziona, a causa di un problema sottile. $a e $b sono pacchetti globali . Si riferiscono a $main::a e $main::b quando sono chiusi nella chiusura .

Potremmo risolvere questo problema dicendo, invece:

my($sort_by_legs_then_name) = sub { 
    return ($other::a->{'legs'} <=> $other::b->{'legs'} || 
      $other::a->{'animal'} cmp $other::b->{'animal'}); 
}; 

Questo funziona, ma ci costringe a codificare il nome del nostro pacchetto di utilità ovunque. Se dovessimo cambiare, dovremmo ricordare di cambiare il codice , non solo la dichiarazione use other qw(sort_it); che probabilmente sarebbe presente nel mondo reale con .

Si potrebbe pensare immediatamente di provare a utilizzare __PACKAGE__. Ciò rafforza la valutazione di in "main". Così fa eval("__PACKAGE__");.

C'è un trucco usando caller che funziona:

my($sort_by_legs_then_name) = sub { 
    my($context) = [caller(0)]->[0]; 
    my($a) = eval("\$$context" . "::a"); 
    my($b) = eval("\$$context" . "::b"); 

    return ($a->{'legs'} <=> $b->{'legs'} || 
      $a->{'animal'} cmp $b->{'animal'}); 
}; 

Ma questo è piuttosto nero-magica. Sembra che ci dovrebbe essere una soluzione migliore a questo. Ma non l'ho ancora trovato o non l'ho ancora immaginato .

+1

Se si utilizza chiamante in quel modo, non si romperà altrettanto se il pacchetto che ha definito il sub e il pacchetto che chiamano altri :: sort_it sono diversi? – aschepler

risposta

9

Utilizzare il prototipo (soluzione originariamente proposta in Usenet posting per ysth).

Funziona su Perl> = 5.10.1 (non sono sicuro di prima).

my($sort_by_legs_then_name) = sub ($$) { 
    my ($a1,$b1) = @_; 
    return ($a1->{'legs'} <=> $b1->{'legs'} || 
      $a1->{'animal'} cmp $b1->{'animal'}); 
}; 

ottengo come risultato:

$VAR1 = [ 
     { 
     'legs' => 2, 
     'animal' => 'bird' 
     }, 
     { 
     'legs' => 2, 
     'animal' => 'human' 
     }, 
     { 
     'legs' => 4, 
     'animal' => 'dog' 
     }, 
     { 
     'legs' => 4, 
     'animal' => 'rhino' 
     }, 
     { 
     'legs' => 4, 
     'animal' => 'tiger' 
     }, 
     { 
     'legs' => 6, 
     'animal' => 'grasshopper' 
     }, 
     { 
     'legs' => 6, 
     'animal' => 'mosquito' 
     }, 
     { 
     'legs' => 8, 
     'animal' => 'black widow' 
     }, 
     { 
     'legs' => 8, 
     'animal' => 'tarantula' 
     } 
    ]; 
+0

Mi chiedo se Perl6 :: Segnaposto funzionerebbe pure? (http://search.cpan.org/~lpalmer/Perl6-Placeholders-0.07/lib/Perl6/Placeholders.pm) – DVK

+4

La modifica è stata apportata in [Perl 5.6] (http://search.cpan.org/~ gsar/perl-5.6.0/pod/perldelta.pod # Enhanced_support_for_sort% 28% 29_subroutines). C'è una [pena di esibizione documentata] (http://perldoc.perl.org/functions/sort.html) per farlo comunque. –

+3

La penalizzazione delle prestazioni non è così male rispetto all'utilizzo di una subroutine anonima, ma entrambe sono significativamente più lente rispetto all'utilizzo di un blocco: http://gist.github.com/603932 Questo è un senario in cui l'astrazione potrebbe non essere tua amica. –

0

Ecco come fare:

sub sort_it { 
    my ($data, $sort) = @_; 
    my $caller = caller; 
    eval "package $caller;" # enter caller's package 
     . '[sort $sort @$data]' # sort at full speed 
     or die [email protected]    # rethrow any errors 
} 

eval è necessario qui perché package richiede solo un nome di pacchetto a nudo, non è una variabile .

3

Prova questo:

sub sort_it { 
    my($data, $sort_function) = @_; 
    my($context) = [caller(0)]->[0]; 
    no strict 'refs'; 
    local *a = "${context}::a"; 
    local *b = "${context}::b"; 
    return([sort $sort_function @$data]); 
} 

E non si paga in testa in ogni chiamata.

Ma io preferirei

sub sort_it (&@) { 
    my $sort_function = shift; 
    my($context) = [caller(0)]->[0]; 
    no strict 'refs'; 
    local *a = "${context}::a"; 
    local *b = "${context}::b"; 
    return([sort $sort_function @_]); 
} 
Problemi correlati