Bloques y rendimientos en Ruby
Estoy tratando de entender los bloques y yield
y cómo funcionan en Ruby.
¿Cómo se usa yield
? Muchas de las aplicaciones Rails que he visto usan yield
de una manera extraña.
¿Puede alguien explicarme o mostrarme a dónde ir para entenderlos?
9 answers
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 función yield
.
Esto es muy útil, por ejemplo, para iterar sobre una lista o para proporcionar un algoritmo personalizado.
Tomemos el siguiente ejemplo:
Voy a definir una clase Person
inicializada con un nombre, y proporcionar un método do_with_name
que cuando invocado, simplemente pasaría el atributo name
, 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:{[18]]}
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
Observe que el bloque recibe, como parámetro, una variable llamada name
(N.B. puede llamar a esta variable como quiera, pero tiene sentido llamarla name
). Cuando el código 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, invertir 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"
Usamos exactamente el mismo método (do_with_name
) - es solo un bloque diferente.
Este ejemplo es trivial. 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 deberías llamarlo como:
yield(value) if block_given?
Si no es opcional, simplemente invócalo.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-10-20 18:01:33
Es muy posible que alguien proporcione una respuesta verdaderamente detallada aquí, pero siempre he encontrado este post de Robert Sosinski para ser una gran explicación de las sutilezas entre bloques, procs y lambdas.
Debo añadir que creo que el post al que estoy enlazando es específico 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ías algo como lo siguiente:
>> a = "Hello"
=> "Hello"
>> 1.times { |a| a = "Goodbye" }
=> 1
>> a
=> "Goodbye"
Considerando que 1.9 daría usted:
>> 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.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-07-07 20:13:04
En Ruby, los métodos pueden verificar si fueron llamados de tal manera que se proporcionó un bloque además de los argumentos normales. Normalmente esto se hace usando el método block_given?
, pero también puede referirse al bloque como un Proc explícito prefijando un ampersand (&
) antes del nombre del argumento final.
Si se invoca un método con un bloque, entonces 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 del argumento de bloque especial:
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 =)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-08 16:32:27
Quería agregar por qué harías las cosas de esa manera a las ya grandes respuestas.
No tengo idea de qué idioma vienes, pero asumiendo que es un lenguaje estático, este tipo de cosas te parecerá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 todo el asunto del encadenamiento de la corriente, la idea es esta
- Inicializar el recurso que necesita ser limpiado
- use el recurso
- asegúrese de limpiarlo
Así es como hazlo en ruby
File.open("readfile.rb", "r") do |infile|
while (line = infile.gets)
puts "#{counter}: #{line}"
counter = counter + 1
end
end
Tremendamente diferente. Rompiendo éste abajo
- dile a la clase de archivo cómo inicializar el recurso
- dile a la clase de archivo qué hacer con él
- ríete de los chicos de java que todavía están escribiendo; -)
Aquí, en lugar de manejar los pasos uno y dos, básicamente delegas eso en otra clase. Como se puede ver, que reduce drásticamente la cantidad de código que tiene que escribir, lo que hace las cosas más fáciles de leer, y reduce la posibilidades de que cosas como fugas de memoria o bloqueos de archivos no se borren.
Ahora, no es como no se puede hacer algo similar en Java, de hecho, la gente lo ha estado haciendo durante décadas. Se llama el patrón 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 sencilla y elegante de hacerlo, que no hace ninguna sentido de 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 se puede ver en la api form_for en rails) son lo suficientemente similares como para que sea obvio lo que está pasando una vez que se envuelva en esto. Cuando ves bloques, por lo general es seguro asumir que la llamada al método es lo que quieres hacer, y el bloque está describiendo cómo quieres hacerlo.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-30 21:24:39
Encontré este artículo para ser 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 la siguiente salida:
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
Así que esencialmente cada vez que se haga una llamada a yield
ruby ejecutará el código en el bloque do
o dentro de {}
. Si se proporciona un parámetro a yield
, se proporcionará como parámetro al bloque do
.
Para mí, esta fue la primera vez que entendí realmente lo que los bloques do
estaban haciendo. Es básicamente una forma para que la función dé acceso a estructuras de datos internas, ya sea para iteración o para configuración de la función.
Así que cuando en rails escribes lo siguiente:
respond_to do |format|
format.html { render template: "my/view", layout: 'my_layout' }
end
Esto ejecutará la función respond_to
que produce el bloque do
con el parámetro (interno) format
. A continuación, se llama a la función .html
en esta variable interna que a su vez produce el bloque de código para ejecutar el comando render
. Tenga en cuenta que .html
solo rendirá si es el formato de archivo solicitar. (tecnicismo: estas funciones en realidad utilizan block.call
no yield
como se puede ver en la fuente pero la funcionalidad es esencialmente la misma, ver esta pregunta para una discusión.) Esto proporciona una forma para que la función realice alguna inicialización, luego tome la entrada 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 javascript.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:55:10
En Ruby, un bloque es básicamente un trozo de código que puede ser pasado y ejecutado por cualquier método. Los bloques siempre se usan con métodos, que generalmente les proporcionan datos (como argumentos).
Los bloques son ampliamente utilizados en gemas Ruby (incluyendo Rails) y en código Ruby bien escrito. No son objetos, por lo tanto no se pueden asignar a variables.
Sintaxis básica
Un bloque es una pieza de código encerrada por { } o do..final. Por convención, la sintaxis de llaves debe usarse para bloques de una sola línea y el do..la sintaxis final debe usarse 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. Un bloque se ejecuta mediante la sentencia yield 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 sentencia yield, el método meditate cede el control al bloque, el código dentro del bloque se ejecuta y el control se devuelve al método, que reanuda la ejecución inmediatamente después de la sentencia yield.
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 lanzará 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 varios 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" de esta manera:
def add_to_http
"http://#{yield}"
end
puts add_to_http { "www.example.com" }
puts add_to_http { "www.victim.com"}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-04 01:26:31
Los rendimientos, en pocas palabras, permiten que el método que se crea para tomar y llamar a los bloques. La palabra clave yield específicamente es el lugar donde se realizarán las 'cosas' en el bloque.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-12-02 01:02:11
Yield 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 "Up" al que se le asigna un argumento. Ahora puede asignar este argumento a yield 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 a yield, con un argumento, se pasa a la variable block para procesar la solicitud.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-21 15:34:27