Sto cercando di capire i blocchi yield
e come funzionano in Ruby.
Come si yield
usa Molte delle applicazioni di Rails che ho visto usare yield
in modo strano.
Qualcuno può spiegarmi o mostrarmi dove andare per capirli?
Sto cercando di capire i blocchi yield
e come funzionano in Ruby.
Come si yield
usa Molte delle applicazioni di Rails che ho visto usare yield
in modo strano.
Qualcuno può spiegarmi o mostrarmi dove andare per capirli?
Risposte:
Sì, all'inizio è un po 'sconcertante.
In Ruby, i metodi possono ricevere un blocco di codice per eseguire segmenti arbitrari di codice.
Quando un metodo prevede un blocco, lo invoca chiamando la yield
funzione.
Questo è molto utile, ad esempio, per scorrere su un elenco o per fornire un algoritmo personalizzato.
Prendi il seguente esempio:
Definirò una Person
classe inizializzata con un nome e fornirò un do_with_name
metodo che, se invocato, passerebbe semplicemente l' name
attributo al blocco ricevuto.
class Person
def initialize( name )
@name = name
end
def do_with_name
yield( @name )
end
end
Questo ci permetterebbe di chiamare quel metodo e passare un blocco di codice arbitrario.
Ad esempio, per stampare il nome dovremmo fare:
person = Person.new("Oscar")
#invoking the method passing a block
person.do_with_name do |name|
puts "Hey, his name is #{name}"
end
Stampa:
Hey, his name is Oscar
Nota, il blocco riceve, come parametro, una variabile chiamata name
(NB puoi chiamare questa variabile come preferisci, ma ha senso chiamarla name
). Quando il codice invoca yield
riempie questo parametro con il valore di @name
.
yield( @name )
Potremmo fornire un altro blocco per eseguire un'azione diversa. Ad esempio, invertire il nome:
#variable to hold the name reversed
reversed_name = ""
#invoke the method passing a different block
person.do_with_name do |name|
reversed_name = name.reverse
end
puts reversed_name
=> "racsO"
Abbiamo usato esattamente lo stesso metodo ( do_with_name
) - è solo un blocco diverso.
Questo esempio è banale. Usi più interessanti sono filtrare tutti gli elementi in un array:
days = ["monday", "tuesday", "wednesday", "thursday", "friday"]
# select those which start with 't'
days.select do | item |
item.match /^t/
end
=> ["tuesday", "thursday"]
Oppure, possiamo anche fornire un algoritmo di ordinamento personalizzato, ad esempio basato sulla dimensione della stringa:
days.sort do |x,y|
x.size <=> y.size
end
=> ["monday", "friday", "tuesday", "thursday", "wednesday"]
Spero che questo ti aiuti a capirlo meglio.
A proposito, se il blocco è opzionale dovresti chiamarlo come:
yield(value) if block_given?
Se non è facoltativo, basta invocarlo.
MODIFICARE
@hmak ha creato un repl.it per questi esempi: https://repl.it/@makstaks/blocksandyieldsrubyexample
racsO
se the_name = ""
"Oscar"
(non è molto chiara nella risposta)
person.do_with_name {|string| yield string, something_else }
In Ruby, i metodi possono verificare se sono stati chiamati in modo tale che sia stato fornito un blocco in aggiunta ai normali argomenti. In genere questo viene fatto usando il block_given?
metodo ma è anche possibile fare riferimento al blocco come Proc esplicito anteponendo una e commerciale ( &
) prima del nome dell'argomento finale.
Se un metodo viene invocato con un blocco, il metodo può yield
controllare il blocco (chiamare il blocco) con alcuni argomenti, se necessario. Considera questo metodo di esempio che dimostra:
def foo(x)
puts "OK: called as foo(#{x.inspect})"
yield("A gift from foo!") if block_given?
end
foo(10)
# OK: called as foo(10)
foo(123) {|y| puts "BLOCK: #{y} How nice =)"}
# OK: called as foo(123)
# BLOCK: A gift from foo! How nice =)
Oppure, usando la sintassi dell'argomento blocco speciale:
def bar(x, &block)
puts "OK: called as bar(#{x.inspect})"
block.call("A gift from bar!") if block
end
bar(10)
# OK: called as bar(10)
bar(123) {|y| puts "BLOCK: #{y} How nice =)"}
# OK: called as bar(123)
# BLOCK: A gift from bar! How nice =)
È possibile che qualcuno fornisca una risposta davvero dettagliata qui, ma ho sempre trovato questo post di Robert Sosinski come una grande spiegazione delle sottigliezze tra blocchi, proc e lambda.
Dovrei aggiungere che credo che il post a cui mi sto collegando sia specifico per ruby 1.8. Alcune cose sono cambiate in ruby 1.9, come ad esempio le variabili di blocco che sono locali al blocco. In 1.8, otterrai qualcosa di simile al seguente:
>> a = "Hello"
=> "Hello"
>> 1.times { |a| a = "Goodbye" }
=> 1
>> a
=> "Goodbye"
Considerando che 1.9 ti darebbe:
>> a = "Hello"
=> "Hello"
>> 1.times { |a| a = "Goodbye" }
=> 1
>> a
=> "Hello"
Non ho 1.9 su questa macchina, quindi sopra potrebbe esserci un errore.
Volevo aggiungere il motivo per cui avresti fatto le cose in quel modo alle già ottime risposte.
Non ho idea di quale lingua tu stia provando, ma supponendo che sia un linguaggio statico, questo genere di cose sembrerà familiare. Ecco come leggere un file in Java
public class FileInput {
public static void main(String[] args) {
File file = new File("C:\\MyFile.txt");
FileInputStream fis = null;
BufferedInputStream bis = null;
DataInputStream dis = null;
try {
fis = new FileInputStream(file);
// Here BufferedInputStream is added for fast reading.
bis = new BufferedInputStream(fis);
dis = new DataInputStream(bis);
// dis.available() returns 0 if the file does not have more lines.
while (dis.available() != 0) {
// this statement reads the line from the file and print it to
// the console.
System.out.println(dis.readLine());
}
// dispose all the resources after using them.
fis.close();
bis.close();
dis.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Ignorando l'intera cosa del concatenamento del flusso, l'idea è questa
Ecco come lo fai in rubino
File.open("readfile.rb", "r") do |infile|
while (line = infile.gets)
puts "#{counter}: #{line}"
counter = counter + 1
end
end
Selvaggiamente diversa. Abbattere questo
Qui, invece di gestire i passaggi uno e due, in pratica lo deleghi in un'altra classe. Come puoi vedere, questo riduce drasticamente la quantità di codice che devi scrivere, il che rende le cose più facili da leggere e riduce le possibilità di cose come perdite di memoria o blocchi di file che non vengono cancellati.
Ora, non è che non puoi fare qualcosa di simile in Java, infatti, la gente lo fa da decenni. Si chiama modello di strategia . La differenza è che senza blocchi, per qualcosa di semplice come l'esempio di file, la strategia diventa eccessiva a causa della quantità di classi e metodi che è necessario scrivere. Con i blocchi, è un modo così semplice ed elegante di farlo, che non ha senso NON strutturare il codice in quel modo.
Questo non è l'unico modo in cui vengono utilizzati i blocchi, ma gli altri (come il modello Builder, che puoi vedere nel form_per api in rotaie) sono abbastanza simili che dovrebbe essere ovvio cosa sta succedendo una volta avvolto la testa attorno a questo. Quando vedi i blocchi, di solito è sicuro supporre che la chiamata al metodo sia ciò che vuoi fare, e il blocco sta descrivendo come lo vuoi fare.
File.readlines("readfile.rb").each_with_index do |line, index| puts "#{index + 1}: #{line}" end
e ridiamo ancora di più ai ragazzi di Java.
IO.foreach('readfile.rb').each_with_index { |line, index| puts "#{index}: #{line}" }
(più nessun problema di memoria)
Ho trovato questo articolo molto utile. In particolare, il seguente esempio:
#!/usr/bin/ruby
def test
yield 5
puts "You are in the method test"
yield 100
end
test {|i| puts "You are in the block #{i}"}
test do |i|
puts "You are in the block #{i}"
end
che dovrebbe dare il seguente risultato:
You are in the block 5
You are in the method test
You are in the block 100
You are in the block 5
You are in the method test
You are in the block 100
Quindi essenzialmente ogni volta che viene effettuata una chiamata a yield
ruby verrà eseguito il codice nel do
blocco o all'interno {}
. Se viene fornito un parametro, yield
questo verrà fornito come parametro al do
blocco.
Per me, questa è stata la prima volta che ho capito davvero cosa do
stavano facendo i blocchi. È fondamentalmente un modo per la funzione di dare accesso a strutture dati interne, sia per iterazione che per configurazione della funzione.
Quindi quando su rotaie scrivi quanto segue:
respond_to do |format|
format.html { render template: "my/view", layout: 'my_layout' }
end
Questo eseguirà la respond_to
funzione che produce il do
blocco con il format
parametro (interno) . Quindi si chiama la .html
funzione su questa variabile interna che a sua volta produce il blocco di codice per eseguire il render
comando. Si noti che .html
produrrà solo se è il formato di file richiesto. (tecnicità: queste funzioni in realtà block.call
non usano yield
come si può vedere dalla fonte ma la funzionalità è essenzialmente la stessa, vedere questa domanda per una discussione.) Questo fornisce un modo per la funzione di eseguire alcune inizializzazioni quindi prendere input dal codice chiamante e quindi continuare l'elaborazione se necessario.
O in altri termini, è simile a una funzione che prende una funzione anonima come argomento e poi la chiama in javascript.
In Ruby, un blocco è sostanzialmente un pezzo di codice che può essere passato ed eseguito con qualsiasi metodo. I blocchi vengono sempre utilizzati con i metodi, che in genere forniscono loro dati (come argomenti).
I blocchi sono ampiamente utilizzati nelle gemme di Ruby (incluso Rails) e nel codice Ruby ben scritto. Non sono oggetti, quindi non possono essere assegnati a variabili.
Un blocco è un pezzo di codice racchiuso tra {} o do..end. Per convenzione, la sintassi parentesi graffa deve essere utilizzata per i blocchi a linea singola e la sintassi do..end per i blocchi a più linee.
{ # This is a single line block }
do
# This is a multi-line block
end
Qualsiasi metodo può ricevere un blocco come argomento implicito. Un blocco viene eseguito dall'istruzione yield in un metodo. La sintassi di base è:
def meditate
print "Today we will practice zazen"
yield # This indicates the method is expecting a block
end
# We are passing a block as an argument to the meditate method
meditate { print " for 40 minutes." }
Output:
Today we will practice zazen for 40 minutes.
Quando viene raggiunta l'istruzione yield, il metodo meditate fornisce il controllo al blocco, il codice all'interno del blocco viene eseguito e il controllo viene restituito al metodo, che riprende l'esecuzione immediatamente dopo l'istruzione yield.
Quando un metodo contiene una dichiarazione di rendimento, si aspetta di ricevere un blocco al momento della chiamata. Se non viene fornito un blocco, verrà generata un'eccezione una volta raggiunta la dichiarazione di rendimento. Possiamo rendere facoltativo il blocco ed evitare che venga sollevata un'eccezione:
def meditate
puts "Today we will practice zazen."
yield if block_given?
end meditate
Output:
Today we will practice zazen.
Non è possibile passare più blocchi a un metodo. Ogni metodo può ricevere solo un blocco.
Vedi di più su: http://www.zenruby.info/2016/04/introduction-to-blocks-in-ruby.html
A volte uso "yield" in questo modo:
def add_to_http
"http://#{yield}"
end
puts add_to_http { "www.example.com" }
puts add_to_http { "www.victim.com"}
Logger
che non deve svolgere alcuna attività se l'utente non ne ha bisogno. Dovresti spiegare il tuo però ...
Ci sono due punti che voglio fare riguardo alla resa qui. Innanzitutto, mentre molte risposte qui parlano di diversi modi per passare un blocco a un metodo che utilizza il rendimento, parliamo anche del flusso di controllo. Ciò è particolarmente rilevante poiché è possibile assegnare MOLTIPLE volte a un blocco. Diamo un'occhiata a un esempio:
class Fruit
attr_accessor :kinds
def initialize
@kinds = %w(orange apple pear banana)
end
def each
puts 'inside each'
3.times { yield (@kinds.tap {|kinds| puts "selecting from #{kinds}"} ).sample }
end
end
f = Fruit.new
f.each do |kind|
puts 'inside block'
end
=> inside each
=> selecting from ["orange", "apple", "pear", "banana"]
=> inside block
=> selecting from ["orange", "apple", "pear", "banana"]
=> inside block
=> selecting from ["orange", "apple", "pear", "banana"]
=> inside block
Quando viene invocato ogni metodo, viene eseguito riga per riga. Ora quando arriviamo al blocco 3.times, questo blocco verrà invocato 3 volte. Ogni volta che invoca la resa. Tale rendimento è collegato al blocco associato al metodo che ha chiamato ciascun metodo. È importante notare che ogni volta che viene invocato il rendimento, si restituisce il controllo al blocco di ciascun metodo nel codice client. Al termine dell'esecuzione, il blocco ritorna al blocco 3.times. E questo succede 3 volte. Quindi quel blocco nel codice client viene invocato in 3 occasioni separate poiché il rendimento viene esplicitamente chiamato 3 volte separate.
Il mio secondo punto riguarda enum_for e yield. enum_for crea un'istanza della classe Enumerator e anche questo oggetto Enumerator risponde al rendimento.
class Fruit
def initialize
@kinds = %w(orange apple)
end
def kinds
yield @kinds.shift
yield @kinds.shift
end
end
f = Fruit.new
enum = f.to_enum(:kinds)
enum.next
=> "orange"
enum.next
=> "apple"
Quindi nota ogni volta che invochiamo i tipi con l'iteratore esterno, invocherà il rendimento solo una volta. La prossima volta che lo chiameremo, invocherà il prossimo rendimento e così via.
C'è un bocconcino interessante per quanto riguarda enum_for. La documentazione online afferma quanto segue:
enum_for(method = :each, *args) → enum
Creates a new Enumerator which will enumerate by calling method on obj, passing args if any.
str = "xyz"
enum = str.enum_for(:each_byte)
enum.each { |b| puts b }
# => 120
# => 121
# => 122
Se non specifichi un simbolo come argomento su enum_for, ruby aggancerà l'enumeratore a ciascun metodo del destinatario. Alcune classi non hanno un metodo ogni, come la classe String.
str = "I like fruit"
enum = str.to_enum
enum.next
=> NoMethodError: undefined method `each' for "I like fruit":String
Pertanto, nel caso di alcuni oggetti invocati con enum_for, devi essere esplicito su quale sarà il tuo metodo di enumerazione.
La resa può essere utilizzata come blocco senza nome per restituire un valore nel metodo. Considera il seguente codice:
Def Up(anarg)
yield(anarg)
end
È possibile creare un metodo "Su" a cui è assegnato un argomento. Ora puoi assegnare questo argomento a yield che chiamerà ed eseguirà un blocco associato. È possibile assegnare il blocco dopo l'elenco dei parametri.
Up("Here is a string"){|x| x.reverse!; puts(x)}
Quando il metodo Up chiama yield, con un argomento, viene passato alla variabile di blocco per elaborare la richiesta.