Integración entre lenguajes JVM

En 2007 hice una biblioteca de software llamada j8583 y desde entonces la utilizo, actualizándola de vez en cuando con algunas optimizaciones o características nuevas.

Una de estas características nuevas se la agregué cuando comencé a utilizar j8583 en Groovy. Resulta que Groovy tiene azúcar sintáctica para realizar algunas operaciones, por ejemplo:

arreglo.add(elemento); //Java
arreglo << elemento //Groovy

outputStream.write(datos); //Java
outputStream << datos //Groovy

arreglo.get(5); //Java
arreglo[5] //Groovy

//Java
mapa.get("llave");
mapa.put("llave", valor);

//Groovy
mapa['llave']
mapa["llave"] = valor

Esto me llamó la atención porque Groovy no permite nomenclatura "rara" para los métodos. Así que investigando un poco me encontré con que estos operadores al momento de compilar se convierten en invocaciones de métodos:

<<	leftShift(Object)
>>	rightShift(Object)
+	plus(Object)
[]	getAt(int), getAt(Object)
[]=	putAt(int), putAt(Object)

Y así hay otros para poder usar operadores de división, resta, multiplicación, etc. Así es como Groovy implementa una especie de sobrecarga de operadores muy sencilla. Y lo bueno de esta sencillez es que se pueden implementar métodos en código Java con estos nombres, y ser invocados en Groovy como si fueran los operadores. De este modo pude simplificar el uso de mi biblioteca, particularmente de la clase IsoMessage, la cual ya tenía métodos de conveniencia para manipulación de sus campos.

En resumen, los mensajes ISO8583 tienen hasta 128 campos y se manejan por índice: el campo 4, 7, 11, etc. Un campo tiene un tipo de dato: puede ser alfanumérico o numérico de tamaño fijo, o un monto de tamaño fijo también, una fecha (hay como 4 variantes de fecha, con distintas longitudes) o datos alfanuméricos de longitud variable. j8583 cuenta con una clase que representa un campo: IsoValue, y un enumerador que representa los tipos de datos: IsoType. Entonces para ponerle un campo a IsoMessage es algo así:

IsoMessage msg;
IsoValue campoFijo = new IsoValue(IsoType.ALPHA, "Hola", 4);
IsoValue campoVar = new IsoValue(IsoType.LLVAR, "Hola");
msg.setField(20, campoFijo);
msg.setField(21, campoVar);

//y para acceder a estos campos
valor = msg.hasField(20) ? msg.getField(20).getValue() : null

Lo anterior puede ser bastante engorroso, por lo que decidí ponerle unos métodos de conveniencia a IsoMessage para directamente guardar valores sin tener que crear los IsoValues:

msg.setValue(20, "Hola", IsoType.ALPHA, 4);
msg.setValue(21, "Hola", IsoType.LLVAR, 0); //cuando le paso datos de longitud variable, ignora el último parámetro que es la longitud
valor = msg.getObjectValue(20); //si no tiene el campo me devuelve null

Cuando uso j8583, generalmente hay varias líneas con código similar, metiendo valores en los campos. Usando Groovy me di cuenta que podía agregar un poco de código a IsoMessage para lograr algo así:

msg[20] = new IsoValue(IsoType.ALPHA, "Hola", 4)
msg[21] = new IsoValue(IsoType.LLVAR, "Hola")
valor = msg[21]?.value

Particularmente la brevedad para obtener un valor me gustó; ya no tengo que usar el método de conveniencia IsoMessage.getObjectValue(int) sino que puedo aprovechar el operador ?. de Groovy (además de escribir value para invocar getValue()). Pero sigo sintiendo que los setters pueden ser más breves. Así que seguí pensando cómo podría ser mejor, y me imaginé esto:

msg[20] = IsoType.ALPHA("Hola", 4)
msg[21] = IsoType.LLVAR("Hola")

La sintaxis es breve y creo que queda claro que quiero asignar un valor de cierto tipo y longitud en un campo del mensaje. Pero, ¿es posible hacer que funcione algo así? No sólo es posible, es sencillo. Lo que tuve que hacer es implementar dos variantes del método call en el enumerador IsoType: una que recibe Object y una que recibe Object y un int (para la longitud). Internamente estos métodos crean un IsoValue:

public IsoValue call(Object val, int len) {
  return new IsoValue(this, val, len);
}

public IsoValue call(Object val) {
  return new IsoValue(this, val);
}

Cuando Groovy encuentra sintaxis como la que me imaginé, que es como invocar un enum como si fuera un método, lo que hace es buscar en el receptor un método call que tenga los parámetros indicados y eso es lo que invoca. Groovy tiene esto para facilitar la sintaxis de invocación de los closures, porque pues a fin de cuentas un closure en Groovy es un objeto, pero se ve más bonito si lo podemos invocar como un método:

def imprime = { println(it?:"No mandaste nada!") }
imprime(null)
imprime("algo")
//Esto también se vale, pero se más bonito sin el "call"
imprime.call(null)
imprime.call("algo")

Así que pues utilicé esta característica de Groovy a mi favor. Ahora, todo esto no le da realmente ninguna funcionalidad nueva a j8583, pero con un poquito de código que le agregué a mi biblioteca en Java, se puede usar en Groovy siguiendo el estilo de ese lenguaje. Y creo que se pueden hacer cosas similares en otras bibliotecas o frameworks aunque sean en Java puro, pues finalmente van a ser métodos que tal vez no vengan mucho al caso si se invocan desde Java pero que en otros lenguajes serán más útiles.

Y ahora... en Scala

Recientemente he incursionado en Scala, y en mis exploraciones más recientes decidí ver si podía hacer con j8583 algo similar a lo que le hice para Groovy.

En Scala no se usan corchetes para los índices de arreglos, sino paréntesis (los corchetes son para Generics), de modo que en Scala mi sintaxis tendría que quedar así:

msg(20) = IsoType.ALPHA("Hola", 4)
msg(21) = IsoType.LLVAR("Hola")
valor = if msg.hasField(21) msg(21).getValue else null

Esa última línea creo que la puedo volver más scalesca usando Option en vez de la función if pero por el momento enfoquémonos en las otras dos líneas: tengo que ponerle algo a IsoMessage para que pueda devolver y modificar campos como si fuera un arreglo, y tengo que ponerle algo al IsoType para que pueda crear un IsoValue con la misma sintaxis.

Después de unas horas de leer documentación, experimenté con algo que vi en la clase Array de Scala: los métodos apply y update:

public void update(int i, IsoValue<?> v) {
  setField(i, v);
}
public IsoValue<?> apply(int i) {
  return getField(i);
}

Con esos métodos en IsoMessage ya puedo hacer esto:

msg(20) = unIsoValue
valor = msg(20).getValue

Y de hecho con apply también puedo lograr lo de IsoType:

public IsoValue apply(Object val, int len) {
  return new IsoValue(this, val, len);
}
public IsoValue apply(Object val) {
  return new IsoValue(this, val);
}

Y ahora, j8583 sigue teniendo exactamente la misma funcionalidad de antes, pero al menos en lo que respecta a sintaxis, se integra mejor con Groovy y Scala.

Y tu código, ¿Qué tal se puede usar desde otros lenguajes?

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.

Buen enfoque

Me parece buenísimo el hecho de programar no solo para un lenguaje (que podría ser java en este caso), finalmente si usa la JVM es buena la idea de hacerlo que pueda encajar en los demás lenguajes. Tener este tipo de practicas ayudaría muchísimo a unificar el uso de las bibliotecas/frameworks/etc y no decir "hay un framework para Groovy/Java/Scala" sino mejor decir "hay un framework para la JVM" sin importar el lenguaje que se utilice.
Indudablemente requiere el "triple" de esfuerzo conocimiento que dependerá mucho de la complejidad del código que se escriba.

  La cosa es también pensar que pueda usarse de Groovy hacia Scala y de Scala a XXXXXX. A mi punto de vista puede resultar un tanto descabellada la idea hacer código utilizable con las bondades que cada lenguaje traiga consigo porque lo que si es un hecho es que todos dicen que puedes usar código Java de manera natural pero no dicen que puedas usar ese código compilado con las bondades del lenguaje alternativo

  Mi conclusión es que es bueno pensar en programar para que el código creado sea adaptable con otros lenguajes. Me pregunto si en algún momento exista un tipo de desarrollo que involucre múltiples lenguajes en un solo sistema (sobre la misma plataforma)

Imagen de ezamudio

eso ya existe

Ya existen sistemas que están hechos en parte en Java y en parte Groovy por ejemplo (Grails es un ejemplo, hay partes que están hechas en Java). Y una de las grandes ventajas de los lenguajes de JVM es que puedes integrar en un mismo sistema partes hechas en Java con partes hechas en Groovy por ejemplo, o partes hechas en Java con partes hechas en Scala.

Domix está involucrado en un plugin para meter código Scala en Grails, de modo que ya puedes tener ahí las 3 cosas: Groovy, Scala, Java.

Imagen de tuxin39

j8583

Muy bueno

De echo yo estoy probando la versión anterior para un clientesillo(en proceso de desarrollo y en scala) y estos metodos lo hacen más natural.

Voy a realizar la actualización, para poderlos usar, saludos y gracias.

Imagen de ezamudio

hoy subí 1.5.3

Hoy ya subí la 1.5.3 con la integración para lo de Scala y además ya arreglé las definiciones de algunos métodos de IsoMessage para aprovechar bien el hecho de IsoValue tenga generics. Ya está disponible en Maven Central y en SF.net

La integración con Groovy está desde la 1.5.2.