blog de ezamudio

Kotlin, parte 3: Métodos de extensión y sobrecarga de operadores

Kotlin permite la sobrecarga de operadores, como Scala y C++, pero de manera controlada, como Groovy. Es decir, no se pueden definir operadores de manera arbitraria como en Scala, donde se puede definir un método ~->, pero sí se pueden tener métodos que se puedan invocar con operadores como +, -, *, [] etc.

Es curioso que siendo un lenguaje con tipado estático, no se fueron por el camino "limpio" para implementar esto, que era definir interfaces para los operadores (por ejemplo, Summable, o Plus, Minus, etc), sino que lo implementaron de igual manera que en Groovy, sólo que pues en Groovy funciona porque es un lenguaje dinámico. Esto presenta dos problemas: primero, que hay que saberse muy bien cuáles son los operadores que se pueden sobreescribir, junto con los nombres de los métodos correspondientes, los cuales no siempre son obvios a la hora de estar implementado uno (Para usar / ¿es divided, quotient, div o qué?) y el otro, que es más difícil saber si una clase tiene operadores sobrecargados o no, ya que hay que revisar los métodos que implementa, en vez de simplemente revisar las interfaces que implementa, y honestamente es más fácil simplemente hacer prueba y error (a ver si funciona si le pongo un +).

Kotlin, parte 2: (not so) typesafe null y otras monerías

Una característica importante de Kotlin es que maneja seguridad en nulos. Esto es algo que varios lenguajes han estado implementando últimamente, porque ahorra muchos dolores de cabeza.

Normalmente, una variable de cualquier tipo que sea objeto, acepta null. En Kotlin no es así; para que una variable acepte null, se necesita especificar de esa forma. Esto no compila:

var x:String = "hola"
x = null

Porque x ha sido definida como de tipo String, y no acepta nulos. Para que acepte nulos, se tiene que definir así:

var x:String? = "hola"
x = null

Los tipos opcionales se pueden usar en parámetros de funciones, tipos de retorno y declaraciones locales.

Cuando se tiene un valor que puede ser null, no se puede usar de manera directa. Hay que verificar que el objeto exista; esto se puede lograr de varias formas:

var x:String? = "hola"
if (x != null) {
  //Aquí dentro, x ya se considera String
  println(x.length)
}
val largo = x?.length //largo será tipo `Int?`
//Se puede usar el operador "Elvis" con tipos opcionales
println(x ?: "no hay x")

Kotlin: El nuevo lenguaje del emperador (1 de 3)

Hace unas semanas, Google anunció con bombo y platillo que ya soportan oficialmente Kotlin en Android. Para mucha gente, esta fue la primera vez que escucharon algo acerca del lenguaje y, como era de esperarse, a partir de ese momento han salido supuestos expertos en Kotlin hasta debajo de las piedras, ofreciendo seminarios y cursos y demás.

Yo llevo unos años siguiéndole la pista a Kotlin medio de lejos, por mi trabajo en Ceylon; así que creo que es un buen momento para contarles mi opinión al respecto. No les voy a decir si deben usar Kotlin o no; escribo esto porque estoy seguro que muchos lo van a probar y probablemente lo empiecen a usar en proyectos reales, pero tal vez por razones muy simples como la sintaxis o que si no tiene punto y coma o, simplemente, porque no es Java (esta es la razón más popular por la que recibió tan cálida bienvenida en el mundo de Android).

Spring Batch, más allá del tutorial

Parte de la operación de un sistema en el que llevo ya años trabajando, consiste en generar varios reportes diarios para conciliar con proveedores. El proceso es automatizado y ya hay componentes reutilizables para realizar esto, pero la cantidad de productos últimamente ha crecido bastante y esto trae un problema de desempeño, pues cada reporte lee la misma tabla, pero con criterios diferentes, para obtener prácticamente el mismo tipo de datos: Las ventas de X producto de un día.

La tabla en cuestión contiene las ventas de todos los productos, de modo que lo que ocurre diariamente es que se realiza la misma consulta, una vez por producto; algo así como SELECT * FROM venta WHERE producto=? y solamente cambia el producto. Si tenemos 20 productos, pues son 20 consultas a la tabla.

Comunicación asíncrona entre procesos Java

En este post de jpaul estuve comentando acerca de algunas de las broncas de RMI, y de cómo hay opciones más eficientes para cuando se necesita implementar comunicación eficiente entre dos aplicaciones Java, usando algo similar a RMI.

Primero que nada, quiero mostrar cuál es el problema concreto con RMI: cada llamada que se recibe en la aplicación que publica el componente, se hace en un hilo separado. Para demostrar esto, tomé el ejemplo original y lo modifiqué un poco: El servidor tarda un poco de tiempo, y también imprime el nombre del hilo actual y el número total de hilos activos:

Random rng = new Random(System.currentTimeMillis())

Remote stub = UnicastRemoteObject.exportObject(new TestRemote() {
  @Override
  public String sayHello(String name) throws RemoteException {
    println "Corriendo en ${Thread.currentThread().name} total ${Thread.activeCount()}"
    if(rng.nextBoolean())Thread.sleep(10)
    return "Hello, " + name;
  }
}, 0);
def registry = LocateRegistry.createRegistry(Registry.REGISTRY_PORT);
registry.bind("Test", stub);
println "Escuchando..."

Varianza en Ceylon

Bueno pues después del artículo maratónico acerca de varianza en Java, ahora quiero explicar cómo se implementó esto en Ceylon. Para ello vamos a usar los mismos ejemplos, de modo que quede clara la comparación.

Lo primero son las tres clases para el ejemplo, y la primera función:

class A() {}
class B() extends A() {}
class C() extends B() {}

B f(B param) => C();

Hasta aquí, todo funciona exactamente igual que en Java:

B algo;
A a = f(algo); //OK
B b = f(algo); //OK
C c = f(algo); //Error

f(A()); //ERROR
f(B()); //OK
f(C()); //OK

En Ceylon también existe la covarianza en los tipos de retorno, similar a lo que se introdujo en Java 5:

class Padre() {
  shared default A metodo() => A();
}
class Hijo() extends Padre() {
  shared actual C metodo() => C();
}

Con los parámetros no se puede hacer algo similar. Ceylon no tiene sobrecarga de métodos, pero no tiene contravarianza en los parámetros; por lo tanto, al refinar un método de un supertipo, los parámetros deben ser exactamente del mismo tipo que en la declaración original, o el compilador emite un error.

Varianza, Covarianza y Contravarianza

En los sistemas de tipado estático, existe este concepto de varianza, que a veces puede entenderse muy fácil pero tampoco es tan intuitivo como parece.

Para ilustrar la varianza, vamos a definir una jerarquía de clases muy simple:

public class A {}
public class B extends A {}
public class C extends B {}

Y definimos un método o función que usa estos tipos:

public B f(B param) {
   //Qué puede devolver?
}

Primero que nada, viéndolo desde fuera, ¿A qué tipo de variables podemos asignar lo que devuelve esta función?

B algo;
A a = f(algo);
B b = f(algo);
C c = f(algo);

Las primeras dos líneas son correctas, la tercera da un error. Esto es porque la función devuelve un valor de tipo B, y B es también un A. Pero no podemos asignar a una subclase de B, al menos no sin hacer un cast, pero no vamos a hacer casts en esta ocasión. B no es un C, por lo tanto la tercera línea no compila.

Hasta aquí todo bien. Ahora, ¿Qué le podemos pasar como argumento a esta función? Tiene un solo parámetro de tipo B. En esta ocasión, es alrevés:

f(new A()); //ERROR
f(new B()); //OK
f(new C()); //OK

Hystrix: primer contacto

En la conferencia de Software Guru de este año, Agustín Ramos dio una charla acerca de sistemas tolerantes a fallas, en la cual mencionó un software que me llamó mucho la atención, llamado Hystrix, desarrollado por Netflix.

La idea de Hystrix es que en sistemas que se comunican mucho con otros sistemas por medio de red, poder aislar todas esas llamadas a servicios externos y permitir que sean administradas de forma robusta, es decir, que haya un control de conexiones salientes, mantener buenos tiempos de respuesta, con tolerancia a fallas integrada.

Esto suena muy bien: si tengo un sistema que hace llamadas constantes a un web service externo, generalmente el funcionamiento de ese web service afecta el funcionamiento de mi sistema: Si de repente se pone lento, se tarda mucho en contestar, mi sistema empieza a sentirse lento, porque está esperando respuesta del sistema externo. Luego empiezan los problemas porque resulta que mi sistema encola las llamadas a dicho web service, precisamente para no saturarlo, pero pues está lento y eso está fuera de mi control pero resulta que las llamadas encoladas ya se tardan mucho tiempo en ejecutarse, es decir, la tardanza del web service no solamente se convierte en esperar respuesta del mismo, sino que hay llamadas que se quedan mucho tiempo encoladas y entonces puede que ya salgan muy tarde. ¿Y si hay un usuario en línea esperando la respuesta? ¿Y si el sistema ya le respondió error, incluso antes de que siquiera se realice la llamada al servicio externo?

Java 8: mis primeros tropiezos

Pues ahora que ya salió oficialmente Java 8, lo instalé y empecé a hacer pruebas, con un proyecto grande que ya llevo desde hace varios años (empezó en Java 1.4 y lo he migrado a Java 5, 6, 7 y ahora seguirá la 8).

Cuando he migrado, las etapas suelen ser así:

Primero correr las aplicaciones tal cual están, sin modificar nada, sobre la nueva JVM. Eso parece que está funcionando bien (pero tendrán que estar en observación varios días obviamente).

Luego, compilar el código tal cual está, sin modificar nada, con el nuevo compilador. Aquí es donde me di de topes ya desde ahorita. Pasaron dos cosas:

Utilizo Lombok para reducir código en varios lugares y facilitarme la vida en general cuando programo en Java. Una de las cosas que trae es la anotación @SneakyThrows, que permite tratar una excepción verificada como si fuera RuntimeException al momento de compilar. Pero al compilar código que utiliza esta anotación en Java 8:

post-compiler 'lombok.bytecode.SneakyThrowsRemover' caused an exception: null

Y esto es usando la versión 1.12.6 que se supone ya trae soporte para Java 8.

Clases internas en Groovy

Acabo de toparme con un problema en Groovy. Tristemente la solución que todo mundo da en foros es simplemente "no lo hagas, es mejor si haces [cualquier otra solución]"; pero, qué tal si lo que necesitaba era esto?

Bueno y ¿cuál es el problema? Es simple: el soporte en clases internas tiene problemas con atributos heredados en la clase externa. Tiene solución, y es sencilla, pero me parece una leaky abstraction y honestamente la encontré por pura suerte, buscando maneras de darle vuelta al asunto.

El problema se da bajo estas circunstancias:

//Una clase padre, normalita, con un atributo normalito
class Parent {
  String name
}
//Una subclase, normalita, con una clase interna
class External extends Parent {
  //Un atributo propio de esta clase
  String outer
  //Y esta es la clase interna
  class Internal {
    //También tiene un atributo propio
    String inner
    //Este método truena como ejote
    void boom() {
      //Esta línea se ejecuta bien, se despliega el atributo externo y el interno
      println "Outer is " + outer + ", inner is " + inner
      //Pero esta línea truena con un error macabro,
Distribuir contenido