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 yieldfunción.
Esto es muy útil, por ejemplo, para iterar sobre una lista o para proporcionar un algoritmo personalizado.
Tome el siguiente ejemplo:
Definiré una Personclase inicializada con un nombre y proporcionaré un do_with_namemétodo que, cuando se invoque, simplemente pasaría el nameatributo al bloque recibido.
classPersondef initialize( name )@name= nameenddef do_with_name yield(@name)endend
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.reverseend
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:
Lo sentimos, el nombre es una variable de instancia inicializada con "Oscar" (no está muy claro en la respuesta)
OscarRyz
¿Qué pasa con un código como este? person.do_with_name {|string| yield string, something_else }
f.ardelian
77
Entonces, en términos de Javascripty, es una forma estandarizada de pasar una devolución de llamada a un método determinado y llamarlo. ¡Gracias por la explicación!
yitznewton
De una manera más general: un bloque es un azúcar de sintaxis "mejorado" de rubí para el patrón de Estrategia. porque el uso típico es proporcionar un código para hacer algo en el contexto de otra operación. Pero las mejoras de ruby abren un camino a cosas tan geniales como escribir DSLs usando block para pasar el contexto
Roman Bulgakov
25
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 yieldcontrolar 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 blockend
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 bueno saber diferentes formas de activar un bloqueo.
LPing
22
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.
@klenwell gracias por el aviso, he actualizado el enlace nuevamente.
theIV
13
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
publicclassFileInput{publicstaticvoid main(String[] args){File file =newFile("C:\\MyFile.txt");FileInputStream fis =null;BufferedInputStream bis =null;DataInputStream dis =null;try{
fis =newFileInputStream(file);// Here BufferedInputStream is added for fast reading.
bis =newBufferedInputStream(fis);
dis =newDataInputStream(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
decirle a la clase File cómo inicializar el recurso
decirle a la clase de archivo qué hacer con ella
reírse de los chicos de Java que todavía están escribiendo ;-)
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.
Vamos a simplificar eso un poco: File.readlines("readfile.rb").each_with_index do |line, index| puts "#{index + 1}: #{line}" endy reírnos aún más de los chicos de Java.
Michael Hampton
1
@MichaelHampton, ríe después de leer un archivo de un par de gigabytes de largo.
akostadinov
@akostadinov No ... ¡eso me da ganas de llorar!
Michael Hampton
3
@MichaelHampton O, mejor aún: IO.foreach('readfile.rb').each_with_index { |line, index| puts "#{index}: #{line}" }(más sin problemas de memoria)
Financia la demanda de Mónica el
12
Este artículo me pareció muy útil. En particular, el siguiente ejemplo:
#!/usr/bin/rubydef test
yield5
puts "You are in the method test"yield100end
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 5You are in the method test
You are in the block 100You are in the block 5You are in the method test
You are in the block 100
Entonces, esencialmente cada vez que se realiza una llamada a yieldruby, se ejecutará el código en el dobloque o en el interior {}. Si se proporciona un parámetro, yieldentonces se proporcionará como un parámetro para el dobloque.
Para mí, esta fue la primera vez que entendí realmente lo que doestaban 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:
Esto ejecutará la respond_tofunción que produce el dobloque con el formatparámetro (interno) . Luego llama a la .htmlfunción en esta variable interna que a su vez produce el bloque de código para ejecutar el rendercomando. Tenga en cuenta que .htmlsolo rendirá si es el formato de archivo solicitado. (tecnicismo: estas funciones en realidad block.callno se usan yieldcomo 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.
Sintaxis Básica
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 blockend
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 blockend# We are passing a block as an argument to the meditate method
meditate { print " for 40 minutes."}Output:Today we will practice zazen for40 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."yieldif 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.
Está bien, pero por qué ? Hay muchas razones, como la Loggerque no debe realizar alguna tarea si el usuario no lo necesita. Aunque deberías explicar el tuyo ...
Ulysse BN
4
Los rendimientos, para decirlo simplemente, permiten que el método que cree tome y llame a los bloques. La palabra clave de rendimiento específicamente es el lugar donde se realizarán las 'cosas' en el bloque.
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:
classFruit
attr_accessor :kinds
def initialize
@kinds=%w(orange apple pear banana)enddef each
puts 'inside each'3.times{yield(@kinds.tap {|kinds| puts "selecting from #{kinds}"}).sample }endend
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.
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:
DefUp(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.
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é undo_with_name
método que, cuando se invoque, simplemente pasaría elname
atributo al bloque recibido.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:
Imprimiría:
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 llamarlaname
). Cuando el código lo invocayield
, llena este parámetro con el valor de@name
.Podríamos proporcionar otro bloque para realizar una acción diferente. Por ejemplo, invierta el nombre:
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:
O también podemos proporcionar un algoritmo de ordenación personalizado, por ejemplo, basado en el tamaño de la cadena:
Espero que esto te ayude a entenderlo mejor.
Por cierto, si el bloque es opcional, debe llamarlo así:
Si no es opcional, simplemente invoca.
EDITAR
@hmak creó un repl.it para estos ejemplos: https://repl.it/@makstaks/blocksandyieldsrubyexample
fuente
racsO
sithe_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:O, usando la sintaxis especial de argumento de bloque:
fuente
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:
Mientras que 1.9 te daría:
No tengo 1.9 en esta máquina, por lo que lo anterior podría tener un error.
fuente
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
Ignorando toda la secuencia de encadenamiento, la idea es esta
Así es como lo haces en rubí
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.
fuente
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:
que debería dar el siguiente resultado:
Entonces, esencialmente cada vez que se realiza una llamada a
yield
ruby, se ejecutará el código en eldo
bloque o en el interior{}
. Si se proporciona un parámetro,yield
entonces se proporcionará como un parámetro para eldo
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:
Esto ejecutará la
respond_to
función que produce eldo
bloque con elformat
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 elrender
comando. Tenga en cuenta que.html
solo rendirá si es el formato de archivo solicitado. (tecnicismo: estas funciones en realidadblock.call
no se usanyield
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.
fuente
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.
Sintaxis Básica
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.
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:
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:
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
fuente
A veces uso "rendimiento" como este:
fuente
Logger
que no debe realizar alguna tarea si el usuario no lo necesita. Aunque deberías explicar el tuyo ...Los rendimientos, para decirlo simplemente, permiten que el método que cree tome y llame a los bloques. La palabra clave de rendimiento específicamente es el lugar donde se realizarán las 'cosas' en el bloque.
fuente
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:
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.
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:
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.
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.
fuente
El rendimiento se puede usar como bloque sin nombre para devolver un valor en el método. Considere el siguiente código:
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.
Cuando el método Up llama rendimiento, con un argumento, se pasa a la variable de bloque para procesar la solicitud.
fuente