Sobrecarga de operadores y otras monerías de Groovy

Recientemente he estado empezando a integrar código Groovy a aplicaciones que tengo hechas en Java. Esto implica que al final compilo las clases Groovy, y la aplicación Java sigo ejecutándola con java, pero ahora tengo que agregar un par de bibliotecas (hasta la fecha, con Groovy 1.7.10, solamente tengo que agregar groovy-1.7.10.jar y asm-3.2.jar, no he tenido que agregar otras, y prefiero meter esas dos que groovy-all-1.7.10.jar porque este último trae varias cosas que podrían causarme conflictos en aplicaciones con muchas dependencias).

También he visto de qué manera puedo usar mejor mis clases Java desde Groovy. Y he descubierto que hay algunas monerías que se pueden implementar en Java, para simplificar el uso de algunas clases desde Groovy.

Por ejemplo, supongamos que tenemos una clase que entre otras cosas, tiene una lista la cual puede ser modificable desde fuera. Aquí pongo un ejemplo, de una clase Java muy simple (realmente es como un envoltorio de una lista, pero imagínense que además hace otras cosas):

import java.util.*;

//Esta clase está en Java
public class SoporteGroovy {

  private ArrayList<Object> lista = new ArrayList<Object>();

  //Este metodo es para obtener un elemento de la lista
  public Object getAt(int idx) {
    return lista.get(idx);
  }
  //Este metodo es para modificar la lista
  public void putAt(int idx, Object value) {
    lista.add(idx, value);
  }
  //Este metodo es también para modificar la lista
  public void leftShift(Object value) {
    lista.add(value);
  }

  //Esto es para la sobrecarga de operadores
  public SoporteGroovy plus(Object other) {
    if (other != null) lista.add(other);
    return this;
  }

  public String toString() {
    return String.format("Ejemplo soporte groovy con elementos %s", lista);
  }

}

Los métodos getAt y putAt permiten hacer algo así en Groovy:

SoporteGroovy test1 = new SoporteGroovy()
test1[0] = "cero"
println test1[0] //Esto imprime "cero"

La asignación realmente invoca a putAt y la siguiente línea invoca a getAt. El otro método sirve en vez del típico add:

SoporteGroovy test2 = new SoporteGroovy()
test2 << "dos"
test2 << "tres"
println test2 //Esto imprime "Ejemplo soporte groovy con elementos [dos, tres]"

Es cuestión de gustos y estética si les gusta esa sintaxis (tomada de C++) o no.

Ahora el método plus, ese lo podemos usar así desde Groovy:

test2 += "cuatro"

Con eso ya tenemos tres maneras de agregar objetos: llamando el método putAt, usando el operador <<, o usando el operador +=.

Todavía podemos hacer más monerías... si hacemos que nuestra clase implemente Iterable, podremos usar el famoso each desde Groovy. Así que modificamos y agregamos un método a nuestra clase Java:

public class SoporteGroovy implements Iterable<Object> {

  public Iterator<Object> iterator() {
    return lista.iterator();
  }
}

Y con eso ahora podemos hacer algo como esto:

test1.each { println "test1 tiene $it" }
test2.each { println "test2 tiene $it" }

Esto es porque Groovy permite que llamemos el each en cualquier objeto; si la clase implementa iterator() entonces se itera sobre ese iterador.

Y de hecho ahora que la clase es Iterable, podemos también implementar una variante de plus de manera que se sumen dos instancias de nuestra clase:

  //Agregamos esto a la clase Java
  public SoporteGroovy plus(SoporteGroovy other) {
    if (other == null) return this;
    SoporteGroovy nuevo = new SoporteGroovy();
    for (Object item : lista) {
      nuevo.leftShift(item);
    }
    //Esto ya es posible porque la clase es Iterable
    for (Object item : other) {
      nuevo.leftShift(item);
    }
    return nuevo;
  }

Y una vez que tengamos ese método, podremos hacer esto en Groovy:

SoporteGroovy test3 = test1 + test2
println test3 //Nos debe imprimir que el objeto tiene los 5 elementos

Integración

Hoy en día, aún cuando implementamos clases en Java, hay que tomar en cuenta que existen otros lenguajes para la JVM. En el caso de Groovy, podemos seguir trabajando en Java pero el agregar algunos métodos como los que describí aquí, facilitan el uso de nuestras clases Java desde el lenguaje Groovy. Como autor de un par de proyectos de software libre en Java, esto me ha ayudado puesto que me permite simplificar el código que escribo en Groovy cuando utilizo dichos proyectos, aunque hayan sido hechos en Java, y agregar esos métodos a mis clases fue una tarea bastante simple.

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.

Me agrada mucho, veo que la

Me agrada mucho, veo que la onda podria ser que se genere la estructura en java y la implementacion en Groovy para hacer un codigo todavia mas efectivo

Imagen de Abaddon

Que buenos tips

Que buenos tips sobre como usar java con groovy.

Yo he usado groovy últimamente básicamente para hacer algunos scripts de soporte, pero me ha surgido la curiosidad de usar groovy para probar el código java y estos tips son muy utiles.

Esta super. Hay una lista de

Esta super. Hay una lista de todos los métodos que tienen estas conversiones? Por lo que veo en Groovy esta es una lista fija y predefinida así se pueden usar los métodos que en Java son ilegales.

A mi se me ocurre que hubiera una anotación ( por ejemplo @Alias ) con la cual pudieras decirle a tu clase en un lenguaje, que método es ese en otro lenguaje. Aún estoy por investigar sobre eso.

Lo del each, me surge una duda. Entonces, ¿el .each no es en realidad un método del objeto, sino más bien como un operador en forma de método? Eso está raro ¿ Que hace cuando le mandas .each a algo que no es in iterador ?

Imagen de domix

Si es un método,

Si es un método, revisa:
http://groovy.codehaus.org/groovy-jdk/java/lang/Object.html#each(groovy.lang.Closure)
http://groovy.codehaus.org/groovy-jdk/java/util/Map.html#each(groovy.lang.Closure)

No mandas un iterador al each, mandas un closure, y el closure recibe el elemento de la colección.

Por lo que veo, Groovy trata

Por lo que veo, Groovy trata de descomponer el objeto en partes que el dice que lo componen

por ejemplo podria lapractica decirnos que en Groovy el String es un arreglo de char y cuando le das el each te devuelve su elemento que cmpone el String y claro, no es directamente Iterable en java

Yo pienso que cuando no aplica e each devuelve el mismo objeto... segun mis pruebas:

aqui si que te hace la separacion:

def cadena = "Esta es una cadena"
cadena.each {c -> println c}

aqui no jala el each (porque nadamas es una posicion)

def cadena = 123
cadena.each {c -> println c}

bueno mas bien si jala pero no tiee sentido usarlo

Imagen de ezamudio

Metaclass

Recuerda que en Groovy tienes la Metaclass con la que puedes agregar métodos en tiempo de ejecución a un objeto o a una clase. Para responder tu primera pregunta, sí hay una lista de los métodos que Groovy agrega a la clase Object. Eso mismo contesta la segunda pregunta: each es un método que TODOS los objetos en Groovy tienen (y algunas otras preguntas que tengan). La implementación default devuelve el resultado del método iterator() que también todos los objetos tienen. La implementación default de iterator() no es muy interesante, devuelve un iterator de una colección que contiene al receptor de la invocación.

En tus clases donde venga al caso, puedes implementar iterator() para devolver algo que venga al caso. O puedes directamente sobreescribir each().

Lo del @Alias yo no creo que fuera muy buena idea porque porque podría crear bastante confusión. Mejor investigas si hay un nombre especial de método en el otro lenguaje y si ya tienes algo así implementado simplemente llamas a tu método real dentro del método con el nombre especial para el otro lenguaje.

Imagen de ezamudio

Each

Algunas implementaciones de each:

class Persona {
  String nombre
  String apellido
  Date fechaNacimiento
}

Persona p = new Persona(nombre:'Enrique', apellido:'Zamudio', fechaNacimiento:new Date())
Map m = [k1:'v1', k2:'v2', k3:'v3']

p.each { println it }
m.each { println it }
'hola'.each { println it }
"hola".each { println it }

$ groovy Each.groovy 
Persona@620a3d3b
k1=v1
k2=v2
k3=v3
h
o
l
a
h
o
l
a

Lo de hola primero es un String normal y luego un GString. Tal vez no parezca muy útil, pero si recuerdan por ahí alguien que pidió una tarea de contar caracteres, en Groovy sería simplemente:

Map m=[:]
"enrique zamudio lopez (o cualquier otra cadena que se te ocurra)".each { m[it] = m[it]? m[it]+1 : 1 }
println m

Me refiero a una lista de

Me refiero a una lista de "conversiones" entre Groovy y Java, como: "plus" mapea a "+".

Entonces, Groovy tiene un metaclass que puede aumentar el comportamiento de la contraparte en Java. Ese java.lang.Object que lista domix es un meta class ? Interesante.

Imagen de domix

Un metaclass es solo un

Un metaclass es solo un registro que tiene cada clase en Groovy _solamente_. Ahi se guardan los métodos que se inyectan via el GDK (como el método each y otros mas) y los que un desarrollador agrega.

El uso y acceso de los metaclass esta solo desde clases Groovy, desde código Java no puedes dada la naturaleza del lenguaje Java. A menos que uses o accedas al metaclass con código Java (muy tedioso de hacer, no vale la pena)

Imagen de ezamudio

no sé

Honestamente, no sé si ese Object es un metaClass o cómo lo implementan, seguro él nos podrá aclarar la duda, tiene bastante más experiencia con Groovy. Yo sospecho que por performance, lo que hacen al levantar una app con Groovy, es que le agregan métodos a varias clases de Java, usando asm. Probablemente eso mismo usan para implementar los mecanismos de metaclases, pero la cosa es que a Object le meten esos métodos que viste, igual que a varias colecciones y otras clases básicas como String le agregan varios métodos.

Imagen de domix

Hice un diagrama de como

Hice un diagrama de como funciona mas o menos la metaprogramación en Groovy para un curso, aqui la pueden ver:
http://www.flickr.com/photos/domix/4122968104/in/set-72157622724735681/

Imagen de bferro

Syntactic sugar causes cancer

Syntactic sugar causes cancer of the semicolon. Alan Perlis, Epigrams in Programming http://www.cs.yale.edu/quotes.html

Imagen de ezamudio

Clarke

For every expert, there is an equal and opposite expert - Arthur C. Clarke

Ladra que muerde no perra.

Ladra que muerde no perra. El Chapulín colorado

Je je je..

¿Ahh no verdad?

Ya en serio, lo que menciona bferro es interesante. Durante muchos años este ha sido el pensamiento de los lenguajes de programación principales; hubo poca azúcar sintáctica. Incluso Java mismo se había negado ( hasta Java 1.5 ) a utilizar este tipo de azucar. La razón era básicamente que se introducían instrucciones intermedias innecesarias que pegaban ( mínimamente, pero pegaban ) en el desempeño.

Sin embargo, el hardware mejoró muchísimo y muchas prácticas que eran casí tabú empezaron a tener más y más aceptación, al grado que los lenguajes de programación interpretados ahora no son descartados de inmediato por esta sola razón ( por ser interpretados ). Lenguajes como Ruby, Python, Perl y el mismísimo Java que tuvieron muchísimo rechazo inicialmente hoy son considerados como lenguajes de primera línea. Hay que recordar que Java es un lenguajes interpretado ( el bytecode se interpreta por la JVM, que en una JVM moderna compila a código de máquina, pero lo hace en línea, lo cual no lo hace menos interpretado, solo habla que el interprete es muuuuy bueno en su trabajo ) y que el bytecode mismo es syntactic sugar para ensamblador.

Lo importante es hacer una buena implementación de estas abstracciones sintácticas para que no se note el truco. Es como en la magia, mal hecho todos dicen "bah", bien ejecutada es sorprendente. Scala es syntactic sugar de Java ( y vaya que hay muchos triquitos por ahí ) Java de Bytecode, el Bytecode de C, C de ensamblador y el ensamblador de ... este... bueno lo que diablos haya allá abajo ( el hardware no es lo mío )

Re: Ladra que muerde no perra.

FTW!!!!

Imagen de bferro

IMHO

La sobrecarga de operadores, es un asunto puramente sintáctico, y como bien dice ezamudio, es una cuestión de gusto usarla o no .
Groovy la tiene, Scala también (aunque más clara, ya que admite caracteres no alfanuméricos en los nombres), C++ la tuvo desde sus inicios, y de manera muy limitada Java la tiene, por ejemplo con el operador + para indicar una concatenación.
De hecho casi todos los lenguajes de programación sobrecargan el operador = para indicar una inicialización en el momento de definir una variable y también para asignar un nuevo valor a una variable existente, que son dos operaciones diferentes. Pascal por ejemplo, distingue esas operaciones con = y:=

En lo personal, trato de usar ese mecanismo sin para nada alterar la semántica de lo que escribo. Con esto quiero decir, que si por ejemplo, programo una clase para los números complejos, tiene sentido que sobrecargue los operadores aritméticos porque en el dominio del problema, el lenguaje que uso es ese: voy a sumar el complejo A con el complejo B, o voy a restarlos, etc.

Si la operación que quiero programar, como es el caso de añadir un elemento a una lista, prefiero no usar el operador + con ese propósito, pues normalmente no "sumo" un elemento a una lista. Al explicar el programa normalmente digo que voy a añadir un elemento a la lista, y en ese caso prefiero de manera explícita escribirlo: lista.add(nuevo_elemento).

Por supuesto que pienso que no es saludable resistirse a usar la sobrecarga de operadores. Es un recurso adicional que brinda el lenguaje.

Ah te referías solamente al

Ah te referías solamente al syntactic sugar en los operadores? Yo creí que en todo el lenguaje en sí. :)

En lo particular me gusta que no haya sobre carga de operadores en Java ( o al menos no definidos por el usuario). ¿Se imaginan un lenguaje de programación sin operadores? Bueno quizá solo el de asignación. Estaría raro no? ;) ( je je je je )

Imagen de bferro

Me refiero en general a las "facilidades sintácticas"

En general me refiero a las facilidades sintácticas, como es el caso de la sobrecarga de operadores, notaciones infijas, prefijas postfijas, etc.

En lenguajes como Scala, que combinas lo imperativo con lo funcional, puedes aun más oscurecer la intención de lo que quieres si abusas mucho de esa "dulzura" sintáctica.

Pero, otra vez, voto a favor de esas facilidades.

Imagen de bferro

Guardian.co.uk Switching from Java to Scala