2010-08-24 15 views
5

Empecé a aprender Ruby, y he leído un par de tutoriales e incluso compré un libro ("Programming Ruby 1.9 - The Pragmatic Programmers 'Guide"), y encontré algo nuevo que no lo había visto antes en ninguno de los otros idiomas que conozco (estoy trabajando como webdeveloper de PHP).Bloques y Procs en Ruby

Bloques & Procs. Creo que entiendo cuáles son, pero lo que no entiendo es por qué son tan geniales, y cuándo y por qué debería usarlos. Donde quiera que miren, dicen que los bloqueos y los procesadores son una gran característica de Ruby, pero no los entiendo.

¿Alguien aquí puede darme un total de Ruby-newbie como yo algunas explicaciones?

+0

Busca más información sobre Closures, te ayudará a explicar cómo funcionan los bloques y procesos y para qué sirven. – Doon

Respuesta

13

Hay muchas cosas que son buenas acerca de los bloques. El discurso del ascensor: Bloques nos dejó pasar alrededor de acciones de la misma manera que normalmente pasamos alrededor de datos.

El nivel más obvio es que le permiten abstraer las cosas en funciones que de otro modo no serían posibles.Por ejemplo, veamos un caso común donde se tiene una lista de las cosas y que desea filtrar para incluir sólo los elementos que coinciden con algún criterio:

int list[50] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50}; 
int evenNumbers[50] = {0}; 
int copyIndex = 0; 
for (int i = 0; i < 50; i++) { 
    if (list[i] % 2 == 0) { 
     evenNumbers[copyIndex++] = list[i]; 
    } 
} 

Así es como se escribe que en Ruby:

list = 1..50 
listCopy = list.select {|n| n.even?} 

Todo el trabajo común se mueve fuera de su código y en un método con un nombre significativo. No nos importa copiar el arreglo, examinar los índices y todo eso, solo queremos una lista filtrada. Y eso es lo que nos da select. El bloque nos permite pasar nuestra lógica personalizada a este método estándar.

Pero los iteradores no son el único lugar donde este "hole in the middle pattern" es útil. Por ejemplo, si pasa un bloque al File.open, abrirá el archivo, ejecutará el bloque con el archivo y luego cerrará el archivo por usted.

Otra cosa que los bloques nos dan es una forma realmente poderosa de devoluciones de llamadas. Por ejemplo, sin bloques, que podría tener que hacer algo como esto (en función de cómo funcionan realmente los diálogos en Objective-C cacao):

class Controller 
    def delete_button_clicked(item) 
    item.add_red_highlight 
    context = {:item => item} 
    dialog = Dialog.new("Are you sure you want to delete #{item}?") 
    dialog.ok_callback = :delete_OK 
    dialog.ok_receiver = self 
    dialog.cancel_callback = :cancel_delete 
    dialog.cancel_receiver = self 
    dialog.context = context 
    dialog.ask_for_confirmation 
    end 

    def delete_OK(sender) 
    delete(sender.context[:item]) 
    sender.dismiss 
    end 

    def cancel_delete(sender) 
    sender.context[:item].remove_red_highlight 
    sender.dismiss 
    end 
end 

Yowza. Con bloques, podríamos hacer esto en su lugar (en base a un patrón común que se utiliza en muchas bibliotecas de Ruby):

class Controller 
    def delete_button_clicked(item) 
    item.add_red_highlight 
    Dialog.ask_for_confirmation("Are you sure you want to delete #{item}?") do |response| 
     response.ok { delete item } 
     response.cancel { item.remove_red_highlight } 
    end 
    end 
end 

Eso es en realidad dos niveles de bloques - el bloque do...end y los dos bloques al estilo {}. Pero se lee bastante natural, ¿no? Esto funciona porque un bloque captura el contexto en el que se creó, por lo que no es necesario pasar por self y item.

En cuanto a los Procs, solo son un contenedor de objetos para bloques. No mucho para ellos.

0

Estos conceptos están ligados a los conceptos de programación funcional en ruby, por lo tanto, le permite usar patrones y técnicas que se encuentran generalmente en idiomas, donde las funciones son ciudadanos de primera clase.

+0

No, los bloques en Ruby vienen de Smalltalk y en ambos idiomas son objetos y no funciones, aunque en ambos se ven y actúan como funciones anónimas. p.ej. Incluso la sintaxis es muy parecida. Por ejemplo, en smalltalk un bloque sería 'a: = [: x | x + 1]' – OscarRyz

+0

@OscarRyz: estoy escogiendo liendres aquí, pero los bloques en Ruby realmente son funciones y * no son * objetos. Es por eso que la gente dice que son cierres, los cierres son un tipo de función. No puede, por ejemplo, escribir 'totally_an_object = {| n | n * 7} '- eso es un error de sintaxis. Debe llamar 'proc' con el bloque para obtener un objeto real que represente esa función. Los bloques eran objetos verdaderos en Smalltalk, pero la versión de Ruby es ligeramente diferente. – Chuck

2

Los bloques se usan de muchos métodos en las clases de Ruby, y se usan en PHP donde se usa una devolución de llamada.

[1,2,3,4,5].each {|i| print "#{i} "} 

[1,2,3,4,5].each do |i| 
    print "#{i} " 
end 

File.open('p014constructs.rb', 'r') do |f1| 
    while line = f1.gets 
    puts line 
    end 
end 

PHP5 introdujo funciones anónimas; en lugar de usar una devolución de llamada, puede usar una función anónima.

echo preg_replace_callback('~-([a-z])~', function ($match) { 
    return strtoupper($match[1]); 
}, 'hello-world'); 
+0

Comprendo bloques cuando uso iteradores, etc., pero son todas las otras cosas que no entiendo. (Mayoría de los procesos, supongo) – Nilks

+0

¡Añade en la parte sobre bloques que son cierres! (es decir, puede acceder a variables fuera del alcance del bloque, lo que significa que no tiene que crear un grupo de estructuras temporales, etc.) –

+0

lo que significan los bloques Ruby 1.8 "no tienen variables locales". Creo que encontrarás que no estás en lo correcto. – horseyguy

2

Es importante ver los bloques no como el uso de métodos para iniciar un bloque de código, en realidad se está tomando el bloque y usarlo como un parámetro de la función.

Así que cuando se utiliza el método each para iterar sobre una matriz de este modo:

superOverFlowArray.each { |flow| puts flow } 

Está enviando el bloque {| flujo | pone flow} en cada método. El código le está diciendo a ruby ​​que envíe el valor actual de la matriz a este bloque en lugar de | flujo |.

Los procs toman un bloque y los convierten en una variable. Los procs son instancias de objeto que contienen bloques. Los bloques se pasan como un parámetro al proceso y se ejecutan cuando se llama al método de 'llamada' en esa instancia de Proc.

Así que aquí es un ejemplo Proc:

def category_and_title(category) 
    Proc.new { |title| "The title is: " + title + " in category: " + category } 
end 

myFirstTitle = category_and_title("Police Drama") 
mySecondTitle = category_and_title("Comedy") 

puts myFirstTitle.call("Law and Order") 
puts mySecondTitle.call("Seinfeld") 
puts myFirstTitle.call("CSI") 

El Proc recordará la categoría original que fue aprobada en, lo que permite una forma conveniente de agrupar tipos.

0

Los bloques y procesos le permiten extraer pequeños trozos de código sin la sobrecarga completa y la complejidad de los métodos.

Incluso map por sí mismo es muy poderoso, y jQuery se construye en torno a este mismo concepto:

['spite','rest','purpose'].map {|s| s << 'ful' } 

y se puede tirar la lógica en caso necesario

['sprite','restful','luck'].map {|s| s << (s.end_with?('uck') ? 'i' : '') << 'ly' } 

Hay un operador inteligente '& 'eso significa' convertir el símbolo a un proceso y llamarlo ', para que pueda convertir cadenas de objetos:

['spite',12,:purpose].map(&:to_s) 

Y mediante el uso de cierres y combinando la escritura de un cierre simple podemos encontrar el lugar donde aparecen los números adyacentes. Esto es bastante torpe Ruby, pero más concisa que la mayoría de los idiomas siguientes:

last = -2 # this variable is accessed and changed within the closure 
    objs.sort.map do |o| 
    if (last + 1) != o 
     last = o 
     nil # not one more than previous so return nil 
    else 
     o 
    end 
    end.compact # compact removes nils 

Cuando empieza a volver a escribir en otro idioma se da cuenta de lo conveniente que es esto. Lo alienta a estructurar su código en operaciones más pequeñas, por lo que es más reutilizable y comprobable.

1

Procs, también conocidos como closures o lambdas es un concepto en rubí que puede parecer confuso al principio, especialmente para un novato. En resumen, los procesos le permiten pasar bloques de código con facilidad.Un ejemplo, debajo de

hello = Proc.new do |x, y| 
     puts "i am a proc" 
     sum = x+y 
     puts "sum: #{sum}" 
    end 

ahora para hacer uso de este bloque de código, simplemente llame al método "call" on hello. Tenga en cuenta que el ejemplo anterior recibe los argumentos xey que utiliza el bloque de código. Por lo tanto, asegúrese de pasar argumentos al señalar al objeto Proc como he hecho a continuación:

hello.call(2, 3) 

dando los siguientes resultados:

i am a proc 
    sum: 5 

hurra !! Esa fue una manera fácil de crear un objeto Proc. ¿Qué tan útil es esto? Bueno, los objetos de proceso te permiten pasar trozos de código. Una ilustración a continuación lo explica mejor haciendo uso del proceso creado en el ejemplo anterior. vamos a crear una clase al azar,

class SomeClass 
     def initialize(&block) 
     @block = block 
     end 

     def output_value(x, y) 
     @block.call(x, y) 
     end 
    end 

Ahora, vamos a crear una instancia de SomeClass,

some_class = SomeClass.new(&hello) 

NOTA de que el signo ampersand "&" antes de la hola le permite pasar un objeto proc como una argumento.

por último, vamos a emitir un valor mediante el paso 2 argumentos, como a continuación:

some_class.output_value(1, 3) 

el resultado que se obtiene es el siguiente:

i am a proc 
    sum: 4 

Ver !! Es así de simple. Usted ha podido pasar una porción de código. Los procs son tan útiles que Ruby lo usa mucho. Espero que esto haya sido muy útil :)

Cuestiones relacionadas