x += y; //equivalente a x = x + y;
x *= y; //equivalente a x = x * y;
x -= y; //equivalente a x = x - y;
x++; //equivalente a x = x + 1;
Realizá las siguientes consultas en la consola:
longitud([])
longitud(numerosDeLoteria)
longitud([4, 3])
Y si delegamos? Podríamos separar la lógica de la siguiente manera:
function sePuedeConcentrar(infusion, temperatura, estaProgramando) {
return infusionATemperaturaCorrecta(infusion, temperatura) && estaProgramando;
}
Al delegar correctamente, hay veces en las que no es necesario alterar el orden de
precedencia, ¡otro punto a favor de la delegación!
La delegación es la forma que tenemos en objetos de dividir en subtareas: separar un
problema grande en problemas más chicos para que nos resulte más sencillo resolverlo.
A diferencia de lenguajes sin objetos, aquí debemos pensar dos cosas:
1. cómo dividir la subtarea, lo cual nos llevará a delegar ese comportamiento en
varios métodos;
2. qué objeto tendrá la responsabilidad de resolver esa tarea.
Siguiente Ejercicio: ¿Es mi responsabilidad?
En Ruby, es una convención que los mensajes que devuelven booleanos (o sea,
verdadero o falso) terminen con un ?.
Intentá respetarla cuando crees tus propios mensajes, acordate que uno de los objetivos
del código es comunicar nuestras ideas a otras personas... y las convenciones, muchas
veces, nos ayudan con esto.
En Ruby, podemos simplicar la manera de escribir un if dentro un else con elsif. Por
ejemplo este código:
def self.nota_conceptual(nota)
if nota > 8
"Sobresaliente"
else
if nota > 6
"Satisfactoria"
else
"No satisfactoria"
end
end
end
Lo podemos escribir:
def self.nota_conceptual(nota)
if nota > 8
"Sobresaliente"
elsif nota > 6
"Satisfactoria"
else
"No satisfactoria"
end
end
Antes de seguir, ¿te animás a editar tu solución para que use elsif?
¿Qué pasa si dos objetos, como Pepita, Norita o Pepo son capaces de responder a un mismo
mensaje? Podemos intercambiar un objeto por otro sin notar la diferencia, como
experimentaste recién.
Este concepto es fundamental en objetos, y lo conocemos como polimorfismo. Decimos
entonces que dos objetos son polimórficos cuando pueden responder a un mismo
conjunto de mensajes y hay un tercer objeto que los usa indistintamente. Dicho de otra
forma, dos objetos son polimórficos para un tercer objeto cuando este puede enviarles
los mismos mensajes, sin importar cómo respondan o qué otros mensajes entiendan.
En nuestro caso:
Pepita, Norita y Pepo son polimórficas para Emilce.
Pepita, Norita y Pepo no son polimórficas para Pachorra.
Pepita y Pepo son polimórficas para Pachorra.
Hasta ahora, en objetos, un programa es simplemente una secuencia de envíos de
mensajes. Por ejemplo, éste es un programa que convierte en mayúsculas al string "hola".
> "hola".upcase
=> "HOLA"
Sin embargo, podemos hacer algo más: declarar variables. Por ejemplo, podemos declarar
una variable saludo, inicializarla con "hola", enviarle mensajes...
> saludo = "hola"
> [Link]
=> "HOLA"
...y esperar el mismo resultado que para el programa anterior.
El mensaje equal? nos dice si dos objetos son el mismo. Veamos qué pasó con las pruebas
del ejercicio anterior:
otro_saludo = "buen día" # se crea la variable otro_saludo que referencia al objeto "buen día"
despedida = otro_saludo # se crea la variable despedida que, por asignarle la referencia otro_saludo, apunta al mismo objeto
> "buen día".equal? "buen día"
=> false
> [Link]? "buen día"
=> false
En ambos casos el resultado fue false, dado que aquellos strings son objetos distintos, a
pesar de que tengan los mismos caracteres. Cada vez que escribimos un string estamos
creando un nuevo objeto. Sin embargo:
> otro_saludo.equal? otro_saludo
=> true
> [Link]? otro_saludo
=> true
¿Por qué? ¡Simple! Ambas referencias, otro_saludo y despedida, apuntan al mismo objeto. La
moraleja es que declarar una variable significa agregar una nueva referencia al objeto
existente, en lugar de copiarlo:
Distinto sería si hacemos:
otro_saludo = "buen día"
despedida = "buen día"
Lo cual da como resultado este ambiente:
Veamos otro ejemplo. Si tuvieramos el siguiente código...
persona = "Graciela"
hija_de_hector = "Graciela"
hermana_de_tito = persona
hija_de_elena = "Gracielita"
hermana_de_ana = hermana_de_tito
mama_de_gustavo = "hermana_de_ana"
tia_de_gonzalo = hija_de_hector
... podríamos decir que solo hermana_de_tito y hermana_de_ana referencian al mismo objeto
que persona.
Ya entendimos que dos strings con el mismo contenido no necesariamente son el mismo
objeto. Pero esto puede ser poco práctico . ¿Cómo hacemos si realmente queremos saber
si dos objetos, pese a no ser el mismo, tienen el mismo estado?
Entonces, ¿qué pasa si lo que quiero es comparar los objetos no por su identidad, sino por
que representen la misma cosa?
Pensemos un caso concreto. ¿Hay forma de saber si dos strings representan la misma
secuencia de caracteres más allá de que no sean el mismo objeto? ¡Por supuesto que la
hay! Y no debería sorprendernos a esta altura que se trate de otro mensaje:
> "hola" == "hola"
=> true
> "hola" == "adiós"
=> false
> "hola".equal? "hola"
=> false
El mensaje == nos permite comparar dos objetos por equivalencia; lo cual se da
típicamente cuando los objetos tienen el mismo estado. Y como vemos, puede
devolver true, aún cuando los dos objetos no sean el mismo.
Por ejemplo, en este caso...
procer = "San Martín"
avenida = "San Martín"
ciudad = "San Martín"
... las 3 referencias distintas apuntan a objetos equivalentes entre sí, pero no idénticos.
¡Cuidado! A diferencia de la identidad, que todos los objetos la entienden sin tener que
hacer nada especial, la equivalencia es un poco más complicada.
Por defecto, si bien todos los objetos también la entienden, delega en la identidad,
así que muchas veces es lo mismo enviar uno u otro mensaje;
y para que realmente compare a los objetos por su estado, vos tenés que
implementar este método a mano en cada objeto que crees. Los siguientes objetos
ya la implementan:
o Listas
o Números
o Strings
o Booleanos
Hasta ahora sólo vimos un tipo de colección: las listas. ¡Pero hay más!
Otro tipo muy común de colecciones son los sets (conjuntos), los cuales tienen algunas
diferencias con las listas:
no admiten elementos repetidos;
sus elementos no tienen un orden determinado.
Vamos a ver un ejemplo transforma una lista en un set utilizando to_set:
> numeros_aleatorios = [1,27,8,7,8,27,87,1]
> numeros_aleatorios
=> [1,27,8,7,8,27,87,1]
> numeros_aleatorios.to_set
=> #<Set: {1, 27, 8, 7, 87}>
Algo importante a tener en cuenta es que tanto las listas como los sets tienen mensajes
en común. Dicho de otro modo, son polimórficos para algunos mensajes. Por
ejemplo: push, delete, include? y size.
Sin embargo, los siguientes mensajes...
numeros_de_la_suerte = [6, 7, 42]
numeros_de_la_suerte.first
# Nos retorna el primer elemento de la lista
numeros_de_la_suerte.last
# Nos retorna el último de la lista
numeros_de_la_suerte.index 7
# Nos retorna la posición de un elemento en la lista
... no podemos enviárselos a un set porque sus elementos no están ordenados.
Pero no te preocupes, todos lo que veamos de ahora en adelante en esta lección funciona
tanto para listas como para sets.
Hay una diferencia notable entre los primeros dos mensajes (push y delete) y los otros dos
(include? y size):
1. push y delete, al ser evaluados, modifican la colección. Dicho de otra forma, producen
un efecto sobre la lista en sí: agregan o quitan un elemento del conjunto.
2. include? y size sólo nos retornan información sobre la colección. Son métodos sin
efecto.
Ahora que ya dominás las listas, es el turno de subir un nivel más...
¡Pausa! Antes de continuar, necesitamos conocer a unos nuevos amigos: los bloques.
Los bloques son objetos que representan un mensaje o una secuencia de envíos de
mensajes, sin ejecutar, lista para ser evaluada cuando corresponda. La palabra con la que
se definen los bloques en Ruby es proc. Por ejemplo, en este caso le asignamos
un bloque a incrementador:
un_numero = 7
incrementador = proc { un_numero = un_numero + 1 }
Ahora avancemos un pasito: en este segundo ejemplo, al bloque { otro_numero = otro_numero
* 2 } le enviamos el mensaje call, que le indica que evalúe la secuencia de envíos de
mensajes dentro de él.
otro_numero = 5
duplicador = proc { otro_numero = otro_numero * 2 }.call
¿Cuánto vale un_numero luego de las primeras dos líneas? Tené en cuenta que la secuencia
de envío de mensajes en el bloque del primer ejemplo está sin ejecutar. En cambio, en el
ejmplo de otro_numero estamos enviando el mensaje call. Por lo tanto:
un_numero vale 7, porque el bloque incrementador no está aplicado. Por tanto, no se le
suma 1.
otro_numero vale 10, porque el bloque duplicador se aplica mediante el envío de
mensaje call, que hace que se ejecute el código dentro del bloque. Por tanto, se
duplica su valor.