Tipos dinámicos

Hoy traemos un poco más de magia negra. Leyendo el otro día un poco sobre los procesos de mejora de Scala (SIPs) me encontré algo que realmente me llamó la atención: tipos dinámicos.

¿Qué son?

Los tipos dinámicos permiten definir el comportamiento de un objeto cuando se invoca un método o se intenta acceder a un miembro que no existe. Por ejemplo:

class MyClass {
  def someMethod(): String = "hi"
}
val myInstance = new MyClass
myInstance.nonExistentMethod

dará un fallo en tiempo de compilación. Con los tipos dinámicos podemos decir que en caso de que se invoque a un método, se llame como se llame, devuelva cierto valor.

¿Cómo funcionan?

Para poder utilizar los tipos dinámicos, es preciso importar import scala.language.dynamics y extender del trait Dynamic:

class MyClass extends Dynamic {
  //...
}

La clase (o trait) que definamos puede tener todos los atributos y métodos que queramos, pero además podemos implementar los siguientes métodos:

applyDynamic

este método se encarga de gestionar las llamadas a métodos de la instancia.
Si queremos que nuestra instancia sepa como invocar el método ‘myMethod’ con el argumento “hi”, podemos hacerlo como sigue:

class MyClass extends Dynamic {
  def applyDynamic(methodName: String)(args: Any*) = {
    val beginning = 
      args.headOption.getOrElse("hello")
    if (methodName=="myMethod") 
      println(beginning + " you")
    else println("Dunno...")
  }
}

Si ahora creamos una instancia e invocamos otro método inventado, veremos que imprimirá por pantalla que no sabe a que se refiere:

val myInstance = new MyClass
myInstance.anotherMethod()//Dunno...

Y si invocamos al método myMethod que tenemos contemplado:

myInstance.myMethod("hi")//hi you
myInstance.myMethod(2)//2 you
myInstance.myMethod()//hello you

applyDynamicNamed

Funciona exactamente igual que el applyDynamic solo que permite obtener por nombre los argumentos con los que se invoca al método:

class MyClass extends Dynamic {
  def applyDynamicNamed(name: String)(args: (String, Any)*) = {
    val (argName,argValue) = 
      args.headOption.getOrElse(("unknown","hello"))
    if (name=="myMethod") 
      println(s"$argName : $argValue - you")
    else println("Dunno...")
  }
}

Si se invoca al método sin indicar el nombre de algún parámetro, este será cadena vacía, pero para que se invoque a applyDynamicNamed al menos uno de los argumentos debe tener nombre:

myInstance.myMethod(test=2)//test : 2 - you
myInstance.myMethod(true,test=2)// : true - you
myInstance.myMethod(true)
/*
error: value applyDynamic is not a member of MyClass
error after rewriting to myInstance.<applyDynamic: error>("myMethod")
possible cause: maybe a wrong Dynamic method signature?
*/

selectDynamic

Sería el equivalente a un getter dinámico. Es decir:

class MyClass extends Dynamic {
  def selectDynamic(name: String) = 
    s"My $name equals this string"
}
val myInstance = new MyClass
myInstance.someAttribute//My someAttribute equals this string

updateDynamic

Y con este método definimos el comportamiento de actualización de miembros que no están definidos de manera estática:

class MyClass extends Dynamic {
  def updateDynamic(name: String)(value: Any) {
    println(s"you updated your attribute $name with $value value")
  }
}
val myInstance = new MyClass
myInstance.myAttribute = 2
//you updated your attribute myAttribute with 2 value

NB: En la REPL no funciona de manera muy católica (lo interpreta como un getter). Si lo encapsulais dentro de un App debería funcionar.

Easy peasy.

¿Por qué en un lenguaje de tipado estático?

haters

La motivación oficial de la propuesta de mejora es:

Static types are great, but sometimes we would like to do without them. This could give better support for flexible DSLs that do not require defining the members of a type. It could also provide simpler interfaces with dynamic languages. Scala already provides a number of ways to escape from the straightjacket of the type system: The universal type Any, type tests and type casts, as well as pattern matching. However, one thing is missing: When selecting a member qual.sel, sel must be defined in qual, or be addable with an implicit conversion. This means that we can select members or call methods only if it is statically known that the receiver type supports that member.

Si bien es cierto que los más puristas pueden verlo como una aberración hacia la naturaleza de tipado estático del lenguaje, para los más malabaristas puede dar margen de maniobra a la hora de jugarse el tipo definiendo DSLs sin comprobaciones en tiempo de compilación. Juzguen ustedes.

¡Agur de limón!

Anuncios

One thought on “Tipos dinámicos

  1. Muy útil, es mucho más limpio que en PHP ya que tienes que extender de Dynamic.

    Yo utilicé este tipo de cosas para construir un ORM y poder implementar los típicos “findByXXXXXX”. Otro caso de uso fue para hacer una especie de Bridge entre una clase enormisima legacy y la versión moderna, mientras reimplementábamos, ibamos haciendo un fallback a la clase vieja con este tipo de cosas….

    Por si a alguien le ayuda a ver casos de uso más mundanos.

    Gracias por el aporte! 😀

    Le gusta a 1 persona

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