La expresión Ruby case es una poderosa bestia, y para hacerla aún más potente, viene en dos sabores. La primera forma es bastante cercana a una serie de sentencias if: le permite enumerar una serie de
condiciones y ejecutar la instrucción correspondiente a la primera que se cumple. Por ejemplo, los años bisiestos deben ser divisibles por 400 o por 4 y no por 100.
bisiesto = case
when year % 400 == 0: true when year % 100 == 0: false else year % 4 == 0
end
La segunda forma de la sentencia case es probablemente más común. Puede especificar un objetivo al principio de la sentencia case, y un cada when en claúsulas con una o más comparaciones.
case input_line when “debug”
dump_debug_info dump_symbols when /p\s+(\w+)/ dump_variable($1) when “quit”, “exit” exit
else
print “Illegal command: #{input_line}” end
Al igual que con if, case devuelve el valor de la última expresión ejecutada, y se puede utilizar la
palabra clave then, si la expresión está en la misma línea que la condición. kind = case year
when 1850..1889 then “Blues” when 1890..1909 then “Ragtime”
when 1910..1929 then “New Orleans Jazz” when 1930..1939 then “Swing”
when 1940..1950 then “Bebop”
else “Jazz”
end
Y al igual que con if, puede utilizar los dos puntos (:) en lugar de then. kind = case year
when 1850..1889: “Blues” when 1890..1909: “Ragtime”
when 1910..1929: “New Orleans Jazz” when 1930..1939: “Swing”
when 1940..1950: “Bebop” else “Jazz” end
case opera mediante comparación del objetivo (la expresión después de la palabra clave case) con
cada una de las expresiones de comparación después de la palabra clave when. Esta prueba se realiza
mediante comparación === objetivo. Mientras una clase defina una semántica significativa para ===
(y todas las clases internas al lenguaje -built-in- lo hacen), los objetos de esa clase se puede utilizar en las expresiones case.
Por ejemplo, las expresiones regulares definen === como una simple comparación de patrón. case line when /title=(.*)/ puts “Title is #$1” when /track=(.*)/ puts “Track is #$1” when /artist=(.*)/ puts “Artist is #$1” end
Las clases de Ruby son instancias de la clase Class, que define === para comprobar si el argumento
es una instancia de la clase o una de sus superclases. Así que (abandonando los beneficios del polimor- fismo y tirando a los dioses de la refactorización de las orejas), se puede testear la clase de los objetos.
case shape
when Square, Rectangle # ...
when Circle # ...
# ... else
# ... end
Bucles
No se lo digais a nadie, pero Ruby tiene internamente una construcción de bucles bastante primitiva. El bucle while ejecuta el cuerpo de la sentencia cero o más veces, siempre y cuando su condición sea
verdadera. Por ejemplo, este código común lee hasta que se agota la entrada.
while line = gets # ...
end
El bucle until que es todo lo contrario, se ejecuta el cuerpo hasta que la condición sea verdadera. until play_list.duration > 60
play_list.add(song_list.pop) end
Al igual que con if y unless, se pueden utilizar ambas en los bucles como modificadores de sentencia . a = 1
a *= 2 while a < 100 a -= 10 until a < 100 a -> 98
En la sección de expresiones booleanas, dijimos que un rango se puede utilizar como una especie de flip-flop, retornando true cuando ocurre algún evento y permanece true hasta que un segundo evento
ocurre. Esta funcionalidad se utiliza normalmente en los bucles. En el ejemplo siguiente, se lee un archivo de texto que contiene los diez primeros números ordinales (“primero”, “segundo”, y así sucesivamente), pero solo empieza a imprimir las líneas cuando coincide con “tercero” y termina cuando coincide con “quinto ”.
file = File.open(“ordinal”) while line = file.gets
puts(line) if line =~ /third/ .. line =~ /fifth/ end
produce:
third fourth fifth
Programadores que vienen de Perl pueden escribir el ejemplo anterior de forma ligeramente diferente.
file = File.open(“ordinal”) while file.gets
print if ~/third/ .. ~/fifth/ end
produce:
third fourth fifth
lectura de la variable global $_, el operador ~ hace una comparación de expresión regular contra $_ y print sin argumentos imprime $_. Este tipo de código está pasado de moda en la comunidad Ruby.
Los elementos de un rango utilizados en una expresión booleana pueden ser también expresiones. Es- tos son evaluados cada vez que se evalúa la expresión lógica en general. Por ejemplo, el siguiente código utiliza el hecho de que la variable $. contiene el número de línea de entrada actual, para mostrar números de línea del uno al tres y aquellas que coincidan entre la comparación /eig/ y /nin/.
File.foreach(“ordinal”) do |line|
if (($. == 1) || line =~ /eig/) .. (($. == 3) || line =~ /nin/) print line end end produce: first second third eighth ninth
Uno se puede arrugar al usar while y until como modificadores de sentencia. Si la declaración que
va a modificar es un bloque de begin/end, el código en el bloque siempre se ejecutará al menos una vez
independientemente del valor de la expresión lógica.
print “Hello\n” while false begin
print “Goodbye\n” end while false
produce:
Goodbye
Iteradores
Si se lee el comienzo de la sección anterior, es posible que se haya desanimado. “Ruby tiene inter- namente una construcción de bucles bastante primitiva”, se dijo. No se desespere, amable lector, ya que tenemos una buena noticia. Ruby no necesita ningún tipo de complejos bucles integrados, porque todas las cosas divertidas se implementan mediante iteradores Ruby.
Por ejemplo, Ruby no tiene un bucle “for”, (por lo menos no del tipo que se encuentran en C, C++ o Java). En cambio, Ruby utiliza métodos definidos en varias clases integradas para proporcionar equiva- lentes, pero menos propensos a errores..
Veamos algunos ejemplos.
3.times do print “Ho! “ end
produce:
Ho! Ho! Ho!
Es fácil evitar errores, este ciclo se ejecutará tres veces y punto. Además de con times, con los nú-
meros enteros se pueden hacer bucles en intervalos específicos llamando a downto y upto, y para todos
los tipos de números se peuede utilizar step. Por ejemplo, un tradicional bucle “for” que recorre de 0 a 9
0.upto(9) do |x| print x, “ “ end
produce:
0 1 2 3 4 5 6 7 8 9
Un bucle de 0 a 12 cada 3 se puede escribir de la siguiente manera:
0.step(12, 3) {|x| print x, “ “ }
produce:
0 3 6 9 12
Del mismo modo, iterar sobre matrices y otros contenedores se hace fácil con el método each: [ 1, 1, 2, 3, 5 ].each {|val| print val, “ “ }
produce:
1 1 2 3 5
Y una vez que una clase soporta each, los otros métodos están disponibles en el módulo Enumerable
(documentado más adelante). Por ejemplo, la clase File proporciona un método each, que devuelve
cada línea de un archivo por turno. Utilizando el método grep en Enumerable, podríamos iterar sólo
aquellas líneas que cumplen una determinada condición.
File.open(“ordinal”).grep(/d$/) do |line| puts line end produce: second third
Por último, está el lazo más básico de todos. Ruby proporciona un iterador integrado llamado loop. loop do
# block ... end
El iterador loop llama al bloque asociado para siempre (o por lo menos hasta interrumpir del bucle,
pero antes tendrá que seguir leyendo para saber cómo hacerlo).
For...In
Anteriormente hemos dicho que las únicas primitivas de bucle integradas son while y until. ¿Qué
es for, entonces? Bueno, for es casi un terrón de azúcar sintáctico. Cuando se escribe: for song in songlist
song.play end
songlist.each do |song| song.play
end
La única diferencia entre el bucle for y la forma each es el alcance de las variables locales que se
definen en el cuerpo. Esto se discute ahora un poco más adelante.
Se puede utilizar for para iterar sobre cualquier objeto que responde al método each, como un Array
o un Range.
for i in [‘fee’, ‘fi’, ‘fo’, ‘fum’] print i, “ “
end
for i in 1..3 print i, “ “ end
for i in File.open(“ordinal”).find_all {|line| line =~ /d$/} print i.chomp, “ “
end
produce:
fee fi fo fum 1 2 3 second third
Mientras su clase defina un razonable método each, se puede utilizar un bucle for para recorrer sus
objetos. class Periods def each yield “Classical” yield “Jazz” yield “Rock” end end periods = Periods.new for genre in periods print genre, “ “ end
produce:
Classical Jazz Rock