2012-01-16 10 views
8

Sul (molto apprezzato) perlmonks sito, ho trovato il following snippet that trims gli spazi da entrambi i lati di una stringa:In Perl, una funzione dovrebbe eseguire la danza wantarray o possiamo aspettarci che il chiamante utilizzi la mappa?

sub trim { 
    @_ = $_ if not @_ and defined wantarray; 
    @_ = @_ if defined wantarray; 
    for (@_ ? @_ : $_) { s/^\s+//, s/\s+$// } 
    return wantarray ? @_ : $_[0] if defined wantarray; 
} 

Non capisco il motivo per cui l'autore va a tutti i problemi di controllo wantarray quasi ogni riga . Perché non tagliare la stringa e il programmatore usa map quando passa una matrice?

Qual è la differenza tra questo assetto, chiamato in questo modo:

my @test_array = ('string1', ' string2', 'string3 ', ' string4 '); 
my @result = trim(@test_array); 

O un semplice assetto, chiamato come questo, quando si ha la necessità di tagliare un array:

my @test_array = ('string1', ' string2', 'string3 ', ' string4 '); 
my @result = map { trim($_) } @test_array; 
+0

perché vuoi lasciare la mappa all'utente mentre la puoi estrarre? –

+1

correlati: [ritaglia in Perl] (https://plus.google.com/105725977711317285348/posts/ienzxqHJmRe) – daxim

+0

wantarray e simili merda magica sono una piaga. Non fare una programmazione intelligente a meno che non sia necessario. Mai a API intelligenti. – tsee

risposta

11

Prima di tutto è meglio se si astratta che mappa via:

#e.1. 
sub trim 
{ 
    my @ar = @_; 
    for (@ar) { s/^\s+//, s/\s+$// }; 
    return wantarray ? @ar : $ar[0]; 
} 

In secondo luogo, si consideri l'esempio di cui sopra e la confronta con:

#e.2. 
sub trim 
{ 
    for (@_) { s/^\s+//, s/\s+$// }; 
} 

qual è il differenza?

e.1. restituisce un nuovo array tagliato, mentre e.2. modifica l'array originale.

Ok ora, cosa fa la subroutine criptica originale?

Si auto-magicamente (yeaah, è Perl) modifica la matrice originale se non si assegna il valore di ritorno a qualcosa o esce l'array originale intatta e restituisce un nuovo array rifilato se si sta assegnando il valore di ritorno a un'altra variabile.

Come?

Controllando per vedere se wantarray è definito affatto. finché la funzione è sul lato destro e il valore di ritorno è assegnato a una variabile "wantarray definito" è vero (indipendentemente dal contesto scalare/matrice).

+2

L'autore desidera anche che la funzione operi su '$ _' implicitamente se non vengono passati argomenti alla funzione. – mob

+0

Pertanto, si consiglia di far sì che ciascuna funzione sia in grado di accettare sia LIST che SCALAR, in modo che il chiamante non debba scrivere la mappa da solo? Mi piace l'idea, ma due cose mi hanno impedito: 1. il sovraccarico quando non necessario; 2. il copia-incolla del codice (4 righe anziché 1) solo per gestire il contesto void/scalare/lista. – Konerak

+0

non accettare (comunque i parametri andranno tutti @ nella subroutine) ma per tornare. e può essere raggiunto con return wantarray? @ar: $ ar [0]; dichiarazione. il 4-liner originale sta facendo molto di più che si occupa di scalare/lista come ho cercato di spiegare sopra. –

5

Probabilmente l'autore ha voluto per imitare il comportamento della funzione standard chomp. Non è necessario farlo nella tua funzione.

man perlfunc 
chomp VARIABLE 
chomp(LIST) 
chomp 

[...] Se si chomp una lista, ogni elemento è chomp. [...]

+0

immagino che wantarray non abbia nulla a che fare con questo tipo di comportamento in chomp –

+0

chomp sta modificando l'array originale in modo che possa essere semplice come: sub chomp {per (@_) s/blah/blah /} –

+0

Sì, sei destra. Infatti 'wantarray' viene definito quando il chiamante si aspetta uno scalare. – kubanczyk

1

Si noti che questo è esattamente il modo in cui è implementato Text::Trim. Vedi la sua descrizione su vari casi d'uso. Riproduce con wantarray consente di distinguere vari contesti e implementare semantica diversa per ciascuno.

Personalmente preferisco solo semantica singola in quanto è più facile da capire e da usare. Eviterei di usare la variabile di default $_ o la modifica in atto, in linea con l'esempio 1 di Nylon Smile.

7

Rompere questa linea per linea in quanto non è stato:

sub trim { 
    @_ = $_ if not @_ and defined wantarray; 
    # if there are no arguments, but a return value is requested 
    # then place a copy of $_ into @_ to work on 

    @_ = @_ if defined wantarray; 
    # if the caller expects a return value, copy the values in @_ into itself 
    # (this breaks the aliasing to the caller's variables) 

    for (@_ ? @_ : $_) { s/^\s+//, s/\s+$// } 
    # perform the substitution, in place, on either @_ or $_ depending on 
    # if arguments were passed to the function 

    return wantarray ? @_ : $_[0] if defined wantarray; 
    # if called in list context, return @_, otherwise $_[0] 
} 

Sono d'accordo che il codice diventa un po 'noiosa, con tutti i wantarray controlli, ma il risultato è una funzione che condivide un livello di flessibilità con le funzioni integrate di Perl. Il risultato netto di rendere la funzione "intelligente" è quello di ripulire il sito di chiamata (evitando costrutti di ciclo, variabili temporanee, ripetizione, ...) che a seconda della frequenza utilizzata dalla funzione può migliorare significativamente la leggibilità del codice.

La funzione potrebbe essere semplificata un po ':

sub trim { 
    @_ = @_ ? @_ : $_ if defined wantarray; 

    s/^\s+//, s/\s+$// for @_ ? @_ : $_; 

    wantarray ? @_ : shift 
} 

Le prime due righe possono essere riuniti in un unico, dal momento che stanno facendo la stessa cosa (assegnando a @_) solo con i valori di origine diverse. E non è necessario il controllo esterno return ... if defined wantarray alla fine, poiché restituire un valore nel contesto vuoto non fa comunque nulla.

Ma probabilmente cambierei l'ultima riga in wantarray ? @_ : pop in quanto ciò lo fa comportarsi come una lista (ultimo elemento in contesto scalare).

Una volta che tutto è detto e fatto, questo permette gli stili seguenti chiamata da utilizzare:

my @test_array = ('string1', ' string2', 'string3 ', ' string4 '); 

my @result = trim @test_array; 
my $result = trim $test_array[0]; 
trim @test_array; # in place trim 

e anche supporta ancora il ciclo sito chiamata:

my @result = map trim, @test_array; 

o più verbosely come:

my @result = map trim($_), @test_array; 

e può essere utilizzato all'interno di un ciclo while simile a chomp

while (<$file_handle>) { 
    trim; 
    # do something 
} 

Le opinioni sulla birra in Perl sono miste. Personalmente mi piace quando le funzioni mi danno la flessibilità di codificare il chiamante in un modo che ha senso, piuttosto che aggirare l'interfaccia rigida di una funzione.

Problemi correlati