Programando en Ruby

La Guía de los "Programadores Pragmáticos"

Expresiones



Hasta el momento hemos sido bastante informales en nuestro uso de expresiones en Ruby. Después de todo, a=b+c es algo bastante estandar. Podriamos escribir un montón de código en Ruby sin leer este cápitulo.

Pero no sería tan divertido ;-).

Una de las primeras diferencias con Ruby es que practicamente todo lo que podría regresar algún valor, lo hace: Practicamente todo es una expresión, Qué significa esto en la práctica?

Algunas puntos obvios son la habilidad de encadenar o crear secuencias de declaraciones.

a = b = c = 0 » 0
[ 3, 1, 7, 0 ].sort.reverse » [7, 3, 1, 0]

Tal vez no tan obvio, cosas que normalmente son declaraciones en C o Java son expresiones en Ruby. Por ejemplo, las declaraciones if y case ambas retornan el valor de la última expresión ejecutada.

songType = if song.mp3Type == MP3::Jazz
             if song.written < Date.new(1935, 1, 1)
               Song::TradJazz
             else
               Song::Jazz
             end
           else
             Song::Other
           end

 rating = case votesCast           when 0...10    then Rating::SkipThisOne           when 10...50   then Rating::CouldDoBetter           else                Rating::Rave           end

Hablaremos más acerca de if y case empezando la página 79.

Expresiones de Operadores

Ruby posee el grupo básico de operadores (+, -, *, /, etc) asi como algunas sorpresas. Una lista completa de los operadores, y sus precedencias, se encuentra en la Tabla 18.4 en la página 219.

En Ruby, muchos operadores son en realidad llamadas a método. Cuando escribe a*b+c en realidad esta preguntando al objecto referenciado pora que ejecute el método ``*'', pasando el parámetro b. Después le pregunta al objeto resultante de ese cálculo que ejecute ``+'', pasando c como el parámetro. Esto equivale a escribir:

(a.*(b)).+(c)

Debido a que todo es un objeto, y porque se pueden redefinir los métodos de instancia, Usted podría redefinir las matemáticas básicas, si no le gustan las respuestas que obtiene.

class Fixnum
  alias oldPlus +
  def +(other)
    oldPlus(other).succ
  end
end
1 + 2 » 4
a = 3
a += 4 » 8

Más útil es el hecho de que las clases que escribe pueden participar en expresiones de operadores como si fueran objetos integrados de Ruby. Por ejemplo, si quisieramos ser capaces de extraer un número de segundos de música, a la mitad de una canción. Podriamos usar el operador de índices ``[]'' para especificar la música a ser extraida.

class Song
  def [](fromTime, toTime)
    result = Song.new(self.title + " [extract]",
                      self.artist,
                      toTime - fromTime)
    result.setStartTime(fromTime)
    result
  end
end

Este fragmento extiende la clase Song con el método ``[]'', el cual toma dos parámetros (tiempo de inicio y tiempo de cierre). Este retorna una nueva canción, con la música recortada al intervalo señalado. Entonces podríamos tocar la introducción a una canción, con código como:

aSong[0, 0.15].play

Expresiones Misceláneas

Asi como las obvias expresiones de operador, las llamadas a método, y las (posiblemente) menos obvias expresiones de declaraciones (tales como if y case), Ruby tiene algunas otras cosas que puede usar en expresiones.

Expansión de Comandos

Si escribe entre comillas simples una cadena de texto, o usa la forma delimitada precedida por %x, esta será (por defecto) ejecutada como un comando de su sistema operativo. El valor de la expresión es la salida estándar de ese comando. Las lineas nuevas no serán recortadas, de tal manera que el valor que regrese probablemente contendrá algun carácter de retorno(CR) ó cambio de linea(LF)

`date` » "Sun Jun  9 00:08:26 CDT 2002\n"
`dir`.split[34] » "lib_singleton.tip"
%x{echo "Hello there"} » "Hello there\n"

Puede usar expresiones de expansión y todos los caracteres de escape usuales en la cadena de comando.

for i in 0..3
  status = `dbmanager status id=#{i}`
  # ...
end

El resultado de ejecución o estado de salida del comando ejecutado se encuentra en la variable global $?.

Las Comillas Simples son Suaves

En al descripción de la expresión de comando, dijimos que la cadena de texto entre comillas simples sería ``por defecto'' ejecutada como un comando. De hecho, esta cadena de texto es pasada al método denominado Kernel::` (una comilla simple). Si lo desea puede modificar este comportamiento.

alias oldBackquote `
def `(cmd)
  result = oldBackquote(cmd)
  if $? != 0
    raise "Command #{cmd} failed"
  end
  result
end
print `date`
print `data`
produces:
Sun Jun  9 00:08:26 CDT 2002
prog.rb:3: command not found: data
prog.rb:5:in ``': Command data failed (RuntimeError)
	from prog.rb:10

Asignación

Casi todos los ejemplos propocionados en este libro han incluido asignaciones. Tal vez ya sea tiempo de hablar de ellas.

Una declaración de asignación establece a la variable o atributo de su lado izquierdo (el lvalue) la referencia al valor a su derecha (el rvalue). Este entonces regresa dicho valor como resultado de la expresión de asignación. Esto significa que se pueden encadenar asignaciones y que ademas se pueden realizar asignaciones en algunas partes fuera de lo común.

a = b = 1 + 2 + 3
a » 6
b » 6
a = (b = 1 + 2) + 3
a » 6
b » 3
File.open(name = gets.chomp)

Hay dos formas básicas de asignación en Ruby. La primera asigna una referencia de objecto a una variable o constante. Esta forma de asignación esta integrada en el lenguage.

instrument = "piano"
MIDDLE_A   = 440

La segunda forma de asignación involucra un atributo de objeto o una elemento de referencia en el lado izquierdo.

aSong.duration    = 234
instrument["ano"] = "ccolo"

Estas formas son especiales, porque estan implementadas mediante llamadas a métodos en el lado izquierdo, lo cual significa que las puede modificar.

Ya hemos visto como definir un atributo de objeto como modificable. Simplente definimos un nombre de método terminando en un signo de igual. Este método recibe como parámetro el valor de la derecha.

class Song
  def duration=(newDuration)
    @duration = newDuration
  end
end

No hay necesidad o razón para que estos métodos para establecer atributos tengan que corresponder con variables de instancia internas, o que tenga que existir un lector de atributos por cada modificador de atributos (o viceversa).

class Amplifier
  def volume=(newVolume)
    self.leftChannel = self.rightChannel = newVolume
  end
  # ...
end

Sidebar: Utilizando Accesos Dentro de una Clase

Porque escribimos self.leftChannel en el ejemplo de la página 74? bueno, existe un detalle oculto con los atributos escribibles. Normalmente, los métodos dentro de una clase pueden invocar otros métodos en la misma clase o superclase de manera funcional (esto es, con un receptor implícito self). Sin embargo, esto no funciona con los atributos escribibles o modificables. cuando Ruby ve esta asignación decide que el nombre en la izquierda debe ser una variable local, no una llamada de método a un modificador o escritor de atributos.
class BrokenAmplifier
  attr_accessor :leftChannel, :rightChannel
  def volume=(vol)
    leftChannel = self.rightChannel = vol
  end
end
ba = BrokenAmplifier.new
ba.leftChannel = ba.rightChannel = 99
ba.volume = 5
ba.leftChannel » 99
ba.rightChannel » 5

Olvidamos colocar ``self.'' en frente de la asignación a leftChannel, asi que Ruby guardó el nuevo valor en una variable local del método volume=; El atributo del objeto nunca fue actualizado. Este error puede ser dificil de localizar.

Asignación Paralela

Durante la primera semana en un curso de programación ( ó el segundo semestre si es una escuela oficial), tal vez tendría que escribir código para intercambiar los valores en dos variables:

int a = 1;
int b = 2;
int temp;

temp = a; a = b; b = temp;

Usted puede realizar esto de una manera mas limpia en Ruby:

a, b = b, a

Las asignaciones de Ruby son efectivamente realizadas en forma paralela, asi que los valores asignados no son afactados por la asignación en si misma. Los valores en el lado derecho son evaluados en el orden en el cual aparecen antes de realizar la asignación a las variables a la izquierda. Un ejemplo algo artificial ilustrará esto. La segunda linea asigna a las variables a, b, y c los valores de las expresiones x, x+=1, y x+=1, respectivamente.

x = 0 » 0
a, b, c   =   x, (x += 1), (x += 1) » [0, 1, 2]

Cuando una asignación tiene mas de un valor izquierdo, la expresión de asignación regresa un arreglo de los valores derechos. Si una asignación contiene mas valores izquierdos que derechos, los valores izquierdos excedentes contendrán nil. Si una asignación mútiple contiene mas valores derechos que izquierdos, los valores derechos sobrantes serán ignorados. A partir de Ruby 1.6.2, si una asignación contiene un valor izquierdo y varios derechos, los valores a la derecha serán convertidos en un arreglos y asignados al valor izquierdo.

Usted puede colapsar o expandir los arreglos usndo el operador de asignación paralela de Ruby. Si el último valor izquierdo esta precedido por un asterisco, todos los valores derechos sobrantes serán recolectados y asignados a ese valor izquierdo como un arreglo. De manera similar, si el último valor derecho es un arreglo, le puede preceder con un asterisco, lo cual efectivamente expande dicho arreglo a los valores de sus elementos constituyentes. (esto no es necesario si el valor derecho es lo único que hay del lado derecho--- en este caso será expandido por defecto.)

a = [1, 2, 3, 4]
b,  c = a » b == 1, c == 2
b, *c = a » b == 1, c == [2, 3, 4]
b,  c = 99,  a » b == 99, c == [1, 2, 3, 4]
b, *c = 99,  a » b == 99, c == [[1, 2, 3, 4]]
b,  c = 99, *a » b == 99, c == 1
b, *c = 99, *a » b == 99, c == [1, 2, 3, 4]

Asignaciones Anidadas

Las asignaciones paralelas tienen otra característica que vale la pena mencionar. El lado izquierdo de la asignación puede contener una lista de términos dentro de paréntesis. Ruby trata estos términos como si fuera una declaración de asignaciones anidadas. Extrae los correspondientes valores derechos, asignandolos a los términos en paréntesis, antes de continuar con las asignaciones de mayor nivel.

b, (c, d), e = 1,2,3,4 » b == 1, c == 2, d == nil, e == 3
b, (c, d), e = [1,2,3,4] » b == 1, c == 2, d == nil, e == 3
b, (c, d), e = 1,[2,3],4 » b == 1, c == 2, d == 3, e == 4
b, (c, d), e = 1,[2,3,4],5 » b == 1, c == 2, d == 3, e == 5
b, (c,*d), e = 1,[2,3,4],5 » b == 1, c == 2, d == [3, 4], e == 5

Otras Formar de Asignación

En común con otros lenguages, Ruby posee el atajo sintáctico: a=a+2 puede ser escrito como a+=2.

La segunda forma es convertida internamente a la primera. Esto significa que los operadores que haya definido en sus propias clases funciona de la manera esperada.

class Bowdlerize
  def initialize(aString)
    @value = aString.gsub(/[aeiou]/, '*')
  end
  def +(other)
    Bowdlerize.new(self.to_s + other.to_s)
  end
  def to_s
    @value
  end
end
a = Bowdlerize.new("damn ") » d*mn
a += "shame" » d*mn sh*m*

Ejecución Condicional

Ruby tiene diferentes mecanismos para la ejecución condicional de código; la mayoría de ellos deben de resultar familiares, y muchos poseen elegantes variantes. Pero antes de adentrarnos en estas, necesitamos dedicar un tiempo a las expresiones boleanas.

Expresiones Boleanas

Ruby tiene una simple definición de verdad, Cualquier valor que no es nil o la constante false es verdadero. Encontrará que las rutinas de las librerias usan este hecho de manera consistente. Por ejemplo, IO#gets , retorna la siguiente linea de un archivo,y al final del archivo retorna nil de manera que es posible escribir ciclos de esta manera:

while line = gets
  # process line
end

Sin embargo aqui hay una trampa para programadores de C, C++, y Perl. El número cero no es interpretado como falso. Tampoco lo es una cadena de texto de longitud cero. Esto podría ser un hábito difícil de romper.

Defined?, And, Or, y Not

Ruby soporta todos los operadores boleanos e introduce el nuevo operador defined?.

Tanto ``and'' y ``&&'' evalúan a verdad solo si ambos operandos son verdaderos. Estos analizan el segundo operando solo si el primero es verdadero (Esto es a veces conocido como ``evaluación de corto circuito''). La única diferencia en estas dos formas es la precedencia (``and'' es inferior a ``&&'').

De manera similar, tanto``or'' y ``||'' evalúan a verdad si alguno de los operandos es verdadero. Estos evalúan el segundo operando solo si el primero es falso. Al igual que ``and'', la única diferencia entre ``or'' y ``||'' es su precedencia .

Solo para hacer las cosas más interesantes, ``and'' y ``or'' tienen la misma precedencia, mientras ``&&'' tiene mayor precedencia que ``||''.

``not'' y ``!'' regresan el valor opuesto al del operando (falso si el operando es verdadero, y verdadero si el operando es falso). y si, tambien, ``not'' y ``!'' difieren solo en su precedencia.

Todas estas reglas de precedencia estan resumidas en la Tabla 18.4 de la página 219.

El operador defined? retorna nil si el argumento (el cual puede ser cualquier expresión ) no esta definido, de lo contrario regresa una descripsción de ese argumento. Si el argumento es yield,defined? entonces regresará el texto ``yield'', si un bloque de código esta asociado al contexto actual.

defined? 1 » "expression"
defined? dummy » nil
defined? printf » "method"
defined? String » "constant"
defined? $& » nil
defined? $_ » "global-variable"
defined? Math::PI » "constant"
defined? ( c,d = 1,2 ) » "assignment"
defined? 42.abs » "method"

Ademas de los operadores boleanos, los objetos de Ruby soportan comparaciones usando los métodos ==, ===, <=>, =~, eql?, y equal? (ver Tabla 7.1 página 79). Todos excepto <=> son definidos en la clase Object pero pueden ser eliminados por sus descendentes para proveer la semántica apropiada. Por ejemplo la clase Array redefine ==de tal manera que dos objetos arreglo son iguales si ambos tienen el mismo número de elementos y los correspondientes elementos son tambien iguales.

Operadores Comunes de Comparación
Operador Significado
== Prueba por igualdad.
=== Usado para probar igualdad dentro de una clausula when de una declaración case .
<=> Operador general de comparación. Regresa -1, 0, or +1, dependiendo de si el receptor es menor que, igual a, o mayor que su argumento.
<, <=, >=, > Operadores de comparación para menor que, menor o igual a, igual, mayor que o igual a y mayor que.
=~ Igualdad de patrón en expresiones regulares.
eql? Verdadero si el receptor y el argumento tiene el mismo tipo y valores iguales. 1 == 1.0 regresa veradero, pero 1.eql?(1.0) es falso.
equal? Verdadero si el receptor y el argumento tienen el mismo número de objeto(id).

Tanto == como =~ tienen formas negadas, != and !~. Sin embargo, estas son convertidas por Ruby cuando su programa es leido. a!=b es equivalente !(a==b), y a!~b es lo mismo que !(a=~b). Esto significa que si escribe una clase que modifica == ó =~ obtiene un funcional != y !~ gratuito. Por otro lado, esto implica que no puede definir != y !~ independientemente de == y =~,de manera respectiva.

Usted puede usar un rango de Ruby como una expresión boleana. Un rango como exp1..exp2 evaluará a falso hasta que exp1 sea verdadero. El rango entonces evaluará como verdadero hasta que exp2 sea verdadero. Una vez que esto sucede, el rango se reinicia, listo para empezar de nuevo. Mostramos algunos ejemplos de de esto en la página 82.

Finalmente, se puede utilizar una simple expresión regular como una expresión boleana. Ruby la expande a $_=~/re/.

Expresiones If y Unless

Una expresión if en Ruby es bastante similar a declaraciones ``if'' en otros lenguages.

if aSong.artist == "Gillespie" then
  handle = "Dizzy"
elsif aSong.artist == "Parker" then
  handle = "Bird"
else
  handle = "unknown"
end

Si coloca su desclaraciones if en lineas múltiples, entonces puede omitir la palabra clave then.

if aSong.artist == "Gillespie"
  handle = "Dizzy"
elsif aSong.artist == "Parker"
  handle = "Bird"
else
  handle = "unknown"
end

Sin embargo si organiza su código de manera más compacta, la palabra then se torna necesaria para separar las expresiones boleanas de las declaraciones subsecuentes.

if aSong.artist == "Gillespie" then  handle = "Dizzy"
elsif aSong.artist == "Parker" then  handle = "Bird"
else  handle = "unknown"
end

Puede tener cero o más claúsulas elsif y una clausula opcional else.

Como mencionamos previamente, if es una expresión, no una declaración ---este regresa un valor. No es obligatorio usar el valor retornado por una expresión if , pero puede resultar útil.

handle = if aSong.artist == "Gillespie" then
           "Dizzy"
         elsif aSong.artist == "Parker" then
           "Bird"
         else
           "unknown"
         end

Ruby tambien tiene una forma negada de la declaración if :

unless aSong.duration > 180 then
  cost = .25
else
  cost = .35
end

Finalmente, para los fanáticos de C, Ruby tambien acepta expresiones condicionales al estilo de C:

cost = aSong.duration > 180 ? .35 : .25

La expresión condicional retorna el valor de la expresión antes ó despues de los dos puntos ``:'', dependiendo de si la expresión boleana antes del signo de interrogación evalúa a verdadero true o falso false. En este caso, si la duraciónde la canción es mayor de 3 minutos, la expresión retorna .35. Para canciones más cortas, retorna .25. Cualquiera que sea el resultado , este es asignado a cost.

Modificadores de If y Unless

Ruby comparte una útil característica con Perl. Modificadores de declaraciones que le permiten agregar declaraciones condicionales al final de una declaración normal.

mon, day, year = $1, $2, $3 if /(\d\d)-(\d\d)-(\d\d)/
puts "a = #{a}" if fDebug
print total unless total == 0

Para un modificador de if ,la expresión precedente será evaluada solo si la condición es verdadera. unless funciona al revés.

while gets
  next if /^#/            # Skip comments
  parseLine unless /^$/   # Don't parse empty lines
end

Debido a que if es una expresión, Usted puede lograr declaraciones dificiles de entender como:

if artist == "John Coltrane"
  artist = "'Trane"
end unless nicknames == "no"

Este camino puede llevarle a las puertas de la locura.

Expresiones Case

La expresión case en Ruby es una bestia poderosa: un if multi-opciones en esteroides.

case inputLine

  when "debug"     dumpDebugInfo     dumpSymbols

  when /p\s+(\w+)/     dumpVariable($1)

  when "quit", "exit"     exit

  else     print "Illegal command: #{inputLine}" end

Al igual que if, case retorna el valor de la última expresión ejecutada, y tambien necesita usar la palabra then si la expresión esta en la misma linea 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

case opera comparando el objetivo (la expresión despues de case) con cada una de la expresiones de comparación ubicadas despues de when. Esta prueba es realizada utilizando comparación === objetivo. Mientras la clase defina una semántica lógica para === (todas las clases integradas lo hacen), los objetos de esa clase podrán ser usados en expresiones case.

Por ejemplo, las expresiones regulares definen === como un 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 en Ruby son instancias de la clase Class, la cual define === como una prueba para ver si el argumento es una instancia de la clase o de sus superclases. Asi (abandonando asi los beneficios del polimorfismo y trayendo los dioses de refactoración a su nivel), puede evaluar la clase de los objetos:

case shape
  when Square, Rectangle
    # ...
  when Circle
    # ...
  when Triangle
    # ...
  else
    # ...
end

Loops

No se lo diga a nadie, pero Ruby tiene mecanismos de ciclos bastante primitivos.

El ciclo while ejecuta su cuerpo cero o más veces hasta que la condición sea verdadera. Por ejemplo esta forma común lee los datos enterados hasta que ya no se introducen más datos.

while gets
  # ...
end

Hay tambien una forma negada que ejecuta hasta que la condición se torna verdadera.

until playList.duration > 60
  playList.add(songList.pop)
end

Al igual que if y unless, ambos mecnismos de ciclos pueden ser usados como modificadores de declaraciones.

a *= 2 while a < 100
a -= 10 until a < 100

En la página 78 en la sección de expresiones boleanas, mencionamos que un rango puede utilizarse como una especie de flip-flop, retornando verdadero cuando un evento sucede y permaneciendo asi hasta que un segundo evento ocurre. Este mecanismo usualmente es usado dentro de los ciclos. En el ejemplo siguiente, leemos un archivo de texto que contiene los primeros diez números ordinales (``first,'' ``second,'' y sucesivamente) pero solo imprimimos las lineas comprendidas desde ``third'' a ``fifth.''.

file = File.open("ordinal")
while file.gets
  print  if /third/ .. /fifth/
end
produces:
third
fourth
fifth

Los elementos de un rango usados en las expresiones boleanas, tambien pueden ser expresiones por si mismos. Estos son evaluadaos cada vez que la expresión boleana global es evaluada. Por ejemplo, el código siguiente utiliza el hecho de que la variable $. contiene el número de la linea actual, para asi mostrar las lineas uno a tres asi como las que igualen al patrón /eig/ y /nin/.

file = File.open("ordinal")
while file.gets
  print if ($. == 1) || /eig/ .. ($. == 3) || /nin/
end
produces:
first
second
third
eighth
ninth

Hay un detalle con while y until cuando estos son usados como modificadores de declaración. Si la declaración que están modificando hay un bloque begin/end, el código en el bloque siempre se ejecutará al menos una vez, independientemente del valor de la expresión boleana.

print "Hello\n" while false
begin
  print "Goodbye\n"
end while false
produces:
Goodbye

Interactores

Si leyo el principio de la sección previa, posiblemente se haya desanimado. Decia ``Ruby tiene mecanismos de ciclos bastante primitivos,'' . No desespere, amable lector, porque hay buenas noticias. Ruby no necesita mecanismos de ciclos(loop) sofisticados, debido a que lo verdaderamente divertido esta implementado usando Interactores(Iterators).

Por ejemplo, Ruby no tiene un ciclo ``for'' loop---al menos no el tipo que encontraría en C, C++, and Java. En su lugar, Ruby usa métodos definidos en varias de las clases integradas para proveer una funcionalidad equivalente, pero menos propensa a errores.

Veamos los ejemplos.

3.times do
  print "Ho! "
end
produces:
Ho! Ho! Ho!

Es facil evitar errores de fencepost(conteo por elemento) y de margen por 1; este ciclo se ejecutará tres veces,y no mas. Ademas de times, enteros pueden ciclar sobre rangos específicos usando downto, upto, y step. Por ejemplo, Un ciclo ``for'' tradicional que corre de 0 a 9 (algo asi i=0; i < 10; i++) se escribe de la siguiente manera.

0.upto(9) do |x|
  print x, " "
end
produces:
0 1 2 3 4 5 6 7 8 9

Un ciclo de 0 a 2 de 3 en 3 se puede escribir:

0.step(12, 3) {|x| print x, " " }
produces:
0 3 6 9 12

Similarmente, interactuando sobre arreglos u otras estructuras es fácil usando su método each.

[ 1, 1, 2, 3, 5 ].each {|val| print val, " " }
produces:
1 1 2 3 5

Una vez que una clase soporta each, los métodos adicionales del módulo Enumerable (documentado al principio de la página 403 y resumido en las páginas 102--103) se tornan disponibles. Por ejemplo, la clase File provee un método each , el cual retorna cada linea del archivo en turno. Usando el método grep de Enumerable, podriamos recorrer esas lineas hasta cumplir una condición dada.

File.open("ordinal").grep /d$/ do |line|
  print line
end
produces:
second
third

Al último, pero no el menor, es el ciclo mas básico de todos. Ruby provee un interactor integrado llamado loop.

loop {
  # block ...
}

El interactor loop llama al bloque asociado infinitamente (o hasta que rompa el ciclo, pero tendrá que leer más adelante para averiguar como hacer eso.).

For ... In

Previamente dijimos que las únicas primitivas de ciclo integradas en Ruby eran while y until. Entoces qué es este ``for''? Bueno, for es prácticamente una facilidad sintáctica. Cuando escribe

for aSong in songList
  aSong.play
end

Ruby lo traduce a algo como:

songList.each do |aSong|
  aSong.play
end

La única diferencia entre el ciclo for y el each es el alcance de las variables locales que son definidas en el cuerpo del ciclo. Esto se discute en la página 87.

Puede usar for para interactuar con cualquier objeto que responda al método each, tales como Array o 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 { |l| l =~ /d$/}
  print i.chomp, " "
end
produces:
fee fi fo fum 1 2 3 second third

Mientras su clase defina un razonable método each , podrá usar un ciclo for para recorrerla.

class Periods
  def each
    yield "Classical"
    yield "Jazz"
    yield "Rock"
  end
end

periods = Periods.new for genre in periods   print genre, " " end
produces:
Classical Jazz Rock

Break, Redo, y Next

Los mecanismos de control de ciclos break, redo, y next le permiten alterar el flujo normal en un ciclo o iteractor.

break inmediatamente termina el ciclo; y el control retorna a la declaración inmediatamente posterior al bloque. redo repite el ciclo desde el principio, pero sin reevaluar la condición o tomar el siguiente elemento (en un interactor). next brinca hasta el final del ciclo, efectivamente iniciando la siguiente interacción.

while gets
  next if /^\s*#/   # skip comments
  break if /^END/   # stop at end
                    # substitute stuff in backticks and try again
  redo if gsub!(/`(.*?)`/) { eval($1) }
  # process line ...
end

Estas palabras clave pueden ser usadas con cualquiera de los mecanismos de ciclos basados en interactores.:

i=0
loop do
  i += 1
  next if i < 3
  print i
  break if i > 4
end
produces:
345

Retry

La declaración redo provoca que el ciclo repita la interacción actual. Pero ocasionalmente, necesitará que el ciclo se reinicie desde el principio. La declaración retry es justo lo que necesitará. retry reinicia cualquier tipo de ciclo interactor.

for i in 1..100
  print "Now at #{i}. Restart? "
  retry if gets =~ /^y/i
end

Ejecutando esto interactivamente, podrá ver

Now at 1. Restart? n
Now at 2. Restart? y
Now at 1. Restart? n
 . . .

retry reevaluará cualquier argumento al interactor antes de reiniciarlo. La documentación en linea de Ruby tiene el siguiente ejemplo de un ciclo until como un hagalo-usted-mismo.

def doUntil(cond)
  yield
  retry unless cond
end

i = 0 doUntil(i > 3) {   print i, " "   i += 1 }
produces:
0 1 2 3 4

>Alcance de Variables y Ciclos

Los ciclos while, until, y for estan construidos dentro del lenguage y no introducen un nuevo alcance; Variables locales previamente existentes pueden ser usadas en el ciclo, y cualquier variable local que sea creada quedará disponible despues del ciclo.

Los bloques usados por los interactores (Tales como loop y each) son un poco diferentes. Normalmente las variables locales creadas en estos bloques no son accesibles fuera de dicho bloque.

[ 1, 2, 3 ].each do |x|
  y = x + 1
end
[ x, y ]
produces:
prog.rb:4: undefined local variable or method `x'
for #<Object:0x401c2ce0> (NameError)

Si embargo, si al tiempo que un bloque ejecuta una variable local ya existia una variable fuera del bloque con el mismo nombre que el de la que esta en el bloque, la variable local previamente existente será usada en el bloque. Por lo tanto su valor seguirá disponible cuando el bloque acabe. Como el siguiente ejemplo muestra, esto aplica tanto a variables normales en el bloque como a los argumentos del bloque.

x = nil
y = nil
[ 1, 2, 3 ].each do |x|
  y = x + 1
end
[ x, y ] » [3, 4]


Extraído del libro "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2000 Addison Wesley Longman, Inc. liberado bajo los términos de la Open Publication License V1.0.
La referencia está disponible en: download.