08. Escribiendo tus propios métodos
Como hemos visto, los bucles y los iteradores nos permiten hacer lo mismo (ejecutar el mismo código) una y otra vez. Sin embargo, a veces queremos hacer lo mismo muchas veces, pero desde diferentes lugares del programa. Por ejemplo, supongamos que estamos escribiendo un programa de cuestionario para un estudiante de psicología. Teniendo en cuenta a los estudiantes de psicología que conozco y los cuestionarios que me proporcionaron, sería algo así:
puts 'Hola, y gracias por tomarte el tiempo para ayudarme'
puts 'con este experimento. Mi experimento tiene que ver'
puts 'con cómo se siente la gente con la comida mexicana.'
puts 'Solo piensa en comida mexicana e intenta responder'
puts 'cada pregunta honestamente, con un "sí" o un "no".'
puts 'Mi experimento no tiene nada que ver con mojar la cama.'
puts
# Hacemos estas preguntas, pero ignoramos las respuestas.
buenaRespuesta = false
while (not buenaRespuesta)
puts '¿Te gusta comer tacos?'
respuesta = gets.chomp.downcase
if (respuesta == 'sí' or respuesta == 'no')
buenaRespuesta = true
else
puts 'Por favor, responde "sí" o "no".'
end
end
buenaRespuesta = false
while (not buenaRespuesta)
puts '¿Te gusta comer burritos?'
respuesta = gets.chomp.downcase
if (respuesta == 'sí' or respuesta == 'no')
buenaRespuesta = true
else
puts 'Por favor, responde "sí" o "no".'
end
end
# Sin embargo, prestamos atención a *esta* pregunta.
buenaRespuesta = false
while (not buenaRespuesta)
puts '¿Mojas la cama?'
respuesta = gets.chomp.downcase
if (respuesta == 'sí' or respuesta == 'no')
buenaRespuesta = true
if respuesta == 'sí'
mojaCama = true
else
mojaCama = false
end
else
puts 'Por favor, responde "sí" o "no".'
end
end
buenaRespuesta = false
while (not buenaRespuesta)
puts '¿Te gusta comer chimichangas?'
respuesta = gets.chomp.downcase
if (respuesta == 'sí' or respuesta == 'no')
buenaRespuesta = true
else
puts 'Por favor, responde "sí" o "no".'
end
end
puts 'Solo unas pocas preguntas más...'
buenaRespuesta = false
while (not buenaRespuesta)
puts '¿Te gusta comer sopapillas?'
respuesta = gets.chomp.downcase
if (respuesta == 'sí' or respuesta == 'no')
buenaRespuesta = true
else
puts 'Por favor, responde "sí" o "no".'
end
end
# Haz muchas otras preguntas sobre comida mexicana.
puts
puts 'DEBRIEFING:'
puts 'Gracias por tomarte el tiempo para ayudar con'
puts 'este experimento. De hecho, este experimento'
puts 'no tiene nada que ver con la comida mexicana.'
puts 'Es un experimento sobre mojar la cama. La comida'
puts 'mexicana estaba ahí solo para bajarte la guardia'
puts 'con la esperanza de que respondieras más'
puts 'honestamente. Gracias de nuevo.'
puts
puts mojaCama
Un programa bonito y largo, con mucha repetición. (Todas las secciones de código alrededor de las preguntas sobre comida mexicana son idénticas, y la pregunta sobre mojar la cama es ligeramente diferente). La repetición es algo malo. Pero no podemos hacer un gran iterador, porque a veces queremos hacer algo entre las preguntas. En situaciones como esta, es mejor escribir un método. Así es como se hace:
def diMuu
puts 'muuuuuuu...'
end
Eh... Nuestro programa no dijo muuuuuuu... ¿Por qué no? Porque no le dijimos que lo hiciera. Solo le dijimos cómo decir muuuuuuu..., pero nunca le dijimos que lo hiciera realmente. Démosle otra oportunidad:
def diMuu
puts 'muuuuuuu...'
end
diMuu
diMuu
puts 'cuac-cuac'
diMuu
diMuu
¡Ah! Mucho mejor. (En caso de que no hables pato, eso fue un pato en medio del programa).
Entonces, definimos el método diMuu. (Los nombres de los métodos, como los nombres de las variables, comienzan con una letra minúscula. Hay excepciones, como + o ==). ¿Pero los métodos no tienen que estar siempre asociados con objetos? Bueno, sí, lo hacen. Y en este caso (al igual que con puts y gets), el método está asociado solo con el objeto que representa el programa en su conjunto. En el próximo capítulo veremos cómo agregar métodos a otros objetos. Pero primero...
Parámetros de Métodos
Es posible que hayas notado que algunos métodos (como gets, o to_s, o reverse...) se pueden llamar simplemente en un objeto. Sin embargo, otros métodos (como +, -, puts...) toman parámetros para decirle al objeto qué hacer con el método. Por ejemplo, no dices simplemente 5+, ¿verdad? Le estás diciendo a 5 que sume, pero no le estás diciendo qué sumar.
Para agregar un parámetro a diMuu (el número de mugidos, por ejemplo), podemos hacer lo siguiente:
def diMuu numeroDeMuus
puts 'muuuuuuu...'*numeroDeMuus
end
diMuu 3
puts 'oink-oink'
diMuu # Esto dará un error porque no se pasó ningún parámetro.
numeroDeMuus es una variable que apunta al parámetro que se pasó. Lo diré de nuevo, pero es un poco confuso: numeroDeMuus es una variable que apunta al parámetro que se pasó. Entonces, si escribo diMuu 3, el parámetro es 3, y la variable numeroDeMuus apunta a 3.
Como puedes ver, ahora el parámetro es obligatorio. Después de todo, lo que se supone que debe hacer diMuu es multiplicar 'muuuuuuu' por un número. ¿Pero por cuánto, si no lo dijiste? Tu computadora no tiene ni idea.
Si comparamos objetos en Ruby con sustantivos en español, los métodos pueden compararse de manera similar con los verbos. Por lo tanto, puedes pensar en los parámetros como adverbios (como en diMuu, donde el parámetro nos dice cómo hacer diMuu) o a veces como objetos directos (como en puts, donde el parámetro es qué imprimirá puts).
Variables Locales
En el siguiente programa, hay dos variables:
def duplicaEsto num
numVeces2 = num*2
puts 'El doble de '+num.to_s+' es '+numVeces2.to_s
end
duplicaEsto 44
Las variables son num y numVeces2. Ambas están ubicadas dentro del método duplicaEsto. Estas (y todas las demás variables que has visto hasta ahora) son variables locales. Esto significa que viven dentro del método y no pueden salir. Si lo intentas, obtendrás un error:
def duplicaEsto num
numVeces2 = num*2
puts 'El doble de '+num.to_s+' es '+numVeces2.to_s
end
duplicaEsto 44
puts numVeces2.to_s
Variable local indefinida... En realidad, definimos esa variable local, pero no es local en relación con el lugar donde intentamos usarla; es local al método.
Esto puede parecer inconveniente, pero en realidad es muy bueno. Si bien no tienes acceso a las variables dentro de los métodos, esto también significa que nadie tiene acceso a tus variables, y eso significa que nadie puede hacer algo como esto:
def pequeñaPeste var
var = nil
puts '¡JAJA! ¡Arruiné tu variable!'
end
var = '¡No puedes tocar mi variable!'
pequeñaPeste var
puts var
En realidad, hay dos variables en ese pequeño programa llamadas var: una dentro del método pequeñaPeste y otra fuera de él. Cuando llamaste a pequeñaPeste var, realmente solo pasamos la cadena que estaba en var a la otra, por lo que apuntaban a la misma cadena. Luego, el método pequeñaPeste apuntó su var local a nil, pero eso no le hizo nada a la var fuera del método.
Valores de Retorno
Es posible que hayas notado que algunos métodos devuelven algo cuando los llamas. Por ejemplo, el método gets devuelve una cadena (la cadena que escribiste), y el método + en 5+3 (que en realidad es 5.+(3)) devuelve 8. Los métodos aritméticos para números devuelven números, y los métodos aritméticos para cadenas devuelven cadenas.
Es importante entender la diferencia entre los métodos que devuelven un valor al lugar donde se llamó, y tu programa que genera una salida a tu pantalla, como lo hace puts. Ten en cuenta que 5+3 devuelve 8; no imprime 8 en la pantalla.
Entonces, ¿qué devuelve puts? Nunca nos importó antes, pero echemos un vistazo ahora:
valorRetorno = puts 'Este puts devolvió:'
puts valorRetorno
El primer puts devolvió nil. Aunque no probamos el segundo puts, hizo lo mismo; puts siempre devuelve nil. Todo método tiene que devolver algo, incluso si es solo nil.
Tómate un descanso y escribe un programa que averigüe qué devolvió el método diMuu.
¿Estás sorprendido? Bueno, así es como funciona: el valor de retorno de un método es simplemente la última línea evaluada en el método. En el caso del método diMuu, esto significa que devolvió 'puts muuuuuuu...'*numeroDeMuus, que es simplemente nil, ya que puts siempre devuelve nil. Si quisiéramos que todos nuestros métodos devolvieran la cadena 'submarino amarillo', simplemente tendríamos que ponerla al final de ellos:
def diMuu numeroDeMuus
puts 'muuuuuuu...'*numeroDeMuus
'submarino amarillo'
end
x = diMuu 2
puts x
Ahora intentemos esa encuesta de psicología nuevamente, pero esta vez escribiremos un método para hacernos las preguntas. Tendrá que tomar la pregunta como un parámetro y devolver true si la respuesta fue sí y false si la respuesta fue no. (Incluso si ignoramos la respuesta la mayor parte del tiempo, es una buena idea hacer que el método devuelva la respuesta. De esa manera podemos usarla para la pregunta sobre mojar la cama). También voy a acortar el saludo y la despedida, solo para que sea más fácil de leer:
def preguntar pregunta
buenaRespuesta = false
while (not buenaRespuesta)
puts pregunta
replica = gets.chomp.downcase
if (replica == 'sí' or replica == 'no')
buenaRespuesta = true
if replica == 'sí'
respuesta = true
else
respuesta = false
end
else
puts 'Por favor, responde "sí" o "no".'
end
end
respuesta # Esto es lo que devolvemos (true o false).
end
puts 'Hola, y gracias por...'
puts
preguntar '¿Te gusta comer tacos?' # Ignoramos este valor de retorno.
preguntar '¿Te gusta comer burritos?'
mojaCama = preguntar '¿Mojas la cama?' # Guardamos este valor de retorno.
preguntar '¿Te gusta comer chimichangas?'
preguntar '¿Te gusta comer sopapillas?'
preguntar '¿Te gusta comer tamales?'
puts 'Solo unas pocas preguntas más...'
preguntar '¿Te gusta beber horchata?'
preguntar '¿Te gusta comer flautas?'
puts
puts 'DEBRIEFING:'
puts 'Gracias por...'
puts
puts mojaCama
No está mal, ¿eh? Podemos agregar más preguntas (y agregar más preguntas es fácil ahora), ¡pero nuestro programa sigue siendo corto! Esto es un gran progreso: el sueño de todo programador perezoso.
Un Gran Ejemplo Más
Creo que otro ejemplo de método sería muy útil aquí. Llamemos a este numeroEspanol. Este método tomará un número, como 22, y devolverá la versión en español del mismo (en este caso, la cadena 'veintidós'). Por ahora, limitémoslo a enteros entre 0 y 100.
(NOTA: Este método utiliza un nuevo truco para regresar de un método antes de tiempo usando la palabra clave return, e introduce un nuevo concepto: elsif. Esto debería quedar claro en el contexto).
def numeroEspanol numero
# Solo queremos números entre 0 y 100.
if numero < 0
return 'Por favor, introduce un número que no sea negativo.'
end
if numero > 100
return 'Por favor, introduce un número que sea 100 o menos.'
end
numTexto = '' # Esta es la cadena que devolveremos.
# "falta" es cuánto del número nos falta escribir.
# "escribiendo" es la parte que estamos escribiendo ahora mismo.
falta = numero
escribiendo = falta/100 # ¿Cuántos cientos faltan por escribir?
falta = falta - escribiendo*100 # Resta esos cientos.
if escribiendo > 0
return 'cien'
end
escribiendo = falta/10 # ¿Cuántas decenas faltan por escribir?
falta = falta - escribiendo*10 # Resta esas decenas.
if escribiendo > 0
if ((escribiendo == 1) and (falta > 0))
# Como no podemos escribir "diez-dos" en lugar de "doce",
# tenemos que hacer una excepción especial para estos.
if falta == 1
numTexto = numTexto + 'once'
elsif falta == 2
numTexto = numTexto + 'doce'
elsif falta == 3
numTexto = numTexto + 'trece'
elsif falta == 4
numTexto = numTexto + 'catorce'
elsif falta == 5
numTexto = numTexto + 'quince'
elsif falta == 6
numTexto = numTexto + 'dieciséis'
elsif falta == 7
numTexto = numTexto + 'diecisiete'
elsif falta == 8
numTexto = numTexto + 'dieciocho'
elsif falta == 9
numTexto = numTexto + 'diecinueve'
end
# Como ya nos ocupamos del dígito en el lugar de las unidades,
# no nos queda nada por escribir.
falta = 0
elsif escribiendo == 1
numTexto = numTexto + 'diez'
elsif escribiendo == 2
numTexto = numTexto + 'veinte'
elsif escribiendo == 3
numTexto = numTexto + 'treinta'
elsif escribiendo == 4
numTexto = numTexto + 'cuarenta'
elsif escribiendo == 5
numTexto = numTexto + 'cincuenta'
elsif escribiendo == 6
numTexto = numTexto + 'sesenta'
elsif escribiendo == 7
numTexto = numTexto + 'setenta'
elsif escribiendo == 8
numTexto = numTexto + 'ochenta'
elsif escribiendo == 9
numTexto = numTexto + 'noventa'
end
if falta > 0
numTexto = numTexto + ' y '
end
end
escribiendo = falta # ¿Cuántas unidades faltan por escribir?
falta = 0 # Resta esas unidades.
if escribiendo > 0
if escribiendo == 1
numTexto = numTexto + 'uno'
elsif escribiendo == 2
numTexto = numTexto + 'dos'
elsif escribiendo == 3
numTexto = numTexto + 'tres'
elsif escribiendo == 4
numTexto = numTexto + 'cuatro'
elsif escribiendo == 5
numTexto = numTexto + 'cinco'
elsif escribiendo == 6
numTexto = numTexto + 'seis'
elsif escribiendo == 7
numTexto = numTexto + 'siete'
elsif escribiendo == 8
numTexto = numTexto + 'ocho'
elsif escribiendo == 9
numTexto = numTexto + 'nueve'
end
end
if numTexto == ''
# La única forma en que "numTexto" podría estar vacío es si
# "numero" es 0.
return 'cero'
end
# Si llegamos hasta aquí, entonces teníamos un número en algún lugar
# entre 0 y 100, así que necesitamos devolver "numTexto".
numTexto
end
puts numeroEspanol( 0)
puts numeroEspanol( 9)
puts numeroEspanol( 10)
puts numeroEspanol( 11)
puts numeroEspanol( 17)
puts numeroEspanol( 32)
puts numeroEspanol( 88)
puts numeroEspanol( 99)
puts numeroEspanol(100)
Bueno, hay algunas cosas que no me gustan de este programa. Primero: hay demasiada repetición. Segundo: no maneja números mayores que 100. Tercero: hay demasiados casos especiales, demasiados return. Usemos algunos arrays e intentemos limpiarlo:
def numeroEspanol numero
if numero < 0 # No números negativos.
return 'Por favor, introduce un número que no sea negativo.'
end
if numero == 0
return 'cero'
end
# ¡No más casos especiales! ¡No más returns!
numTexto = '' # Esta es la cadena que devolveremos.
unidades = ['uno', 'dos', 'tres', 'cuatro', 'cinco',
'seis', 'siete', 'ocho', 'nueve']
decenas = ['diez', 'veinte', 'treinta', 'cuarenta', 'cincuenta',
'sesenta', 'setenta', 'ochenta', 'noventa']
especiales = ['once', 'doce', 'trece', 'catorce', 'quince',
'dieciséis', 'diecisiete', 'dieciocho', 'diecinueve']
# "falta" es cuánto del número nos falta escribir.
# "escribiendo" es la parte que estamos escribiendo ahora mismo.
falta = numero
escribiendo = falta/100 # ¿Cuántos cientos faltan por escribir?
falta = falta - escribiendo*100 # Resta esos cientos.
if escribiendo > 0
# Ahora aquí hay un truco realmente astuto:
cientos = numeroEspanol escribiendo
numTexto = numTexto + cientos + ' cientos'
# Eso se llama "recursividad". ¿Entonces qué acabo de hacer?
# Le dije a este método que se llamara a sí mismo, pero con "escribiendo" en lugar de
# "numero". Recuerda que "escribiendo" es (en este momento) el número de
# cientos que tenemos que escribir. Después de agregar "cientos" a
# "numTexto", agregamos la cadena ' cientos'. Entonces, por ejemplo, si
# llamamos originalmente a numeroEspanol con 1999 (entonces numero = 1999),
# entonces en este punto escribiendo sería 19, y "falta" sería 99.
# Lo más perezoso que se puede hacer en este punto es hacer que numeroEspanol
# escriba el 'diecinueve' por nosotros, luego escribimos ' cientos',
# y luego el resto de numeroEspanol escribe 'noventa y nueve'.
if falta > 0
# Para no escribir 'dos cientoscincuenta y uno'...
numTexto = numTexto + ' '
end
end
escribiendo = falta/10 # ¿Cuántas decenas faltan por escribir?
falta = falta - escribiendo*10 # Resta esas decenas.
if escribiendo > 0
if ((escribiendo == 1) and (falta > 0))
# Como no podemos escribir "diez-dos" en lugar de "doce",
# tenemos que hacer una excepción especial para estos.
numTexto = numTexto + especiales[falta-1]
# El "-1" es porque especiales[3] es 'catorce', no 'trece'.
# Como ya nos ocupamos del dígito en el lugar de las unidades,
# no nos queda nada por escribir.
falta = 0
else
numTexto = numTexto + decenas[escribiendo-1]
# El "-1" es porque decenas[3] es 'cuarenta', no 'treinta'.
end
if falta > 0
# Para no escribir 'sesentacuatro'...
numTexto = numTexto + ' y '
end
end
escribiendo = falta # ¿Cuántas unidades faltan por escribir?
falta = 0 # Resta esas unidades.
if escribiendo > 0
numTexto = numTexto + unidades[escribiendo-1]
# El "-1" es porque unidades[3] es 'cuatro', no 'tres'.
end
# Ahora simplemente devolvemos "numTexto"...
numTexto
end
puts numeroEspanol( 0)
puts numeroEspanol( 9)
puts numeroEspanol( 10)
puts numeroEspanol( 11)
puts numeroEspanol( 17)
puts numeroEspanol( 32)
puts numeroEspanol( 88)
puts numeroEspanol( 99)
puts numeroEspanol(100)
puts numeroEspanol(101)
puts numeroEspanol(234)
puts numeroEspanol(3211)
puts numeroEspanol(999999)
puts numeroEspanol(1000000000000)
Ahhhh.... Eso es mucho mejor. El programa es bastante denso, por eso puse tantos comentarios. Incluso funciona para números grandes... aunque no tan bien como uno podría esperar. Por ejemplo, creo que 'un billón' sería un valor de retorno más agradable para ese último número, o incluso 'un millón de millones'. De hecho, puedes hacer eso ahora mismo...
Algunas cosas para probar
- Mejora
numeroEspanol. Primero, pon los miles. Así que debería devolver 'mil' en lugar de 'diez cientos' y 'diez mil' en lugar de 'un cientos cientos'. - Amplía
numeroEspanolun poco más. Ahora pon millones, para obtener 'un millón' en lugar de 'mil mil'. Luego intenta agregar billones y trillones. ¿Qué tan alto puedes llegar? - "Noventa y nueve botellas de cerveza..." Usando
numeroEspanoly tu viejo programa, escribe la letra de esta canción de la manera correcta esta vez. Castiga a tu computadora: haz que comience en 9999. (Sin embargo, no elijas un número demasiado grande, porque escribir todo eso en la pantalla le toma a tu computadora un tiempo. Cien mil botellas de cerveza toman algún tiempo; ¡y si eliges un millón, te estarás castigando a ti mismo también!)
¡Felicidades! ¡En este punto, eres verdaderamente un programador! Has aprendido todo lo que necesitas para escribir programas enormes desde cero. Si tienes ideas para programas que te gustaría escribir para ti, ¡pruébalos!
Por supuesto, construir todo desde cero puede ser un proceso lento. ¿Por qué perder el tiempo escribiendo código que alguien más ya ha escrito? ¿Quieres enviar un correo electrónico? ¿Quieres guardar y cargar archivos en tu computadora? ¿Qué tal generar páginas web para un tutorial donde los ejemplos de código se ejecutan realmente cada vez que se carga la página? :) Ruby tiene muchos tipos diferentes de objetos que podemos usar para ayudarnos a escribir programas mejor y más rápido.