|
|||
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]
|
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 |
if y case empezando la
página 79.
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) |
class Fixnum
|
||
alias oldPlus +
|
||
def +(other)
|
||
oldPlus(other).succ
|
||
end
|
||
end
|
||
|
||
1 + 2
|
» |
4
|
a = 3
|
||
a += 4
|
» |
8
|
[]'' 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 |
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 |
if y case), Ruby tiene algunas
otras cosas que puede usar en expresiones.
%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"
|
for i in 0..3
status = `dbmanager status id=#{i}`
# ...
end
|
$?.
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`
|
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 |
a = b = 1 + 2 + 3
|
||
a
|
» |
6
|
b
|
» |
6
|
a = (b = 1 + 2) + 3
|
||
a
|
» |
6
|
b
|
» |
3
|
File.open(name = gets.chomp)
|
||
instrument = "piano" MIDDLE_A = 440 |
aSong.duration = 234 instrument["ano"] = "ccolo" |
class Song def duration=(newDuration) @duration = newDuration end end |
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.
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.
|
||||||||||||||||||||||||||||||||||||
int a = 1; int b = 2; int temp; temp = a; a = b; b = temp; |
a, b = b, a |
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]
|
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] |
| 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 |
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*
|
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 |
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"
|
==, ===, <=>,
=~, 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
|
|||||||||||||||||||||||||
== 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/.
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 |
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 |
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 |
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 |
if :
unless aSong.duration > 180 then cost = .25 else cost = .35 end |
cost = aSong.duration > 180 ? .35 : .25 |
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.
mon, day, year = $1, $2, $3 if /(\d\d)-(\d\d)-(\d\d)/
puts "a = #{a}" if fDebug
print total unless total == 0
|
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 |
if es una expresión, Usted puede lograr
declaraciones
dificiles de entender como:
if artist == "John Coltrane" artist = "'Trane" end unless nicknames == "no" |
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
|
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 |
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 |
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 |
until playList.duration > 60 playList.add(songList.pop) end |
if y unless, ambos mecnismos de ciclos
pueden ser usados como modificadores de declaraciones.
a *= 2 while a < 100 a -= 10 until a < 100 |
file = File.open("ordinal")
while file.gets
print if /third/ .. /fifth/
end
|
third fourth fifth |
$. 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
|
first second third eighth ninth |
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 |
Goodbye |
3.times do print "Ho! " end |
Ho! Ho! Ho! |
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 |
0 1 2 3 4 5 6 7 8 9 |
0.step(12, 3) {|x| print x, " " }
|
0 3 6 9 12 |
each.
[ 1, 1, 2, 3, 5 ].each {|val| print val, " " }
|
1 1 2 3 5 |
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
|
second third |
loop.
loop {
# block ...
}
|
loop llama al bloque asociado infinitamente
(o hasta que rompa
el ciclo, pero tendrá que leer más adelante para averiguar como hacer
eso.).
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 |
songList.each do |aSong| aSong.play end |
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
|
fee fi fo fum 1 2 3 second third |
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 |
Classical Jazz Rock |
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
|
i=0 loop do i += 1 next if i < 3 print i break if i > 4 end |
345 |
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
|
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
}
|
0 1 2 3 4 |
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 ] |
prog.rb:4: undefined local variable or method `x' |
x = nil
|
||
y = nil
|
||
[ 1, 2, 3 ].each do |x|
|
||
y = x + 1
|
||
end
|
||
[ x, y ]
|
» |
[3, 4]
|