2010-08-24 10 views
5

Ho iniziato a imparare Ruby, e ho letto un paio di tutorial e ho persino comprato un libro ("Programming Ruby 1.9 - La guida dei programmatori pragmatici"), e mi sono imbattuto in qualcosa di nuovo che ho non ho mai visto in nessuna delle altre lingue che conosco (sto lavorando come sviluppatore web PHP).Blocks & Procs in Ruby

Blocchi & Proc. Penso di capire quello che sono, ma quello che non capisco è perché sono così grandi, e quando e perché dovrei usarli. Ovunque guardi, dicono che i blocchi e i proc sono una grande funzionalità in Ruby, ma non li capisco.

Qualcuno può dare a un novellino di Ruby come me alcune spiegazioni?

+0

Cerca ulteriori informazioni su Chiusure, ti aiuterà a spiegare come funzionano i blocchi e proc e cosa sono utili .. – Doon

risposta

13

Ci sono molte cose che vanno bene per i blocchi. Il pitch dell'ascensore: i blocchi passano intorno alle azioni allo nello stesso modo in cui normalmente passiamo intorno ai dati .

Il livello più ovvio è che consentono di estrapolare le cose in funzioni che altrimenti non sarebbero possibili.Per esempio, diamo un'occhiata a un caso comune in cui si dispone di una lista di cose e si desidera filtrare per includere solo gli elementi che corrispondono a qualche criterio:

int list[50] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50}; 
int evenNumbers[50] = {0}; 
int copyIndex = 0; 
for (int i = 0; i < 50; i++) { 
    if (list[i] % 2 == 0) { 
     evenNumbers[copyIndex++] = list[i]; 
    } 
} 

Ecco come si scrive che in Ruby:

list = 1..50 
listCopy = list.select {|n| n.even?} 

Tutto il lavoro comune è spostato fuori dal codice e in un metodo con un nome significativo. Non ci interessa copiare l'array e passare attraverso gli indici e tutto il resto - vogliamo solo un elenco filtrato. Ed è quello che ci offre select. Il blocco ci consente di passare la nostra logica personalizzata in questo metodo standard.

Ma gli iteratori non sono l'unico posto in cui questo "hole in the middle pattern" è utile. Ad esempio, se si passa un blocco a File.open, verrà aperto il file, eseguito il blocco con il file e quindi chiuso il file.

Un'altra cosa che ci blocca è una forma di richiami davvero potente. Ad esempio, senza blocchi, potremmo avere a che fare qualcosa di simile (in base a come le finestre di dialogo realmente funzionano in Objective-C Cocoa):

class Controller 
    def delete_button_clicked(item) 
    item.add_red_highlight 
    context = {:item => item} 
    dialog = Dialog.new("Are you sure you want to delete #{item}?") 
    dialog.ok_callback = :delete_OK 
    dialog.ok_receiver = self 
    dialog.cancel_callback = :cancel_delete 
    dialog.cancel_receiver = self 
    dialog.context = context 
    dialog.ask_for_confirmation 
    end 

    def delete_OK(sender) 
    delete(sender.context[:item]) 
    sender.dismiss 
    end 

    def cancel_delete(sender) 
    sender.context[:item].remove_red_highlight 
    sender.dismiss 
    end 
end 

Yowza. Con blocchi, potremmo fare questo, invece (sulla base di un modello comune utilizzato in molti librerie Ruby):

class Controller 
    def delete_button_clicked(item) 
    item.add_red_highlight 
    Dialog.ask_for_confirmation("Are you sure you want to delete #{item}?") do |response| 
     response.ok { delete item } 
     response.cancel { item.remove_red_highlight } 
    end 
    end 
end 

Che in realtà due livelli di blocchi - il do...end blocco e le due {} blocchi -style. Ma si legge abbastanza naturalmente, vero? Funziona perché un blocco acquisisce il contesto in cui è stato creato, quindi non è necessario passare intorno a self e item.

Per quanto riguarda i Proc, sono solo un object wrapper per i blocchi. Non molto per loro.

0

Questi concetti sono legati ai concetti dalla programmazione funzionale al rubino, quindi consente di utilizzare modelli e tecniche che si trovano solitamente nelle lingue, in cui le funzioni sono cittadini di prima classe.

+0

Non proprio, i blocchi in Ruby provengono da Smalltalk e in entrambe le lingue sono oggetti non funzioni, anche se in entrambi, sembrano e agiscono proprio come funzioni anonime. per esempio. Anche la sintassi è molto simile. Ad esempio, in smalltalk un blocco sarebbe 'a: = [: x | x + 1]' – OscarRyz

+0

@OscarRyz: qui sto selezionando i lendini, ma i blocchi in Ruby sono davvero funzioni e * non sono * oggetti. Ecco perché la gente dice che sono chiusure: le chiusure sono una specie di funzione. Ad esempio, non puoi scrivere 'totally_an_object = {| n | n * 7} '- questo è un errore di sintassi. Devi chiamare 'proc' con il blocco per ottenere un oggetto reale che rappresenta quella funzione. I blocchi erano oggetti veri in Smalltalk, ma la versione di Ruby è leggermente diversa – Chuck

2

I blocchi vengono utilizzati da molti metodi nelle classi Ruby e vengono utilizzati dove in PHP si utilizzerà una richiamata.

[1,2,3,4,5].each {|i| print "#{i} "} 

[1,2,3,4,5].each do |i| 
    print "#{i} " 
end 

File.open('p014constructs.rb', 'r') do |f1| 
    while line = f1.gets 
    puts line 
    end 
end 

PHP5 ha introdotto funzioni anonime; invece di usare un callback, potresti usare una funzione anonima.

echo preg_replace_callback('~-([a-z])~', function ($match) { 
    return strtoupper($match[1]); 
}, 'hello-world'); 
+0

Capisco i blocchi quando uso gli iteratori e così via, ma sono tutte le altre cose che non ottengo. (Per lo più procs, credo) – Nilks

+0

Aggiungi nella parte che i blocchi sono chiusure! (significa che è possibile accedere a variabili al di fuori dell'ambito del blocco, ovvero non è necessario creare un gruppo di strutture temporanee, ecc.) –

+0

cosa vuol dire Ruby 1.8 blocca "non avere variabili locali". Penso che troverai che non sei corretto. – horseyguy

2

sua importante per visualizzare i blocchi non come utilizzando metodi per iniziare un blocco di codice, in realtà si sta assumendo il blocco e di utilizzarlo come un parametro nella funzione.

Così, quando si utilizza il ogni metodo per iterare su un array in questo modo:

superOverFlowArray.each { |flow| puts flow } 

Stai inviando il blocco {| flusso | mette il flusso} in ciascun metodo. Il codice sta dicendo a Ruby di inviare il valore corrente della matrice a questo blocco al posto di | flusso |.

I processi prendono un blocco e li trasformano in una variabile. I Proc sono istanze di oggetti che contengono blocchi. I blocchi vengono passati come parametro al proc e vengono eseguiti quando si chiama il metodo 'call' su quell'istanza di Proc.

ecco un esempio Proc:

def category_and_title(category) 
    Proc.new { |title| "The title is: " + title + " in category: " + category } 
end 

myFirstTitle = category_and_title("Police Drama") 
mySecondTitle = category_and_title("Comedy") 

puts myFirstTitle.call("Law and Order") 
puts mySecondTitle.call("Seinfeld") 
puts myFirstTitle.call("CSI") 

La Proc ricorderà la categoria originale che è stato passato in, consentendo un modo conveniente di raggruppare i tipi.

0

Blocchi e proc consentono di estrarre piccoli bit di codice senza il sovraccarico completo e la complessità dei metodi.

Anche map di per sé è piuttosto potente, e jQuery è costruito attorno a questo stesso concetto:

['spite','rest','purpose'].map {|s| s << 'ful' } 

e si può gettare la logica in se necessario

['sprite','restful','luck'].map {|s| s << (s.end_with?('uck') ? 'i' : '') << 'ly' } 

V'è un operatore intelligente '& "significa" converti il ​​simbolo in un proc e lo chiami ", così puoi convertire stringhe di oggetti:

['spite',12,:purpose].map(&:to_s) 

E utilizzando le chiusure e combinando la scrittura di una semplice chiusura, possiamo trovare la posizione adiacente ai numeri. Questo è piuttosto goffo Ruby, ma più conciso rispetto maggior parte delle lingue:

last = -2 # this variable is accessed and changed within the closure 
    objs.sort.map do |o| 
    if (last + 1) != o 
     last = o 
     nil # not one more than previous so return nil 
    else 
     o 
    end 
    end.compact # compact removes nils 

Quando si inizia a riscriverlo in un'altra lingua ci si rende conto quanto sia conveniente questo è. Ti incoraggia a strutturare il tuo codice in operazioni più piccole, quindi è più riutilizzabile e verificabile.

1

Procs, noto anche come chiusure o lambda è un concetto in rubino che può sembrare confuso in un primo momento, soprattutto per un principiante. In breve, proc consente di passare facilmente i blocchi di codice.Un esempio, sotto

hello = Proc.new do |x, y| 
     puts "i am a proc" 
     sum = x+y 
     puts "sum: #{sum}" 
    end 

ora per fare uso di questo blocco di codice, basta chiamare il metodo "chiamata" su Ciao. Si noti che l'esempio precedente riceve gli argomenti x e y utilizzati dal blocco di codice. Quindi essere sicuri di passare gli argomenti quando si chiama l'oggetto Proc come ho fatto qui di seguito:

hello.call(2, 3) 

ottenendo i seguenti risultati:

i am a proc 
    sum: 5 

Evviva !! Quello era un modo semplice per creare un oggetto Proc. Quanto è utile questo? Bene, gli oggetti proc ti permettono di passare pezzi di codice. Un'illustrazione sotto lo spiega meglio facendo uso del proc creato nell'esempio sopra. Creiamo qualche classe casuale,

class SomeClass 
     def initialize(&block) 
     @block = block 
     end 

     def output_value(x, y) 
     @block.call(x, y) 
     end 
    end 

ora, creiamo un'istanza di SomeClass,

some_class = SomeClass.new(&hello) 

NOTA che il segno e commerciale "&" prima della ciao consente di passare un oggetto proc come discussione.

infine, l'uscita di un valore di lasciare passando 2 argomenti, come di seguito:

some_class.output_value(1, 3) 

il risultato che si ottiene è la seguente:

i am a proc 
    sum: 4 

See !! è così semplice. Sei stato in grado di passare un pezzo di codice in giro. I procs sono così utili che il rubino se ne serve molto. Spero che questo sia stato di grande aiuto :)

Problemi correlati