2009-04-18 13 views

risposta

101

Ruby yield non è un iteratore come in C# e Python. yield è in realtà un concetto molto semplice una volta capito come funzionano i blocchi in Ruby.

Sì, i blocchi sono una funzionalità di programmazione funzionale, anche se Ruby non è propriamente un linguaggio funzionale. In effetti, Ruby usa il metodo lambda per creare oggetti di blocco, presi in prestito dalla sintassi di Lisp per la creazione di funzioni anonime, ovvero quali sono i blocchi. Da un punto di vista informatico, i blocchi di Ruby (e le funzioni lambda di Lisp) sono closures.In Ruby, i metodi solitamente richiedono un solo blocco. (Puoi passare di più, ma è imbarazzante.)

La parola chiave yield in Ruby è solo un modo di chiamare un blocco che è stato assegnato a un metodo. Questi due esempi sono equivalenti:

def with_log 
    output = yield # We're calling our block here with yield 
    puts "Returned value is #{output}" 
end 

def with_log(&stuff_to_do) # the & tells Ruby to convert into 
          # an object without calling lambda 
    output = stuff_to_do.call # We're explicitly calling the block here 
    puts "Returned value is #{output}" 
end 

Nel primo caso, stiamo solo supponendo che esista un blocco e dica di chiamarlo. Nell'altro, Ruby avvolge il blocco in un oggetto e lo passa come argomento. Il primo è più efficiente e leggibile, ma è effettivamente lo stesso. Si potrebbe chiamare uno dei due in questo modo:

with_log do 
    a = 5 
    other_num = gets.to_i 
    @my_var = a + other_num 
end 

e sarebbe stampare il valore che si snodava fino ottenendo assegnato a @my_var. (OK, quindi è una funzione completamente stupida, ma penso che tu abbia l'idea.)

I blocchi sono usati per molte cose in Ruby. Quasi ogni posto in cui usi un ciclo in una lingua come Java, viene sostituito in Ruby con metodi che prendono blocchi. Ad esempio,

[1,2,3].each {|value| print value} # prints "123" 
[1,2,3].map {|value| 2**value} # returns [2, 4, 8] 
[1,2,3].reject {|value| value % 2 == 0} # returns [1, 3] 

Come Andrew ha notato, è anche comunemente usato per aprire file e molti altri luoghi. Fondamentalmente ogni volta che si dispone di una funzione standard che potrebbe utilizzare alcune logiche personalizzate (come ordinare un array o elaborare un file), si utilizzerà un blocco. Ci sono anche altri usi, ma questa risposta è già così lunga che temo possa causare attacchi di cuore a lettori con costituzioni più deboli. Spero che questo chiarisca la confusione su questo argomento.

+2

Bella spiegazione. –

+3

Grazie, ha molto più senso, e si lega di più a ciò che ho imparato finora sui blocchi. – hbw

+0

Grazie. Questo ha aiutato. – jjohn

2

Penso che l'istruzione yield sia originata dalla lingua CLU. Mi chiedo sempre se il personaggio di Tron è stato chiamato dopo CLU troppo ....

+2

Al poster originale: apropros della risposta di Daniel, si potrebbe voler google per "coroutine" - questo era il concetto di "informatica" sottostante che CLU implementato utilizzando rendimento. – itowlson

0

Penso che 'coroutine' è la parola chiave che stai cercando.

E.g. http://en.wikipedia.org/wiki/Yield

Resa in informatica e scienze dell'informazione:

  • in informatica, un punto di ritorno (e rientro) di un coroutine
+0

Credito anche a @itowlson, che ha citato contemporaneamente "coroutine" in un commento su un'altra risposta. – Brian

+5

L'utilizzo della parola chiave yield in Ruby non ha assolutamente nulla a che fare con la normale definizione CS di rendimento. È solo una chiamata di subroutine. In effetti, puoi semplicemente usare call al posto di yield, se assegni il blocco anonimo a una variabile. –

+0

Questo non è l'uso in Ruby. – Chuck

6

C'è di più a cedere e blocchi di una semplice loop .

La serie Enumerating enumerable ha una serie di cose che puoi fare con le enumerazioni, come chiedere se una dichiarazione è vera per qualsiasi membro di un gruppo, o se è vera per tutti i membri, o cercare qualcuno o tutti i membri che si incontrano una certa condizione

I blocchi sono anche utili per l'ambito variabile. Piuttosto che essere semplicemente conveniente, può aiutare con un buon design. Ad esempio, il codice

File.open("filename", "w") do |f| 
    f.puts "text" 
end 

assicura che il flusso di file viene chiuso quando hai finito con esso, anche se si verifica un'eccezione, e che la variabile è fuori del campo di applicazione, una volta che hai finito con esso.

Un google occasionale non ha trovato un buon post sul blog su blocchi e rendimenti in ruby. Non so perché.

risposta al commento:

Ho il sospetto che viene chiuso a causa della fine del blocco, non perché la variabile va fuori del campo di applicazione.

La mia comprensione è che non accade nulla di speciale quando l'ultima variabile che punta a un oggetto non rientra nello scope, a parte che l'oggetto è idoneo per la garbage collection. Non so come confermare questo, però.

Posso mostrare che l'oggetto file viene chiuso prima che venga eliminato il garbage collector, che in genere non avviene immediatamente. Nell'esempio seguente, è possibile vedere che un oggetto file viene chiuso nella seconda istruzione puts, ma non è stato garbage collector.

g = nil 
File.open("/dev/null") do |f| 
    puts f.inspect # #<File:/dev/null> 
    puts f.object_id # Some number like 70233884832420 
    g = f 
end 
puts g.inspect # #<File:/dev/null (closed)> 
puts g.object_id # The exact same number as the one printed out above, 
    # indicating that g points to the exact same object that f pointed to 
+0

Come fa a garantire che il file venga chiuso al termine? Ruby lo chiude automaticamente quando termina il blocco e 'f' cade fuori campo? – aidan

+3

@aidan Il metodo 'File # open', quando chiamato con un blocco, chiuderà l'handle del file creato una volta che il blocco è terminato. – qqx

+0

Grazie per il chiarimento! – aidan