Вы находитесь на странице: 1из 10

William Viana y Andreu Belmonte 20

El título de ese capítulo es una referencia al Estaba deseando que llegara este momento. Es el momento de presentar (in)formalmente una de las cosas que más
grupo del mismo nombre de los 80. Cuando me gusta de Ruby: iteradores y bloques. En este capítulo también veremos como definir métodos. Empecemos
era pequeño mi hermana mayor me torturaba por lo más fácil.
con su música
4.1 Métodos
Definir tus propios métodos es bien fácil, veamos su sintaxis y alguna peculiaridad:

metodos.rb
1 def cuadrado ( x )
2 return x∗x
3 end
4
5 def t r i a n g u l o s
6 " son a d i f e r e n t e s de l o s c u a d r a d o s "
7 #s e puede o m i t i r l a p a l a b r a r e t u r n
8 end
9
10 def metodo ( uno , dos , ∗ v a r i o s )
11 "Me han pasado #{uno } , #{dos } , #{ v a r i o s . j o i n ( ’ , ’) }"
12 end
13
14 def es_palindromo ? ( f r a s e )
15 f r a s e = f r a s e . dup #hago una c o p i a de f r a s e
16 f r a s e . gsub ! ( " " , " " ) . downcase ! == f r a s e . r e v e r s e
17 end
18
19 p u t s " Cuadrado de 2 = #{cuadrado 2} "
20 p u t s " Los t r i a n g u l o s son " + t r i a n g u l o s
21 p u t s metodo ( " c o c a i n a " , " h a c h i s " , " mariguana " , " e x t a s i s " )
22 p u t s metodo ( ∗ ( 1 . . 3 ) . to_a)
23 f r a s e = " Dabale a r r o z a l a z o r r a e l abad "
24 i f es_palindromo ? f r a s e
25 p u t s " \"#{ f r a s e } \ " e s un palindromo "
26 else
27 p u t s " \"#{ f r a s e } \ " no e s un palindromo "
28 end
William Viana y Andreu Belmonte 21

$ ruby metodos . rb
Cuadrado de 2 = 4
Los t r i a n g u l o s son a d i f e r e n t e s de l o s c u a d r a d o s
Me han pasado c o c a i n a , h a c h i s , mariguana , e x t a s i s
Me han pasado 1 , 2 , 3
" Dabale a r r o z a l a z o r r a e l abad " e s un palindromo
$

Lo primero que llama la atención es que se puede omitir la palabra return ya que por defecto los métodos
devuelven el valor de la última línea evaluada. Con *varios en la línea 10 lo que se consigue es que el método
pueda recibir un número variable de parámetros, varios es convertido en un array con esos parámetros. Otra
cosa que se puede omitir son los paréntesis en la llamada al método, lo hemos estado haciendo todo el rato con
A partir de la versión 1.9 de Ruby en esos casos puts y print, pero en llamadas del tipo:
será obligatorio incluir los paréntesis para la
puts cuadrado 4
llamada del método
puede resultar confuso la omisión de los paréntesis, entonces se recomienda incluirlos. . Si un método no recibe
argumentos no es necesario incluir los paréntesis ni en la definición ni en la llamada al método.

4.1.1 Cómo hacer bien un finger

Tranquilidad, esto no tiene nada que ver con el sexo (aunque por supuesto el título está puesto a conciencia),
sólo voy a proponer un ejercicio. Sigue leyendo.
El comando finger en los entornos Unix nos da información sobre un usuario si le proporcionas un nombre de
usuario. Como ejercicio para practicar un poco te propongo que hagas un finger que pueda recibir tanto un
nombre de usuario como el nombre real de la persona (completo o no) y que muestre por pantalla su nombre y
su nombre de usuario en el sistema. Has de tener en cuenta que si se proporciona un nombre muy común pueden
haber varios usuarios con el mismo nombre, por ejemplo podrías mostrar todas las coincidencias. Para hacerlo
vas a necesitar abrir el fichero /etc/passwd, pero aún no he explicado como abrir ficheros. Bueno, aquí va una
manera que creo que entenderás fácilmente:
1 F i l e : : open ( " / e t c / passwd " ) . each { | l i n e a |#p r o c e s a r }

Para los parámetros pasados por la línea de comandos puedes utilizar ARGV, que es una lista con todos los
parámetros pasados en la línea de comandos, teniendo en cuenta que el primer elemento es el primer argumento
William Viana y Andreu Belmonte 22

y no el nombre del programa, como en otros lenguajes como C. No te preocupes mucho por el tratamiento de
Si tienes curiosidad, en Ruby el nombre del errores. De todas formas dejo un ejemplo hecho pero recomiendo que lo intentes hacer por tu cuenta sin mirarlo
programa se almacena en la variable global $0 y luego comparar (puede que lo hayas hecho mucho más elegante y te podrás reír de mí).

finger.rb
1 #! / u s r / b i n / env r u b y
2 def b u s c a r ( i d )
3 lista = []
4 F i l e : : open ( " / e t c / passwd " ) . each do | l i n e a |
5 l i s t a << l i n e a i f l i n e a . match ( /#{i d } / i )
6 end
7 l i s t a . c o l l e c t ! do | d a t o s |
8 datos = datos . s p l i t ( " : " )
9 v a l o r = d a t o s [ 0 ] , d a t o s [ 4 ] # me quedo con l o que me i n t e r e s a
10 end
11
12 i f l i s t a . l e n g t h > 1 #m u l t i p l e s matchs
13 l i s t a . each_with_index do | v a l o r , i n d i c e |
14 p u t s " [#{ i n d i c e } ] − #{v a l o r [ 1 ] } "
15 end
16 p r i n t " E l i j e uno : "
17 u n t i l ( 0 . . . l i s t a . l e n g t h )===(i n d i c e = STDIN . g e t s . to_i )
18 p r i n t " E l i j e uno : "
19 end
20 l i s t a [ indice ]
21 else
22 lista [0]
23 end
24 end
25
26 i f ARGV. l e n g t h == 1
27 l o g i n , nombre = b u s c a r (ARGV[ 0 ] )
28 i f l o g i n or nombre
29 p u t s " Login : #{ l o g i n } "
30 p u t s " Nombre : #{nombre} "
31 else
32 p u t s "No he e n c o n t r a d o e s e u s u a r i o "
William Viana y Andreu Belmonte 23

33 end
34 e l s e
35 p u t s " Numero e r r o n e o de argumentos "
36 end

4.2 Bloques e iteradores


Un bloque es un trozo de código encerrado entre {} o entre las palabras clave do y end:
{ p u t s "Un b l o q u e s i m p l e " }
do
p u t s "Un b l o q u e "
p u t s " con v a r i a s l i n e a s "
end

Generalmente cuando el bloque tiene más de una línea se usa la sintaxis do..end.
La gracia de los bloques es que se les puede pasar como un argumento a un método.

bloques.rb
1 def metodo ( f o o )
2 yield f o o
3 yield f o o ∗2
4 end
5 valores = [ ]
6 metodo ( 3 ) { | x | v a l o r e s << x} # a l t e r m i n a r v a l o r e s = [ 3 , 6 ]

Parece algo confuso, ¿no? Yo la primera vez que vi algo así me lo pareció. Le estamos pasando a metodo la
constante 3 y un bloque de código. A algunos les gusta pensar que yield lo que hace es pegar el bloque de código
ahí donde es llamado pero en realidad lo que hace es devolverle el control al bloque, éste se ejecuta y devuelve
el control al método. Bloques y métodos tienen un ámbito distinto, lo que es local al método no es accesible
por el bloque pero yield puede interactuar con el bloque pasándole variables. Las variables que va a recibir el
bloque van encerradas entre | | y separadas por comas.
En nuestro ejemplo metodo recibe el valor 3 y lo primero que hace es pasarle el control al método enviándole
el valor de la variable foo (que es 3), el bloque la recibe y la mete en el array valores, a continuación el bloque
William Viana y Andreu Belmonte 24

devuelve el control al método y justo al volver se encuentra con otro yield que vuelve a devolver el control al
bloque (como en un juego de ping-pong) pero esta vez le pasa el valor de foo multiplicado por 2. Al final en
valores habrá un 3 y un 6.
Ahora te puedes hacer una idea de como está implementado un método each. Un each de nuestra propia clase
Array podría ser algo como esto:

mi_each.rb
1 def each
2 i = 0
3 while i < self . length
4 yield s e l f [ i ]
5 i+=1
6 end
7 return self
8 end

Y eso era un iterador. Vale, aún no he explicado nada de clases, tendré que dar un adelanto. self es como el
puntero this del lenguaje C++, es una referencia al propio objeto que llama al método.

4.2.1 ¿Qué echan en el cine?

Veamos un ejemplo que luego resultará útil. Vamos a la web del cine que suelo frecuentar ultimamente, en
concreto a la sección cartelera: . Vamos a parsearla y quedarnos sólo con los títulos de las pelis. Para ello
tenemos que echar una ojeada al código de la página y buscar patrones. Miro el código, basura, basura, basura,
¡lo tengo!
<s e l e c t name=" p e l i c u l a I D " i d=" p e l i c u l a I D " onchange=" m u e s t r a P e l i c u l a ( ) ; ">
<o p t i o n v a l u e=" 0 ">p e l i c u l a s </o p t i o n >
<o p t i o n v a l u e=" 34 ">C o l e g a s en e l Bosque</o p t i o n >
<o p t i o n v a l u e=" 40 ">El C o r r a l , Una F i e s t a Muy B e s t i a </o p t i o n >
....
</s e l e c t >

Los títulos de las pelis están en una zona que empieza con <select name="peliculaID"... > y termina con un
</select>. Cada título va entre las etiquetas <option ...> título de la peli</option>. Ya tenemos los patrones,
¿y ahora qué? Lo primero será poder bajarse el código de esa página para poder tratarlo. Gracias a un japonés,
William Viana y Andreu Belmonte 25

que del nombre no me acuerdo, está disponible en Ruby 1.8 la biblioteca open-uri que nos permite abrir un
require carga el código de otro fichero fichero via http y tratarlo como si fuera un fichero de texto normal:
1 r e q u i r e ’ open−u r i ’
2 web = open ( " h t t p : / /www. n e o c i n e . e s / i n d e x . php ? s=c a r t e l e r a&i d=7" )
3 web . e a c h _ l i n e { | l i n e a | p u t s l i n e a }

La estrategia será la siguiente


• Leer el fichero línea a línea hasta encontrar una línea que contenga <select name="peliculaID ...>
• A partir de entonces leer cada línea y extraer el título de la peli eliminando las etiquetas feas...
• ...hasta que encuentres una etiqueta de cierre </select>
Vale, ahora es cuando las expresiones regulares nos vienen bien. Intenta escribir tú una expresión regular que
empiece por <select name="peliculaID seguido de cualquier cosa que no sea un > y luego un símbolo >. Si te
rindes mira la solución:
/<select name="peliculaID"[ˆ>]*>/
¡Así que si encuentras esa línea para! Puedes parar con un break. Luego hay una línea fea que no nos interesa,
la ignoramos con un web.readlineque lee la línea y salta a la siguiente. A partir de aquí las líneas contienen los
títulos de las pelis entre las etiquetas <option> y </option>. En realidad no, puede que haya alguna línea
en blanca o que al webmaster se le ocurra meter un comentario por ahí, así que tenemos que asegurarnos que la
línea cumpla la siguiente expresión regular:
/<option[ˆ>]*>.+<option>/i
Ya tenemos la línea que nos interesa, ahora deshagámonos de las etiquetas feas que rodean los títulos:
linea.gsub( /<?[ˆ>]+>/i, "" )
Eso indica: sustituye todo lo que empiece por un < seguido de una /, o no, seguido de cualquier carácter que
no sea un >, una o más veces, seguido de un > por una cadena vacía. ¿Cuanto con tan poco, verdad? Si al
final va a ser que las expresiones regulares molan y todo. Por si aún quedaran espacios al principio o saltos de
línea los eliminamos con el método strip. ¿Qué hacemos con el título ahora? Pues se lo tiramos a un bloque y
que haga él lo que quiera, y también lo metemos en una lista por si alguien los quiere todos juntitos. Todo junto
sería algo así:
William Viana y Andreu Belmonte 26

pelis_en_cartelera.rb
1 r e q u i r e ’ open−u r i ’
2
3 def b u s c a r T i t u l o s
4 web = open ( " h t t p : / /www. n e o c i n e . e s / i n d e x . php ? s=c a r t e l e r a&i d=7" )
5 lista_pelis = []
6 web . e a c h _ l i n e do | l i n e a |
7 break i f l i n e a . match ( /< s e l e c t name=" p e l i c u l a I D "[^ >]∗ > / i )
8 end
9 web . r e a d l i n e
10 web . e a c h _ l i n e do | l i n e a |
11 break i f l i n e a . match ( /<\/ s e l e c t >/ i )
12 i f l i n e a . match ( /<o p t i o n [^>]∗>.+<\/ o p t i o n >/ i )
13 t i t u l o = l i n e a . gsub ( /<\/?[^>]+>/ i , " " )
14 t i t u l o . strip !
15 yield t i t u l o
16 l i s t a _ p e l i s << t i t u l o
17 end
18 end
19 return l i s t a _ p e l i s
20 end
21
22 b u s c a r T i t u l o s { | t i t u l o _ p e l i | puts t i t u l o _ p e l i }

«¡Qué estúpido! Para eso miro directamente la web que está todo más bonito». Bueno, ya haremos algo más
interesante con los títulos de las pelis, de momento pasemos al siguiente capítulo.
5 Un lenguaje con clase

5.1 Clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
5.1.1 Acceso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
5.1.2 Herencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
5.2 Módulos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
5.3 Mixins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33

27
William Viana y Andreu Belmonte 28

5.1 Clases
Vayamos al grano. La definición de la clase va entre las palabras class y end. El nombre de la clase ha de
empezar por mayúsculas. Los constructores de una clase se llaman initialize. Los atributos del objeto empiezan
por una @ y los atributos de la clase por @@.
1 c l a s s Persona
2 def i n i t i a l i z e
3 @nombre = " Pepeeee "
4 @dni = " 12345789A"
5 @mail = " pepeeee@pepe . com "
6 @ l o g i n = " pepe "
7 @ c h i s t e = " Mi j e f e me e n c a r g o i r a B a r c e l o n a para . . . "
8 end
9
10 def c o n t a r C h i s t e
11 @chiste
12 end
13 end

Para acceder desde fuera de la clase a sus atributos para lectura has de definir un método con el mismo nombre
que el atributo. Si quieres modificar el valor de los atributos desde fuera has de definir métodos que tengan el
mismo nombre que el atributo seguido de un símbolo =. La definición de la clase sigue abierta, por eso podemos
hacer:
1 c l a s s Persona
2 def nombre
3 @nombre
4 end
5 def l o g i n =( u n l o g i n )
6 @nombre = un_nombre
7 end
8 end

Ahora tenemos el constructor que habíamos definido antes más los nuevos métodos. Afortunadamente hay una
forma abreviada de hacerlo.
William Viana y Andreu Belmonte 29

1 c l a s s Persona
2 a t t r _ r e a d e r : nombre , : d n i #podemos a c c e d e r para l e c t u r a
3 attr_writer : login #e s l o mismo que e l metodo l o g i n= de a n t e s
4 a t t r _ a c c e s s o r : mail , : c h i s t e #podemos a c c e d e r para l e c t u r a y
modificacion
5 end

¿Te acuerdas de los rangos? Puedes definir tus propios tipos de datos y poder crear rangos con ellos. Lo único que
hace falta es que definas un método succ que actúe de una manera apropiada, incluir el módulo Comparable y
definir el método <=>. WTF! ¿Incluir un moqué dentro de una clase? Bueno, por ahora saber que al hacerlo y
teniendo definido el método <=> de gratis nuestra clase gana los métodos <, >, <=, >=, ===, == y algunos
más. Ya veremos por qué, ahora veamos todo esto en práctica.

dia_semana.rb
1
2 c l a s s DiaSemana
3 Dias_semana = [ " l u n e s " , " martes " , " m i e r c o l e s " , " j u e v e s " , " v i e r n e s " , \
4 " sabado " , " domingo " ]
5 i n c l u d e Comparable
6
7 a t t r _ r e a d e r : numdia
8 def i n i t i a l i z e ( d i a )
9 i f Dias_semana . i n c l u d e ? d i a
10 @numdia = Dias_semana . i n d e x ( d i a )
11 else
12 r a i s e ArgumentError , " Dia no v a l i d o "
13 end
14 end
15
16 def <=>(o t r o d i a )
17 @numdia <=> o t r o d i a . numdia
18 end
19
20 def succ
21 DiaSemana . new ( Dias_semana [ @numdia . succ % 7 ] )
22 end