2009-08-24 19 views
7

Quindi ho una classe Perl. Ha un metodo di sort(), e voglio che sia più o meno identico a quello incorporato in sort() funzione:Domanda per ambito variabile Perl

$object->sort(sub ($$) { $_[0] <=> $_[1] }); 

Ma io non posso fare:

$object->sort(sub { $a <=> $b }); 

A causa di scoping. Ma il modulo List :: Util fa questo con reduce(). Ho esaminato il modulo List :: Util, e fanno cose piuttosto brutte con no strict 'vars' per far sì che ciò accada. Ci ho provato, ma senza risultato.

È a mia conoscenza che reduce() funzioni come funziona perché viene esportato nello spazio dei nomi appropriato, pertanto la mia classe non può farlo poiché la funzione è abbastanza saldamente in un altro spazio dei nomi. È corretto, o c'è un modo (indubbiamente più odioso e sconsiderato) di farlo nella mia situazione?

risposta

8

Beh, le altre due risposte sono entrambe a metà destra. Ecco una soluzione di lavoro che in realtà tipi:

package Foo; 

use strict; 
use warnings; 

sub sort { 
    my ($self, $sub) = @_; 

    my ($pkg) = caller; 
    my @x = qw(1 6 39 2 5); 
    print "@x\n"; 
    { 
     no strict 'refs'; 
     @x = sort { 
      local (${$pkg.'::a'}, ${$pkg.'::b'}) = ($a, $b); 
      $sub->(); 
     } @x; 
    } 
    print "@x\n"; 

    return; 
} 


package main; 

use strict; 
use warnings; 

my $foo = {}; 
bless $foo, 'Foo'; 

$foo->sort(sub { $a <=> $b }); 
# 1 6 39 2 5 
# 1 2 5 6 39 

Presumibilmente si sarebbe ordinare alcuni dati che in realtà è parte dell'oggetto.

È necessario la magia caller in modo che stai localizzare $a e $b nella confezione del chiamante, che è dove Perl è andare a guardare per loro. Sta creando variabili globali che esistono solo mentre viene chiamato quel sub.

Si noti che verrà visualizzato un "nome utilizzato una sola volta" con warnings; Sono sicuro che ci sono alcuni cerchi che puoi attraversare per evitarlo, in qualche modo.

+2

Potrebbe essere sufficiente per i tuoi scopi, ma è fragile. Non c'è alcuna garanzia che la funzione di confronto appartenga allo stesso pacchetto del chiamante del metodo 'sort'. Ecco dove arriva Sub :: Identify. – cjm

+0

@cjm - Questo è vero, e cercherò sicuramente in Sub :: Identify, ma il mio problema più grande è farlo funzionare, piuttosto che farlo funzionare nel caso generale. Le soluzioni specifiche sono migliori dei guasti generali. Tuttavia, combinare questa risposta con la tua mi darebbe una soluzione generale, che è una buona cosa. –

+1

Anche se il builtin 'sort' ha lo stesso problema. Si presuppone che la funzione di confronto provenga dallo stesso pacchetto del chiamante. Quindi se puoi conviverci, si salva una dipendenza su Sub :: Identify. (Oppure potresti richiedere Sub :: Identify condizionatamente e tornare a 'caller' se non è installato. Ma è più lavoro.) – cjm

1

È possibile utilizzare the local operator per impostare i valori per $a e $b per tutta la durata della chiamata subroutine:

sub sample 
{ 
    my $callback = shift; 
    for (my $i = 0; $i < @_; $i += 2) { 
     local ($a, $b) = @_[$i, $i + 1]; 
     $callback->(); 
    } 
}  

sample sub { print "$a $b\n" }, qw(a b c d e f g h i j); 

Se si dispone di una subroutine ordinaria, piuttosto che un metodo, allora si può fare che si tratti ancora di più come sort, quindi non è necessario utilizzare sub prima della funzione di richiamata. Utilizzare un prototipo sulla funzione:

sub sample (&@) 

quindi si chiama chiamare in questo modo:

sample { print "$a $b\n" } qw(a b c d e f g h i j); 

metodi, però, non sono influenzati da prototipi.

+0

Si sta chiedendo specificatamente un 'metodo', non un sub semplice. –

+3

Questo non funzionerà se chiami il metodo al di fuori della classe. Stai localizzando * la classe * $ a e $ b, non quella del chiamante. – cjm

+0

Ah. Pensavo di averlo visto, ma suppongo di no. La mia impressione da perlvar era che '$ a' e' $ b' erano abbastanza magici da "funzionare" in questa situazione. –

3

È possibile utilizzare Sub::Identify per scoprire il pacchetto (che chiama stash_name) associato al coder. Quindi imposta $ a e $ b in quel pacchetto come richiesto. Potrebbe essere necessario utilizzare no strict 'refs' nel metodo per farlo funzionare.

Ecco la risposta di Evee modificato per funzionare nel caso generale:

use strict; 
use warnings; 

package Foo; 

use Sub::Identify 'stash_name'; 

sub sort { 
    my ($self, $sub) = @_; 

    my $pkg = stash_name($sub); 
    my @x = qw(1 6 39 2 5); 
    print "@x\n"; 
    { 
     no strict 'refs'; 
     @x = sort { 
      local (${$pkg.'::a'}, ${$pkg.'::b'}) = ($a, $b); 
      $sub->(); 
     } @x; 
    } 
    print "@x\n"; 

    return; 
} 


package Sorter; 

sub compare { $a <=> $b } 

package main; 

use strict; 
use warnings; 

my $foo = {}; 
bless $foo, 'Foo'; 

$foo->sort(\&Sorter::compare); 

$foo->sort(sub { $b <=> $a }); 
+1

List :: Util usa solo 'caller'. Suppongo che non sia sufficiente, nel caso generale, vero? Se il chiamante passa una funzione da un altro pacchetto, allora List :: Util imposterà '$ a 'del chiamante piuttosto che la funzione. –

+0

La versione di List :: Util in 5.10.1 utilizza il codice XS che fondamentalmente fa la stessa cosa Sub :: Identify per capire il pacchetto a cui appartiene il coderef. – cjm

+0

In alternativa, potrei riscrivere il mio modulo in XS, perdendo così una dipendenza _e_ dandomi la possibilità di imparare XS. Tuttavia, nel frattempo, esaminerò questo. –