Estoy tratando de entender los bloques yield
y cómo funcionan en Ruby.
¿Cómo se yield
usa? Muchas de las aplicaciones de Rails que he visto usan yield
de una manera extraña.
¿Puede alguien explicarme o mostrarme a dónde ir para entenderlos?
Estoy tratando de entender los bloques yield
y cómo funcionan en Ruby.
¿Cómo se yield
usa? Muchas de las aplicaciones de Rails que he visto usan yield
de una manera extraña.
¿Puede alguien explicarme o mostrarme a dónde ir para entenderlos?
Respuestas:
Sí, es un poco desconcertante al principio.
En Ruby, los métodos pueden recibir un bloque de código para realizar segmentos arbitrarios de código.
Cuando un método espera un bloque, lo invoca llamando a la yield
función.
Esto es muy útil, por ejemplo, para iterar sobre una lista o para proporcionar un algoritmo personalizado.
Tome el siguiente ejemplo:
Definiré una Person
clase inicializada con un nombre y proporcionaré un do_with_name
método que, cuando se invoque, simplemente pasaría el name
atributo al bloque recibido.
class Person
def initialize( name )
@name = name
end
def do_with_name
yield( @name )
end
end
Esto nos permitiría llamar a ese método y pasar un bloque de código arbitrario.
Por ejemplo, para imprimir el nombre haríamos:
person = Person.new("Oscar")
#invoking the method passing a block
person.do_with_name do |name|
puts "Hey, his name is #{name}"
end
Imprimiría:
Hey, his name is Oscar
Tenga en cuenta que el bloque recibe, como parámetro, una variable llamada name
(NB puede llamar a esta variable lo que quiera, pero tiene sentido llamarla name
). Cuando el código lo invoca yield
, llena este parámetro con el valor de @name
.
yield( @name )
Podríamos proporcionar otro bloque para realizar una acción diferente. Por ejemplo, invierta el nombre:
#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"
Utilizamos exactamente el mismo método ( do_with_name
): es solo un bloque diferente.
Este ejemplo es trivial. Los usos más interesantes son filtrar todos los elementos en una matriz:
days = ["monday", "tuesday", "wednesday", "thursday", "friday"]
# select those which start with 't'
days.select do | item |
item.match /^t/
end
=> ["tuesday", "thursday"]
O también podemos proporcionar un algoritmo de ordenación personalizado, por ejemplo, basado en el tamaño de la cadena:
days.sort do |x,y|
x.size <=> y.size
end
=> ["monday", "friday", "tuesday", "thursday", "wednesday"]
Espero que esto te ayude a entenderlo mejor.
Por cierto, si el bloque es opcional, debe llamarlo así:
yield(value) if block_given?
Si no es opcional, simplemente invoca.
EDITAR
@hmak creó un repl.it para estos ejemplos: https://repl.it/@makstaks/blocksandyieldsrubyexample
racsO
si the_name = ""
"Oscar"
(no está muy claro en la respuesta)
person.do_with_name {|string| yield string, something_else }
En Ruby, los métodos pueden verificar si se llamaron de tal manera que se proporcionó un bloque además de los argumentos normales. Por lo general, esto se hace utilizando el block_given?
método, pero también puede referirse al bloque como un Proc explícito al prefijar un signo de y comercial ( &
) antes del nombre del argumento final.
Si se invoca un método con un bloque, el método puede yield
controlar el bloque (llamar al bloque) con algunos argumentos, si es necesario. Considere este método de ejemplo que demuestra:
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 =)
O, usando la sintaxis especial de argumento de bloque:
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 =)
Es muy posible que alguien proporcione una respuesta verdaderamente detallada aquí, pero siempre he encontrado que esta publicación de Robert Sosinski es una gran explicación de las sutilezas entre bloques, procesos y lambdas.
Debo agregar que creo que la publicación con la que estoy enlazando es específica de ruby 1.8. Algunas cosas han cambiado en ruby 1.9, como que las variables de bloque sean locales al bloque. En 1.8, obtendría algo como lo siguiente:
>> a = "Hello"
=> "Hello"
>> 1.times { |a| a = "Goodbye" }
=> 1
>> a
=> "Goodbye"
Mientras que 1.9 te daría:
>> a = "Hello"
=> "Hello"
>> 1.times { |a| a = "Goodbye" }
=> 1
>> a
=> "Hello"
No tengo 1.9 en esta máquina, por lo que lo anterior podría tener un error.
Quería agregar un poco por qué harías las cosas de esa manera a las excelentes respuestas.
No tengo idea de qué idioma vienes, pero suponiendo que sea un lenguaje estático, este tipo de cosas te resultarán familiares. Así es como se lee un archivo en 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 toda la secuencia de encadenamiento, la idea es esta
Así es como lo haces en rubí
File.open("readfile.rb", "r") do |infile|
while (line = infile.gets)
puts "#{counter}: #{line}"
counter = counter + 1
end
end
Muy diferente Desglosando este
Aquí, en lugar de manejar los pasos uno y dos, básicamente delega eso en otra clase. Como puede ver, eso reduce drásticamente la cantidad de código que tiene que escribir, lo que hace que las cosas sean más fáciles de leer y reduce las posibilidades de que fugas de memoria o bloqueos de archivos no se borren.
Ahora, no es que no puedas hacer algo similar en Java, de hecho, la gente lo ha estado haciendo durante décadas. Se llama el patrón de estrategia . La diferencia es que sin bloques, para algo simple como el ejemplo de archivo, la estrategia se vuelve excesiva debido a la cantidad de clases y métodos que necesita escribir. Con los bloques, es una forma tan simple y elegante de hacerlo, que no tiene ningún sentido NO estructurar su código de esa manera.
Esta no es la única forma en que se usan los bloques, pero los otros (como el patrón Builder, que puede ver en el formulario_para la api en los rieles) son lo suficientemente similares como para que sea obvio lo que sucede una vez que comprende esto. Cuando ve bloques, generalmente es seguro asumir que la llamada al método es lo que desea hacer, y el bloque describe cómo desea hacerlo.
File.readlines("readfile.rb").each_with_index do |line, index| puts "#{index + 1}: #{line}" end
y reírnos aún más de los chicos de Java.
IO.foreach('readfile.rb').each_with_index { |line, index| puts "#{index}: #{line}" }
(más sin problemas de memoria)
Este artículo me pareció muy útil. En particular, el siguiente ejemplo:
#!/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
que debería dar el siguiente resultado:
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
Entonces, esencialmente cada vez que se realiza una llamada a yield
ruby, se ejecutará el código en el do
bloque o en el interior {}
. Si se proporciona un parámetro, yield
entonces se proporcionará como un parámetro para el do
bloque.
Para mí, esta fue la primera vez que entendí realmente lo que do
estaban haciendo los bloques. Básicamente es una forma para que la función dé acceso a estructuras de datos internas, ya sea para la iteración o para la configuración de la función.
Entonces, cuando estás en rieles, escribes lo siguiente:
respond_to do |format|
format.html { render template: "my/view", layout: 'my_layout' }
end
Esto ejecutará la respond_to
función que produce el do
bloque con el format
parámetro (interno) . Luego llama a la .html
función en esta variable interna que a su vez produce el bloque de código para ejecutar el render
comando. Tenga en cuenta que .html
solo rendirá si es el formato de archivo solicitado. (tecnicismo: estas funciones en realidad block.call
no se usan yield
como se puede ver en la fuente, pero la funcionalidad es esencialmente la misma, vea esta pregunta para una discusión). Esto proporciona una forma para que la función realice un poco de inicialización y luego reciba información del código de llamada y luego continúe con el procesamiento si es necesario.
O dicho de otra manera, es similar a una función que toma una función anónima como argumento y luego la llama en javascript.
En Ruby, un bloque es básicamente una porción de código que puede pasarse y ejecutarse por cualquier método. Los bloques siempre se usan con métodos, que generalmente les envían datos (como argumentos).
Los bloques se usan ampliamente en las gemas de Ruby (incluidos Rails) y en el código de Ruby bien escrito. No son objetos, por lo tanto, no pueden asignarse a variables.
Un bloque es un fragmento de código encerrado por {} o do..end. Por convención, la sintaxis de llaves se debe usar para bloques de una sola línea y la sintaxis do..end se debe usar para bloques de varias líneas.
{ # This is a single line block }
do
# This is a multi-line block
end
Cualquier método puede recibir un bloque como argumento implícito. La declaración de rendimiento ejecuta un bloque dentro de un método. La sintaxis básica es:
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.
Cuando se alcanza la declaración de rendimiento, el método de meditación cede el control al bloque, se ejecuta el código dentro del bloque y se devuelve el control al método, que reanuda la ejecución inmediatamente después de la declaración de rendimiento.
Cuando un método contiene una declaración de rendimiento, espera recibir un bloque en el momento de la llamada. Si no se proporciona un bloque, se generará una excepción una vez que se alcance la declaración de rendimiento. Podemos hacer que el bloque sea opcional y evitar que se genere una excepción:
def meditate
puts "Today we will practice zazen."
yield if block_given?
end meditate
Output:
Today we will practice zazen.
No es posible pasar múltiples bloques a un método. Cada método puede recibir solo un bloque.
Ver más en: http://www.zenruby.info/2016/04/introduction-to-blocks-in-ruby.html
A veces uso "rendimiento" como este:
def add_to_http
"http://#{yield}"
end
puts add_to_http { "www.example.com" }
puts add_to_http { "www.victim.com"}
Logger
que no debe realizar alguna tarea si el usuario no lo necesita. Aunque deberías explicar el tuyo ...
Hay dos puntos que quiero destacar sobre el rendimiento aquí. Primero, aunque muchas respuestas aquí hablan sobre diferentes formas de pasar un bloque a un método que usa rendimiento, también hablemos sobre el flujo de control. Esto es especialmente relevante ya que puede producir MÚLTIPLES veces a un bloque. Echemos un vistazo a un ejemplo:
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
Cuando se invoca cada método, se ejecuta línea por línea. Ahora, cuando lleguemos al bloque 3.times, este bloque se invocará 3 veces. Cada vez que invoca rendimiento. Ese rendimiento está vinculado al bloque asociado con el método que llamó a cada método. Es importante tener en cuenta que cada vez que se invoca el rendimiento, devuelve el control al bloque de cada método en el código del cliente. Una vez que el bloque termina de ejecutarse, vuelve al bloque 3.times. Y esto sucede 3 veces. Por lo tanto, ese bloque en el código del cliente se invoca en 3 ocasiones separadas, ya que el rendimiento se llama explícitamente 3 veces separadas.
Mi segundo punto es sobre enum_for y yield. enum_for crea una instancia de la clase Enumerator y este objeto Enumerator también responde al rendimiento.
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"
Por lo tanto, observe que cada vez que invoquemos tipos con el iterador externo, invocará el rendimiento solo una vez. La próxima vez que lo llamemos, invocará el próximo rendimiento y así sucesivamente.
Hay un dato interesante con respecto a enum_for. La documentación en línea establece lo siguiente:
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
Si no especifica un símbolo como argumento para enum_for, ruby conectará el enumerador a cada método del receptor. Algunas clases no tienen cada método, como la clase String.
str = "I like fruit"
enum = str.to_enum
enum.next
=> NoMethodError: undefined method `each' for "I like fruit":String
Por lo tanto, en el caso de algunos objetos invocados con enum_for, debe ser explícito sobre cuál será su método de enumeración.
El rendimiento se puede usar como bloque sin nombre para devolver un valor en el método. Considere el siguiente código:
Def Up(anarg)
yield(anarg)
end
Puede crear un método "Arriba" al que se le asigna un argumento. Ahora puede asignar este argumento para obtener lo que llamará y ejecutará un bloque asociado. Puede asignar el bloque después de la lista de parámetros.
Up("Here is a string"){|x| x.reverse!; puts(x)}
Cuando el método Up llama rendimiento, con un argumento, se pasa a la variable de bloque para procesar la solicitud.