style="display:inline-block;width:728px;height:90px"
data-ad-client="ca-pub-5164839828746352"
data-ad-slot="7563230308">

Erasure vs. reification ( preguntas sobre un blog post de Cedric Beust )

Resumen

( para el que como yo, odie las paredes de texto )

¿Podrá convivir el código con generics en Java y código con reified generics de los NJVML's?

Versión larga

( para el que como yo, a veces quiere leer algo largo porque parece interesante )

Le dejo una entrada de blog de Cedric Beust que habla sobre los generics en Java y los retos que presentan dos nuevos lenguajes ( bueno tres lenguajes ) para agregar una cosa que se llama reification ( reified generics ), la palabra reify significa según google, hacer algo abstracto más concreto. C# y C++ tienen reifed generics

http://beust.com/weblog/2011/07/29/erasure-vs-reification/ ( inglés )

Generics

Repaso de los generics.

Sin generics:

List  personas = new ArrayList();
personas.add( new Persona("Oscar", 0x20 ) );
....
Persona oscar = ( Persona ) personas.get( 0 ); // se necesita el cast
personas.add( new Integer( 0 ) ); // Ok, no se queja..

Con generics:

List<Persona> personas = new ArrayList<>(); // Se especifica de la lista es de Persona ( since 1.7 ya se puede escribir usar el "diamante" )
personas.add( new Persona("Oscar", 0x20 ) );
..
Persona oscar = personas.get( 0 ); // no se necesita el cast, se sabe que es una lista de Persona
personas.add( new Integer( 0 ) ) ;// compile error, no se puede agregar un Integer a una lista de Personas

Hay varias opciones para usar los generics y varias combinaciones, por ejemplo se puede incluso poner dos interfaces y varias cosas más que no vienen ahorita al caso.

Erasure

Pues bueeno en Java, estos generics son "rasurados" y la información en tiempo de ejecución sobre el tipo de dato que se almacena no está disponible. Esto para el que usa el código generificado no tiene mayor problema, pero para el que quiere que su código sea generico presenta el problema de que no puede:

  • Hacer sobre carga de métodos
  • Introspección
  • Instanciación

Ver ejemplos en el post de Cedric

Y probablemente no puede hacer algunas otras cosas más, porque simplemente el compilador los vuela del código y ya no está disponible. La razón para hacer esto es que se use el mismo código siempre en vez de hacer como en C++ una copia del código por cada tipo de dato usado.

Bueno.

¿Y eso que?

Bueno, el post dice que hay pocas quejas sobre la falta de generics reifed, en el sentido que hemos podido vivir sin ellos. Yo creo que la razón es que aprendemos rápido que no se puede y encontramos otra manera ( muchas veces es agregar la anotación para que no valide que no estamos usando generics y/o aguantarse el warning )

Pues bien, Gosu un nuevo JVML ( Lenguaje de la Máquina Virtual de Java ) soporta genéricos "reconstituidos" y esas tres cosas que listé arriba sí se pueden hacer. Klotlin que fue recién anunciado y que estará disponible a finales de año, también va a tener reified generics y en los comentarios de G+ Gavin King menciona que Ceylon también los tendrá ( se parece a alguien que anuncia cosas en su lenguaje cuando aparece el tema, bueeno yo personalmente le creo cuando lo dice )

No en serío ¿ y eso que?

El post y toda esta información va para preguntar

  • ¿Cómo podría ser la convivencia de código de estos NJVML's con código Java existente ?
  • ¿Saben de alguien que haya necesitado esta información en tiempo de ejcución? ( yo la necesite casi el mismo día que usé por primera vez un generic y descubrí que no se podía y no volví a insistir )
  • ¿ Como podría ser un modelo ideal de generics ?
  • ¿ Dan lata el type erasure ?
  • ¿Sería mejor tener reified generics en Java? ( aunque esta última es una pregunta ociosa porque eso no va a pasar )
  • ¿Importa un cacahuate?
  • ¿Algún otro JVML hace que esto sea más fácil ( Scala, Groovy, Clojure, etc )?

    En fin, sé que no a todos les interesan temas de detalles de diseño de lenguajes de programación y este tipo de cosas, pero pues para elevar un poco más el nivel de la discusión y seguir aprendiendo.

Comentarios

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de Nopalin

Pues la verdad a igual que tu

Pues la verdad a igual que tu la primera vez que use generics quize saber alguna forma de obtener que tipo de objeto es el que tenia la lista, pero solo era por saber si se podia obtener no por que en realidad le fuera a dar un uso contundente y masivo.

Podrias informarnos cual era tu objetivo al querer saber que tipo de dato era el que contenia el generic? Si tienes una lista con generic de tipo Persona, ya sabes que es de ese tipo, por que necesitarias un metodo que te lo dijera? Ahora, si declaras una lista de tipo Persona, ¿por qué habrías de agregar un objeto de tipo Integer?, A menos que vayas a estar formando objetos de forma dinamica a partir de cadenas que el usuario escriba (el package con la clase por supuesto) e ir agregándolos a la lista, yo veo sin mucho uso la funcionalidad que se reclama.

Tal vez todavia no me topo con alguna necesidad como para necesitar saberlo, por eso entonces te pregunto, ¿como en qué casos lo ocuparias?

Saludos

Imagen de ezamudio

contravarianza

Si los generics en Java aceptaran contravarianza, se podría hacer algo al momento de compilar para que las colecciones o clases con generics realmente sólo acepten los tipos definidos en tiempo de ejecución. Aunque esto ofrece ciertas ventajas, surgirían otro tipo de broncas en tiempo de ejecución. Una cosa por otra.

Re: Pues la verdad a igual que tu

Sólo una nota respecto de tu comentario:

[...]A menos que vayas a estar formando objetos de forma dinamica a partir de cadenas que el usuario escriba[...]

Pues, para eso yo en lugar de usar listas sin generics, mejor uso un Hash. De ahí en fuera estoy totalmente de acuerdo con tu comentario.

Imagen de bferro

Scala usa Manifest[T] para "recuperar" el tipo de los genéricos

El libro de Oderky tiene algunos ejemplos del uso de Manifest[T] para poder "concretizar" (to reify) a los genéricos. Muchos de los ejemplos que se ponen casi siempre tienen que ver con la posibilidad de crear arreglos genéricos.
El siguiente ejemplo no compila:

def evenElems[T](xs: Vector[T]): Array[T] = {
val arr = new Array[T]((xs.length + 1) / 2)
for (i <0
until xs.length by 2)
arr(i / 2) = xs(i)
arr
}

El compilador anuncia el siguiente error:
error: cannot find class manifest for element type T
val arr = new Array[T]((arr.length + 1) / 2)
en el momento de intentar crear el arreglo genérico.

No puede compilar basado en la información que se le brinda debido a que el tipo real que corresponde con el parámetro tipo T se borra en ejecución

Para poder compilarlo hay que hacerle llegar al compilador información del tipo mediante un Manifest.
El siguiente ejemplo compila:

def evenElems[T](xs: Vector[T])
(implicit m: Manifest[T]): Array[T] = {
val arr = new Array[T]((xs.length + 1) / 2)
for (i <0
until xs.length by 2)
arr(i / 2) = xs(i)
arr
}

Otro ejemplo extraido de otro post:

def foo[T](x: List[T])(implicit m: Manifest[T]) = {
     if (m <:< manifest[String])
         println("Hey, this list is full of strings")
      else
         println("Non-stringy list")
 }

scala> foo(List("one", "two"))
Hey, this list is full of strings

scala> foo(List(1, 2))
Non-stringy list

@bferro En el post de Cedric

@bferro En el post de Cedric el Manifest pero no tenía ni la más mínima idea de como se usaba. Ahora me queda mucho más claro.

Por cierto ¿como se lee el operador ( función/método/ símbolo o lo que sea ) <:<? me lo he encontrado un par de veces y no he sabido pronunciarlo.

@nopalin La verdad es que ya no me acuerdo. Cuando lo intenté por primera vez fue hace ya varios años.

Mmhh el caso más común sería crear una nueva instancia del tipo generico. Por ejemplo en un DAO/ORM casero ( no es que haya pasado antes ) se podría necesitar crear una instancia nueva cada vez que se encuentra un registro:

class GenericDAO<T> {
    public List<T> fetch( Criteria c ) {
         // connection blahabal
         // select x,y,z from t where c = ? ? ? etc. etc
         while( rs.next() ) {
              T item = new T(); //<-- no es posible
              resultado.add( item );
         }
    }
}

Esto si es posible en C# donde se tienen reified generics. Si es bueno/malo necesario o no ya es otro tema.

El ejemplo de usar dos tipos de datos diferentes ( Integer y Strings en mi ejemplo ) descubrí después que si se puede hacer con algo como : <T extends InterfazUno & InterfazDos>

Ejemplo

import java.io.Serializable;
class X {
    public <T extends Serializable & Comparable> void add( T t ) {
    }
    public void test() {
        add( new Integer( 1 ) );
        add( "x" );
    }
}

en los comentarios de G+

en los comentarios de G+ Gavin King menciona que Ceylon también los tendrá ( se parece a alguien que anuncia cosas en su lenguaje cuando aparece el tema, bueeno yo personalmente le creo cuando lo dice )

Jej. Quizas tiene razon. De hecho en este caso "reified generics" fue mencionado en estos slides del 13 de Abril. Como siempre, la gente ponen atencion en los flamewars de estupidez en lugar de fijarse en la informacion que esta disponible. Esta bien, le perdono ;-)

Se encuenta bastante informacion sobre el idioma en el Introduction to Ceylon. (Puro ingles, lamentablamente. Me tomaria mucho tiempo traducirlo y estoy muy ocupado por el compiler y por un bebe.)

Oops y por ese "alguien" me

Oops y por ese "alguien" me refería a mi mismo :P ( bueno ya , pero estoy perdonado )

Les transcribo acá lo que @GavinKing menciona sobre los reified generics en uno de los links que dejó:

http://in.relation.to/Bloggers/IntroductionToCeylonPart6

( Original )


The root cause of very many problems when working with generic types in Java is type erasure. Generic type parameters and arguments are discarded by the compiler, and simply aren't available at runtime. So the following, perfectly sensible, code fragments just wouldn't compile in Java:

// This is Ceylon
if (is List<Person> list) { ... }

if (is Element obj) { ... }


A major goal of Ceylon's type system is support for reified generics. Like Java, the Ceylon compiler performs erasure, discarding type parameters from the schema of the generic type. But unlike Java, type arguments are supposed to be reified (available at runtime). Of course, generic type arguments won't be checked for typesafety by the underlying virtual machine at runtime, but type arguments are at least available at runtime to code that wants to make use of them explicitly. So the code fragments above are supposed to compile and function as expected. You will even be able to use reflection to discover the type arguments of an instance of a generic type.

( Traducción al Español )

La causa raíz de muchos problemas cuando se trabaja con tipos genéricos en Java es el "rasurado" de tipos. Los tipos genéricos de los parámetros y los argumentos son descartados por el compilador y simplemente no están disponibles en tiempo de ejecución. Por lo que el siguiente código, perfectamente sensible simplemente no compilaría en Java:

// This is Ceylon
if (is List<Person> list) { ... }

if (is Element obj) { ... }


Un objetivo principal en el sistema de tipo de Ceylon es soportar "reified generics". Al igual que en Java, el compilador de Ceylon realiza un "rasurado" de tipo, descartando los tipos de parametros del esquema del tipo genérico. Pero a diferencia de Java, el tipo de los argumentos es se supone que serán "reified" ( disponibles en tiempo de ejecución ). Por supuesto, el tipo de los argumentos no será revisado para seguridad de tipo ( typesafety ) por la máquina virtual en tiemop de ejecución, pero el tipo de los argumentos al menos estará disponible en tiempo de ejecución para el código que desee hacer uso de él explicitamente. Entonces, el código anterior compilará y funcionará como se espera. Incluso será posible usar reflection para descubrir el tipo del argumento de una instancia de tipo genérico.

Imagen de Shadonwk

Al leer el artículo, y

Al leer el artículo, y revisar los comentarios pensé como @Nopalin, debido a que siempre he usado listas de tipo especifico,

List<Persona> personas = new ArrayList<Persona>();

Ojo diferente de:

List<Persona> personas = new ArrayList<>(); // Se especifica de la lista es de Persona ( since 1.7 ya se puede escribir usar el "diamante" )

Con lo cual sabia que tipo iba a meter a la lista, y por ende que tipo iba a obtener, por lo cual no tenía que hacer cast ni cosas por el estilo... pero

<T extends InterfazUno & InterfazDos>

Realmente no lo sabía, aquí sería importante saber qué tipo es el que obtienes.

Aunque ¿Qué ventaja traería meter dos tipos diferentes en una sola lista, en lugar de hacer dos listas y meter un tipo de elemento a cada una?

Imagen de bferro

Gracias Gavin

Gracias Gavin por participar en este foro. Que bueno que ya te animaste. Take care of the baby
Saludos, Bárbaro

style="display:inline-block;width:728px;height:90px"
data-ad-client="ca-pub-5164839828746352"
data-ad-slot="7563230308">