class Persona
:attr_accessor :name
def initialize(name)
self.name = name
end
end
class Saludador
def saludar(a)
"asd" + a.nombre
end
end
Saludador.new().saludar(Persona.new("asd"))
Esto rompe en ejecución cuando intento mandar este mensaje, porque persona no entiende :nombre, sino que entiende :name.
Tendríamos que agregar algún tipo de validación para que esto no pase, o sea, un sistema de tipos.
Además, para poder ser usado con saludador, lo que le pasamos al método saludar tiene que ser una Persona o alguien que entienda :nombre.
Cuando hablamos de tipos, hablamos de un conjunto de elementos o valores asociado a un conjunto de operaciones.
Si el saludador no puede interactuar con las operaciones que yo espero que interactúe, entonces tenemos un error de tipos.
En objetos, el tipo de uno de ellos (valores) está definido por el conjunto de métodos que entiende. Si hay dos elementos que entienden un mismo mensaje, entonces comparten un tipo, pero no quiere decir que sean del mismo.
No pasa por qué tecnología tengo para extender el tipo o no, ni tampoco por si va a saltar cuando lo ejecute o antes, porque eso es parte del chequeo.
object Saludador extends App {
case class Persona(name: String)
class Saludador {
def saludar(alguien: Persona) =
"hola" + alguien.nombre
}
new Saludador().saludar(new Persona("asdds"))
}
Además, si la firma de saludar fuese diferente y recibiera algo de tipo Any, por más de que en todo el código nunca se llame al metodo saludar con algo que no entienda el método nombre, rompe igual porque en el chequeo estático se fija en el código, y cualquier cosa que le pase no entiende nombre.
!! Puedo tener polimorfismo sólo entre dos cosas que yo establecí explícitamente que comparten un tipo !!
f (_,_,a) = a
equivalente a
val x = 5
val y = 0
x/y // Los numeros entienden la división, por eso no sería un error de tipos en realidad
val list = List()
list.head // Las listas también entienden head, el problema es que está vacía
Acá aparece la operación parcial, que encapsula a un error de tipos, cuando puramente no lo son.
Si a un parámetro le pongo val o var, lo expone como si fuera público. Puedo recrear un constructor haciendo algo del estilo class A(variableA = valor) pero en realidad no es un constructor en sí mismo.
Necesitamos que un parámetro esté expuesto, si tenemos que accederlo después, por ejemplo
class Guerrero(potencialOfensivo = 10)
def atacarA (unGuerrero : Guerrero) = {
.. .... unGuerrero.potencialOfensivo // si no estaba expuesto en la clase, no podía hacerlo.
}
En este caso, era trivial poner ese =, de lo contrario me va a devolver Unit (void).
No es necesario poner las llaves si tengo una sola expresión adentro.
En cambio, si yo quiero hacer algo del estilo
atila.atacarA(unaMuralla)
No funciona, aunque podría ser perfectamente válido, porque entiende los mismos mensajes. Pero yo explicité el tipo de unGuerrero, y debe ser Guerrero.
En cambio, si en vez de definir el tipo de forma nominal como lo teníamos, y lo hacemos estructural:
type Atacable = {
def potencialDefensivo : Int // metodo abstracto
def perderEnergia(a:Int) : Unit // metodo abstracto
}
def atacarA(unAtacable : Atacable) = {
....
}
Lo que hicimos en este caso, fue decir que atacarA recibe a cualquier cosa que entienda esos dos métodos con esa firma. Ahora, puede aceptar tanto Muralla como Guerrero.
Cambiemos la forma de hacer esto:
abstract class Defensor = {
def potencialDefensivo : Int // metodo abstracto
def perderEnergia(a:Int) : Unit // metodo abstracto
var energia = 100
}
class Muralla(altura: Int) extends Defensor {
def potencialDefensivo = altura * 10
// val energia = 1000 no puedo hacer esto porque me falta un **override**
// override val energia = 1000 tampoco, porque energia tiene solo un getter y no un setter,
//y en la clase abstracta tenia un var, que tiene getter y setter.
// var energia = 1000 tampoco, porque estaría pisando la variable energia con otra variable energia.
energia = 1000
}
Al extender una clase, tengo que pasarle tambien los parametros a la clase de la cual está extendiendo. La linearización va de derecha a izquierda, teniendo menos prioridad la superclase siempre porque esa clase podria tener superclases, muchos mixins.
extends Klass with Mixin1 with Mixin2 with Mixin3
En este caso, tiene más prioridad el Mixin3 y menor prioridad su superclase Klass.
def leerDeLaBase(q: String): Any = {
//....
new Guerrero(3,13)
}
// no podemos hacer val atila = leerDeLaBase("asdasd"), sino:
val atila = leerDeLaBase("asdasd").asInstanceOf[Guerrero]
Aunque lo que estamos devolviendo sabemos que es un Guerrero, como no le explicitamos que era uno, sino que era un Any esto no compila. Hay que castearlo. Ahora puedo tratar al guerrero como tal. En cambio, si:
def leerDeLaBase(q: String): Any = {
//....
new Guerrero(3,13)
"asdasd"
}
// lo de abajo rompe
val atila = leerDeLaBase("asdasd").asInstanceOf[Guerrero]
Y está bien que rompa. Porque no hay ningún lugar en el cual compartan un tipo o se pueda convertir un String a un Guerrero.