Tipos estructurales

Se denomina en programación Ducktyping a la definición de tipos que viene dada por los atributos y métodos que lo componen, y no por la herencia. Por ejemplo, en Scala podemos definir el siguiente tipo estructural:

type Animal = {
  val legs: Int
  def noise(): String
}

def animalDescription(a: Animal): String =
  s"My animal has ${a.legs} legs and says ${a.noise()}"

En este caso, podemos afirmar que toda clase que posea un atributo llamado legs de tipo entero y un método noise que devuelve un String, se puede denominar Animal.

Ahora bien, ¿qué diferencia hay entre esto y la herencia clásica?

trait Animal {
  val legs: Int
  def noise(): String
}
class Dog extends Animal{
  val legs = 4
  def noise() = "woof"
}

A priori puede parecer que ninguna. De hecho hay que tener en cuenta que en Scala, cuando utilizamos tipos estructurales sufrimos cierto overhead en compile-time dado que es preciso inferir si el tipo que le pasamos cumple con las restricciones que impone el tipo estructural.

¿Entonces qué demonios aporta el tipo estructural?

Muy sencillo: hasta ahora veníamos suponiendo que la implementación que estamos haciendo es 100% home-made. Pero, ¿qué ocurre si estamos usando un framework o librería de terceros y resulta que los autores son tan majos de declarar las clases finales o de definir sealed traits (o incluso privados a cierto ámbito)?

tGWoYYo

Pues ocurre que, aparte de estar en todo su derecho, nosotros no podríamos hacer nada del estilo:

package some.private.library {

  sealed trait TraitIWantToExtend {
    val member1: Int
    def method2(): Boolean
  }

  private[some.private] class ClassIMaybeWantToMock extends TraitIWantToExtend {
    val member1 = 2
    def method2() = true
  }

}

package my.package {

  class MyClass extends TraitIWantToExtend {// nuke-explosion in scalac!
    val member1 = 3
    def method2() = false
  }

  def methodToRun[T<:TraitIWantToExtend](obj: T) {
    println(obj.member1)
    println(obj.method2())
  }

}

Sin embargo, con lo aprendido anteriormente, podríamos extraer del siguiente modo la esencia de los rasgos comunes que nos interesan entre nuestra clase y la clase que proviene de la librería de terceros:

package my.package {

  type CommonFeatures = {
    val member1: Int
    def method2(): Boolean
  }

  class MyClass {// no nuke explosion now ^^
    val member1 = 3
    def method2() = false
  }

  def methodToRun[T<:CommonFeatures](obj: T) {
    println(obj.member1)
    println(obj.method2())
  }

}

Así que, como casi todo, no recomendamos un abuso de esta característica.
No obstante, en circunstancias similares a las expuestas, puede resultar muy útil.

¡Yogur,amigos!:)

Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s