Kotlin, ¿otra moda más? (Spoiler: no)

Cuando empecé a escribir este artículo, hace unas semanas, os hablaba de la cantidad de frameworks, arquitecturas y lenguajes nuevos que salen cada día, y de la necesidad de separar el polvo de la paja antes de invertir nuestro tiempo en tratar de asimilar nuevas tecnologías.

Me lo había currado, pero el 17 de mayo de 2017, hace justo una semana, Google anunció que adoptaba Kotlin como lenguaje de primer nivel para el desarrollo Android. O sea, que ahora el mundillo se ha dividido entre los que gritan “lo sabía” y los que susurran “¿pero eso de qué va?”.

Sé que os pido un acto de fe, pero no puedo dejar de señalar que en Paradigma éramos del bando de los visionarios. Y, sin más introducción, empezamos por lo más básico…

¿Qué es Kotlin?

Kotlin es un lenguaje de programación de tipado estático para la JVM, Android, el navegador y próximamente LLVM, 100% interoperable con Java, creado por Jetbrains, la compañía responsable, entre otros, de Intellij Idea.

Dicho así, parece otro lenguaje más para la JVM de los que a estas alturas ya hay tantos, ¿verdad? En cierto modo, así es, pero el diablo está en los detalles…

¿Por qué Kotlin?

¿Quién no ha hecho alguna vez algo en JavaScript? Vamos, levantad la mano los que alguna vez hayais acariciado la idea de poner en el currículum eso de Full Stack Developer. ¿Y quién no la ha liado parda confundiéndose entre igualdades dobles y triples, o convirtiendo algún valor aleatorio en truthy y flipándolo al depurar las condiciones de sus if?

No me hace falta veros para saber que casi todas las manos siguen arriba (y el que la ha bajado es por vergüenza, ¡a mí no me engaña!).

JavaScript puede ser muchas cosas, pero si lo tuviera que definir con una palabra, ésta sería confuso. Y, antes de que se me enfaden los de front, ¿cómo se explica, sino, que el libro más recomendado sobre el lenguaje sea éste?

Mención especial al subtítulo: desenterrando la excelencia en JavaScript. Desenterrando. Poca broma.

El quid de la cuestión es que un lenguaje de programación, como cualquier otra herramienta, tiene que hacer lo correcto sencillo, y lo erróneo, más complicado. Como el señor Crockford, y otros quinientos vídeos en YouTube, nos demuestran parece que JavaScript se empeña a veces en hacerlo al revés. Y así nos va.

Pero que no se rían los javeros, que nosotros también tenemos nuestros diez mandamientos de cómo programar sin echarnos la soga al cuello…

El amigo Joshua Bloch se marcó aquí un libro imprescindible. Y el que lleve programando en Java diez o quince años y no se lo haya leído, que no se crea que no tiene nada que aprender de él, sino todo lo contrario. Ahora es cuando va a poder sacarle todo el jugo, cada vez que vea una solución a alguna metedura de pata de esas que todos tenemos en nuestra conciencia.

¿Qué significa para un lenguaje que le salgan libros tan imprescindibles como éstos? Que algo huele a podrido en Dinamarca. Sinceramente, creo que la máquina virtual de Java es la mejor opción para el desarrollo de software actualmente, un ecosistema insuperable y un lenguaje que recogió muchas buenas ideas y nos ha permitido llegar muy lejos. Pero, desengañémonos, el tiempo no pasa en balde y ahora tenemos más que claro que, además de buenas ideas, también hay decisiones de diseño claramente superadas.

Nuestras opciones son dos: aprendemos a programar estupendamente, invirtiendo tiempo y esfuerzo extra en ello, y evitamos coger nuestra herramienta por el lado que corta, o buscamos una herramienta mejor. Porque no basta con que tú hagas las cosas bien, sino que tienes que confiar en que el resto de tu equipo también lo haga… y que vuestras versiones de bien coincidan.

Que se puede, ojo, y lo hacemos muchos día a día. (En Paradigma, por supuesto, lo hacemos todos, ¡qué demonios!) No obstante, ¿por qué esforzarse más de lo necesario habiendo opciones mejores y, me atreveré a decirlo, más divertidas?

Pero ya tenemos otros lenguajes…

Eso es cierto, y sin ni siquiera salirnos de la JVM podríamos hablar de Jython, Groovy, Scala, Clojure, Ceylon, Xtend… O decidir que no nos importa atarnos a Microsoft y dedicarnos a C#, que salió de la sombra de Java para convertirse en un muy buen lenguaje con una plataforma con muy pocas posibilidades por desgracia.

El primer gran debate sobre lenguajes de la Historia

Pero los de Jetbrains fueron muy listos cuando decidieron que el mantenimiento del código de sus productos era demasiado farragoso y que necesitaban un lenguaje mejor, ya que vieron un hueco en el mercado que nadie estaba cubriendo. Había sitio para una alternativa siempre que fuera:

  • Un lenguaje para la JVM. Porque es el mejor runtime del mercado con diferencia, sin rival para grandes servidores pero capaz de ejecutar software en un PC normal y corriente.
  • Fácil de aprender para los javeros, que es donde fallan opciones como Scala o Clojure. Siempre se tuvo en mente hacer un lenguaje práctico, no académico, para que cualquiera de los 9.000.000 de desarrolladores Java en el mundo lo pudiera pillar prácticamente pudieran entender sin mucha dificultad y ser productivos rápidamente.
  • 100% compatible con Java, de forma que en un mismo software se pueden mezclar partes hechas en Java con partes hechas en Kotlin de forma transparente. O sea, que no hay que tirar todo lo hecho y empezar de cero. Se puede hacer una nueva clase en Kotlin, o un test y convive perfectamente con el resto del proyecto. Otra consecuencia de esto es que se puede usar cualquier librería Java desde Kotlin. O hacer una librería en Kotlin y usarla desde Java. Ojo a esto, que puede parecer una tontería pero, en mi opinión, es brutal. Como para poner el párrafo entero en negrita, si no quedara tan feo.
  • Fuertemente tipado. Porque que un test te diga que tu código está mal es bueno, pero que te lo diga el compilador es mejor. Por eso Intellij Idea te puede ayudar a convertir código, autocompletarlo, analizarlo… Y aquí la gente de Groovy llora.
  • Probado en producción. Los experimentos están bien para el típico proyecto de juguete, pero para usar algo en el trabajo más nos vale estar seguros de que no nos va a dar más dolores de cabeza de los necesarios. Jetbrains lleva desarrollando el lenguaje seis años, y casi desde el principio se ha estado usando en el desarrollo de sus propios productos. A ellos les funciona. Y a los de Trello, y a los de Basecamp y tantos otros.
  • Compatible con Android. En mi opinión, éste fue el golpe de gracia. Con más del 80% del mercado de los smartphones, los desarrolladores Android nos hemos visto anclados en una especie de Java 7 que, visto el dineral que se llevan dejados en abogados Google y Oracle, no tiene visos de mejorar mucho a corto plazo. ¿Cuánto tiempo estuvo Google añadiendo funcionalidades de Java 7 poco a poco en Android? ¿Años? ¿Alguien sabe si todavía falta algo? Mientras tanto, llegó Java 8, se extendieron las lambdas y los streams como la pólvora, y en el móvil seguimos escribiendo líneas y líneas de parafernalia para crear una triste clase anónima. ¿Alguien ha intentado escribir RxJava sin lambdas? Creedme, no es bonito la primera vez, no hablemos de mantenerlo. 
  • Google era muy consciente de esto y, viendo que la gente estaba recurriendo a follones como usar Retrolambda (que a mí siempre me ha funcionado genial, pero reconozcamos que es un hack como un castillo), acabó creando jack, un nuevo compilador que permitía algunas funcionalidades de Java 8Pero, vamos, los problemas de compatibilidad y rendimiento han sido tan gordos que hace poco lo tiraron a la basura e incorporaron estas novedades en el compilador de siempre. Era poco, llegaba muy tarde y ya veremos si algún juez no lo tira para atrás. En Google sabían que no era suficiente… (Me dice mi compañero Miguel Sesma que no tenemos por qué suponer que el soporte a Java 8 no vaya a ser completo. Yo digo que, visto que todavía están anunciando nuevas características planificadas para ser portadas, me lo creeré cuando lo vea… en 2021, a este paso).

    Recapitulemos. En Android Java está anticuado, Groovy en Android no tira, Scala va regular, y Ceylon (obra de otro crack como es Gavin King, creador de Hibernate y con Red Hat respaldándole) ni está ni se le espera.
    Pero Kotlin está hecho para funcionar perfectamente en móviles desde el día uno. Y, un señor llamado Jake Wharton (prácticamente el semidiós oficial para los que nos dedicamos a Android) va, se fija en él y lo recomienda en un informe interno para su empresa. El resto ya es historia…

Jake Wharton, seduciendo a la cámara

Y, como colofón de la historia, en la keynote de la Google I/O 2017 saltó el bombazo: Google da soporte oficial a Kotlin como lenguaje de primer nivel para el desarrollo en Android:

Los gritos y los aplausos de los que la veíamos en streaming desde el Campus Madrid se tuvieron que oír desde la calle, y Twitter y Slack se convirtieron en una fiesta. Gente aplaudiendo a un lenguaje de programación, ojo. Casi nada.

Luego también anunciaron que, además de ser open source, donan Kotlin a una fundación sin ánimo de lucro y demás. Pero eso ya son cosas de abogados…

¡¿Pero cómo es Kotlin?!

Vale, igual todavía no habéis visto ni una línea de código. Pero creo que era importante conocer el porqué de las cosas y, si he hecho bien mi trabajo, ahora estaréis deseosos de ver algo de chicha en lugar de leeros los ejemplos en diagonal.

¿Cómo es Kotlin? Kotlin es como coger el libro de Effective Java e implementar casi todas sus recomendaciones en el lenguaje. Pensad en todo lo que os dio dolores de cabeza en vuestro último proyecto y las vueltas que tuvisteis que dar para dejarlo bien. Ahora ved cómo dejarlo niquelado desde el minuto uno.

Disclaimer: El siguiente código no va a ser siempre tan idiomático como podría. La idea es que se entienda la diferencia, pero haceos un favor y no lo cojáis como ejemplo para ponerlo en producción.

Tipos nullables

¿A alguien más le pasa que más de la mitad de los errores que acaba resolviendo son NullPointerException en algún punto? ¿Cómo se sabe que un método puede devolver null o no? ¿Y se lo puedo pasar a un parámetro? Hoare dice sin complejos que la invención de null fue su error de los mil millones de dólares, y cincuenta años después sigue siendo una verdad como un templo.

En Java hay muchas formas de comprobarlo, incluyendo dos o tres librerías que compiten entre ellas con anotaciones @Null, @Nullable, @NotNull, etc. que, si te acuerdas de usarlas y el IDE que utilizas las parsea, te pueden salvar de algún patinazo. ¿Las usas en tus proyectos? ¿Y no son… um… un peñazo?

Podía ser peor, podía ser la opción oficial de Java 8, el tipo Optional. (Opción… Optional… ok, lo dejo.) O, como yo lo llamo, la gran oportunidad perdida de Java. Señores de Oracle, ¿tan difícil era darle un poco de azúcar sintáctico a esto para hacerlo un poco sencillo de usar? O, yo que sé, hacerlo Serializable. Llamadme loco, pero cuando se han montado flamewars sobre cuándo y cómo se debe usar Optional y, sobre todo, cuándo no, es que igual esto no está todo lo pulido que podría.

En fin, que al final lo que hacemos todos es mirar el Javadoc, rezar para que esté actualizado y, por último, sembrar el código de if.

No con Kotlin. Kotlin pasa de todo esto.

Java

/**
* Típico ejemplo.
*
* @param nombre nombre a saludar, no puede ser null
*/
public void helloWorld(String nombre) {
   // Le ponemos el trim para darle emoción al asunto
   System.out.println("Hola, " + nombre.trim()); // NPE si viene null
}

public void helloWorldDefensivo(String nombre) {
   if (nombre != null) {
       System.out.println("Hola, " + nombre.trim());
   }
}

public void helloWorldAnotado(@NotNull String nombre) {
   System.out.println("Hola, " + nombre.trim());
}

public void helloWorldOpcional(Optional<String> nombre) {
   // ¡Ojo, si usas Idea hasta el IDE se queja por usar Optional en un parámetro!
   System.out.println("Hola, " + nombre.map(String::trim).orElse("mundo"));
}

Kotlin

// Un String no puede ser nulo jamás
fun helloWorld(nombre: String) {
   println("Hola, " + nombre.trim())
}

// String? sí, pero el compilador obliga a comprobarlo antes de usarlo
fun helloWorldConIf(nombre: String?) {
   if (nombre != null) {
       println("Hola, " + nombre.trim())
   }
}

// Hay llamadas seguras, que devuelven null si la variable es null
// Y un operador Elvis, que devuelve un valor por defecto si lo de la izquierda es null
fun helloWorldLlamadaSegura(nombre: String?) {
   println("Hola, " + (nombre?.trim() ?: "mundo"))
}

// Por interoperabilidad, si un valor viene de Java se presupone nullable, pero el
// programador puede jurar que está seguro de que no... y si no es así, que se lance 
// una NullPointerException
// Allá tú, si te parece buena idea
fun helloWorldPeligroso(nombre: String?) {
   println("Hola, " + nombre!!.trim())
}

Inmutabilidad

Otro clásico de los días perdidos depurando código. ¿Una variable se supone que se puede sobreescribir o no? Tenemos la opción de poner final a todo, pero es poner una palabra extra a, efectivamente, todo y la mayoría de las veces no se hace. Mal por nosotros.

Con Kotlin te tienes que decidir desde el principio. Y quizás te des cuenta de que la mutabilidad es el demonio, así por regla general. Bien por Kotlin.

Java

final String inmutable = "Esto no se puede cambiar";
String mutable = "Esto sí, pero igual habíamos pensado que no, ¿no?";

Kotlin

val inmutable = "No se puede cambiar"
var mutable = "Esto sí, y digo explícitamente que espero que cambie"

Propiedades

Odio los JavaBeans. Los odio. Hay que escribir las variables, los getters y los setters y todo en Javadoc, copiando y pegando lo mismo cuatro veces. Pero se hace por si alguien quiere alguna lógica, me diréis. Vale, ¿y no podría haber una notación un poco más concisa?

Java

public class BeanJava {
   // El nombre
   // Tranquilo si no te has enterado, ahora te lo repito unas cuantas veces
   private String nombre;

    /**
    * Constructor.
    *
    * @param nombre nombre
    */
    public BeanJava(String nombre) {
       this.nombre = nombre;
    }

   /**
    * Devuelve el nombre.
    *
    * @return nombre
    */
   public String getNombre() {
       return nombre;
   }

   /**
    * Escribe el nombre.
    *
    * @param nombre nombre
    */
   public void setNombre(String nombre) {
       this.nombre = nombre;
   }
}

Kotlin

/**
* Bean con un [nombre].
*/
class BeanKotlin {
   var nombre: String? = null
}

/**
* Bean con un [nombre] cuya primera letra se devuelve en mayúscula.
*/
class BeanKotlinLogica {
   var nombre: String? = null
   // ¿Queremos añadir lógica? Sin problema
   get() = nombre?.capitalize()  
}

/**
* Bean con un [nombre] que se inicializa con un parámetro del constructor.
*/
class BeanKotlinConstructor(var nombre: String)

fun pruebaDeUso() {
   // Se usa así de fácil
   val bean: BeanKotlin = BeanKotlin()
   bean.nombre = "sergio"
   println(bean.nombre)   // sergio

   // ¿El getter tiene lógica? ¡Y a mí qué! 
   // Os dais cuenta de que podéis meterla a posteriori sin tocar ni una coma del 
   // resto del código, ¿verdad?
   val bean2: BeanKotlinLogica = BeanKotlinLogica()
   bean2.nombre = "sergio"
   println(bean2.nombre)    // Sergio

   val bean3: BeanKotlinConstructor = BeanKotlinConstructor("Sergio")
   println(bean3.nombre)
}

Sin tipos primitivos

En Java hay tipos primitivos y objetos. Esto lo hicieron en Sun por optimización. ¿Sabéis quiénes son mejores que las personas a la hora de analizar código y optimizarlo? Los compiladores, eso es. En Kotlin todo dato es un objeto, y deriva de Any?. Los arrays también. No hay casos especiales.

Inferencia de tipos

¿Es obvio de qué tipo es una variable o una función? Pues no lo pongas si no quieres.

var cadena = "Soy un String"

Equals

Llevas programando años y años, y todavía te equivocas a veces poniendo == en lugar de equals, o viceversa. En Kotlin ambas cosas son lo mismo.

Java

AbstractMap.SimpleEntry<Integer, String> peras =
       new AbstractMap.SimpleEntry<>(2, "frutas");
AbstractMap.SimpleEntry<Integer, String> manzanas =
       new AbstractMap.SimpleEntry<>(2, "frutas");
 
System.out.println(peras.equals(manzanas)); // True
System.out.println(peras == manzanas);        // False

Kotlin

val peras = Pair(2, “frutas”)
val manzanas = Pair(2, “frutas”)
  
println(peras.equals(manzanas)) // True
println(peras == manzanas)        // True
println(peras === manzanas)      // El == de Java, False en este caso

Valores por defecto

¿Queremos en Java un valor por defecto en un parámetro? ¡Bienvenidos a la fiesta del polimorfismo!

Java

public void helloWorld(String sustantivo, String adjetivo) {
   // Todos sabemos que la concatenación es poco eficiente
   System.out.println(String.format("Hola, %s %s", sustantivo, adjetivo));
}

public void helloWorld() {
   helloWorld("mundo", "cruel");
}

public void helloWorld(String sustantivo) {
   helloWorld(sustantivo, "anodino");
}

// ¿Quieres una versión con sólo el adjetivo? Eso sería otro método con un parámetro String. 
// No se puede.
// Hay que cambiarle el nombre al método.
public void helloWorldFeo(String adjetivo) {
   helloWorld("cosa", adjetivo);
}

Kotlin

fun helloWorld(sustantivo: String ="mundo", adjetivo: String="cruel") {
   println("Hola, " + sustantivo + " " + adjetivo)
}

fun pruebaDeUso() {
   helloWorld("planeta") // Hola, planeta cruel
}

Parámetros por nombre

Espera, pero al ejemplo anterior le faltaba algo. ¿Se puede llamar al método para dejar el sustantivo como está y sólo cambiar el adjetivo? Claro que sí, guapi.

fun pruebaDeUso() {
   helloWorld(adjetivo = "feliz")  // Hola, mundo feliz
}

Clases de datos

Ok, hemos visto propiedades, inferencia de tipos, valores por defecto y parámetros por nombre. Era todo parte de un plan para enseñaros las clases de datos ya que, si ordenara estos ejemplos por importancia, para mí estaría claramente en el Top 3 de Kotlin. Atentos.

Si ya definir en una clase propiedades te ahorra líneas y líneas de burocracia con getters, setters y documentación, pensemos en que al típico JavaBean, que queremos que se identifique por los datos que lleva dentro y ya está, hay que escribirle su equals, su hashCode y su toString.

Porque nunca se nos escapa comprobar que se respeta el contrato entre equals y hashCode, obviamente. Al fin y al cabo, hay librerías que te ayudan, y prácticamente todos los IDEs traen funciones para generarlos.

Eso sí, luego añadimos un campo nuevo, se nos olvida mantener estos dos métodos, colocamos una instancia en una colección y empieza la fiesta…

No nos quedemos aquí. ¿Los campos han de ser mutables o inmutables? ¿Creamos un constructor o un Builder? ¿Y un método para hacer copias? Podríamos decir que no nos hace falta y que ya veremos más adelante, pero eso es una receta segura para acabar haciendo spaghetti con el código, y lo sabéis. Mejor acordar una convención al principio, ¿no? ¿O Kotlin nos la podría ahorrar?

Ojo a esta comparativa, que es de las que duelen. Para ser honesto, podría usar Guava o Apache Commons, pero apenas me quitaría dos o tres líneas…

Java

public class BeanJava {
   private String nombre;
   private String apellido;

   public BeanJava(String nombre, String apellido) {
       this.nombre = nombre;
       this.apellido = apellido;
   }

   public String getNombre() {
       return nombre;
   }

   public void setNombre(String nombre) {
       this.nombre = nombre;
   }

   public String getApellido() {
       return apellido;
   }

   public void setApellido(String apellido) {
       this.apellido = apellido;
   }

   @Override
   public boolean equals(Object o) {
       if (this == o) return true;
       if (!(o instanceof BeanJava)) return false;

       BeanJava beanJava = (BeanJava) o;

       if (nombre != null ? !nombre.equals(beanJava.nombre) : beanJava.nombre != null) return false;
       return apellido != null ? apellido.equals(beanJava.apellido) : beanJava.apellido == null;
   }

   @Override
   public int hashCode() {
       int result = nombre != null ? nombre.hashCode() : 0;
       result = 31 * result + (apellido != null ? apellido.hashCode() : 0);
       return result;
   }

   @Override
   public String toString() {
       return "BeanJava{" +
               "nombre='" + nombre + '\'' +
               ", apellido='" + apellido + '\'' +
               '}';
   }

   public BeanJavaBuilder copy() {
return new BeanJavaBuilder(nombre, apellido);
   }

public static final class BeanJavaBuilder {
   private String nombre = "José";
   private String apellido = "García";

   public BeanJavaBuilder() {
   }

   private BeanJavaBuilder(String nombre, String apellido) {
       this.nombre = nombre;
       this.apellido = apellido;
   }

   public static BeanJavaBuilder aBeanJava() {
       return new BeanJavaBuilder();
   }

   public BeanJavaBuilder withNombre(String nombre) {
       this.nombre = nombre;
       return this;
   }

   public BeanJavaBuilder withApellido(String apellido) {
       this.apellido = apellido;
       return this;
   }

   public BeanJava build() {
       BeanJava beanJava = new BeanJava(nombre, apellido);
       return beanJava;
   }
}
}

Kotlin

data class BeanKotlin(var nombre = "José", var apellido= "García")

Lo repito, la palabra data ha añadido ha añadido a la clase lo siguiente:

  • equals
  • hashCode
  • toString
  • copy, para crear una nueva instancia a partir de ésta, cambiando algún parámetro si es necesario

A lo que le sumamos, por pura sintaxis de Kotlin, los valores por defecto en los parámetros y la facilidad de llamar a los parámetros por nombre.

fun quieroUnHermanito() {
   var sergio = BeanKotlin(“Sergio”, “Delgado”)
   var bebe = sergio.copy(nombre=”Raúl”)
}

¡Todo esto en una línea! ¡Sin posibilidad de bugs! ¡Y siempre estará al día!

Interpolación de cadenas

Por cierto, dijimos que la concatenación no molaba. Pero hasta ahora, lo he hecho en todos los ejemplos de Kotlin. Ha sido sólo por ir poco a poco. En realidad lo que escribiría es esto:

Java

System.out.println(String.format("Hola, %s %s", sustantivo, adjetivo));

Kotlin

println("Hola, $sustantivo $adjetivo")

Recordad, hacer sencillo lo correcto. ¿Cuántas veces no pasamos del String.format porque luego no se entiende o simplemente por pereza?

Literales de cadenas

No es sólo la interpolación, es que definir cadenas en Java se hace muy farragoso. Entre estas dos opciones, ¿qué versión os parece más legible?

Java

String cadenaLarga = "Una línea\nDos líneas";

String otraCadenaLarga = "Para que se noten las líneas\n" +
       "Partimos en varios literales\n" +
       "Y concatenamos";

String json = “{\”nombre\”:\”Sergio\”,\n\”apellido\”:\”Delgado\”}”;

Kotlin

var cadenaLargaKotlin = """
   Aquí no hay que escapar
   Los saltos de línea están permitidos
   Pero los espacios a la izquierda
   Se cuelan en la variable
   <--- estos espacios
"""

var cadenaLargaKotlinConTrim = """
   |No os preocupéis
   |Kotlin tiene solución para todo
   |El pipe es el prefijo por defecto
   |Pero podríamos usar otro carácter
""".trimMargin()

var json = “””
{ 
	// Qué cambio, ¿no?
“nombre”: “Sergio”,
	“apellido”: “Delgado”
}
“””

Métodos de extensión

Todos tenemos una clase StringUtils o dos. Pena que eso no sea orientación a objetos ni nada que se le parezca. Un método de extensión permite añadir código a una clase que no puedes tocar, de forma que se lea como Dios manda.

fun String.sayHello() {
   println("Hola, $this")
}

fun pruebaDeUso() {
   "mundo".sayHello()
}

No lo uséis para ejemplos tan cutres. Pero añadidlo a vuestros Context y Activity de Android y maravillaos de la diferencia.

Funciones y lambdas

Habéis visto que los métodos llevan todos la palabra reservada fun delante. Efectivamente, significa function. ¿Por qué se introduce? Porque Kotlin permite tener funciones fuera de objetos. También definir una función con una lambda, o una referencia a otra función. Y pasar funciones como parámetros de otras de orden superior, sin necesidad de inventarse interfaces. ¡25 de diciembre, fun, fun, fun!

fun funcionOrdenSuperior(cadena: String, funcion: (String) -> String) {
   println(funcion(cadena))
}

fun inicialAMayusculas(cadena: String): String {
   return cadena.capitalize()
}

// Una forma más fácil de decir lo mismo
fun inicialAMayusculasExpresion(cadena: String) = cadena.capitalize();

fun pruebaDeUso() {
    // Sin instancia, porque la función está definida en el paquete
   funcionOrdenSuperior("sergio", ::inicialAMayusculas)  

   // Una función de una clase
   funcionOrdenSuperior("sergio", String::capitalize)    
  
   // Una lambda, de la forma más farragosa posible
   funcionOrdenSuperior("sergio", { cadena: String -> cadena.capitalize()})
  
   // Si el último parámetro es una lambda, se puede sacar de la lista de parámetros
   funcionOrdenSuperior("sergio") { cadena: String -> cadena.capitalize() }

   // Y si sólo tiene un parámetro, se puede tomar el nombre por defecto it
  // ¿Os imagináis los listeners de Android así?
   funcionOrdenSuperior("sergio") {
       it.capitalize()
   }

}

Colecciones y rangos

Tenemos funciones de orden superior. Tenemos lambdas. Tenemos clases con datos inmutables. ¿No os suena esto a programación funcional? Nadie nos obliga, pero si queremos, podemos. Y para ello, un buen recubrimiento de las librerías de colecciones es poco menos que imprescindible.

val numeros = listOf(1, 2, 3, 4)
numeros.filter {
   // Quita impares
   it % 2 == 0                  
}.map {
   // Multiplica por 2
   it * 2                       
}.forEach {
   // Y los pinta por consola
   println("Resultado: $it")    
}

val mapa = hashMapOf("uno" to 1, "dos" to 2, "tres" to 3)
val cadena = mapa.filter {
   // Desestructuramos cada pareja en dos variables, e ignoramos la clave
   (_, num) -> num % 2 != 0      
}.map {
   // Ahora desechamos el valor
   (nombre, _) -> nombre         
}.joinToString(", ")
// Y obtenemos "uno, tres"

También hay rangos, que no sólo se usan para los for.

for (i in 1..10) {
   println(i)
}

val j = 3
if (j in 1..10)
   println("$j está entre 1 y 10")
}

Incluso hay secuencias, que son como las colecciones pero con evaluación perezosa y potencialmente infinitas.

// Generará las potencias de 2, a partir de un valor inicial y una función para calcular a partir de él el siguiente
generateSequence(1) { it * 2 }
       // Cogemos las 5 primeras
       .take(5)
       // Y las metemos en una lista
       .toList()

Puntos y comas

Os habréis fijado en que los puntos y comas son opcionales en Kotlin. Se ve que mucha gente los odia. Problemas del primer mundo.

¿Y no hay cosas malas?

Pues, para seros sinceros, alguna también:

  • Poco extendido. Hasta esta semana, Kotlin era conocido por una minoría, eso sí, entusiasta. Pero ya veréis el número de visualizaciones en YouTube de las dos conferencias que le han dedicado en la Google I/O…
  • No es matemáticamente perfecto. La gente de Scala, que no pierde oportunidad para criticarnos al resto.
  • Faltan herramientas. Por ejemplo, os habréis fijado que no podemos formatear código fuente Kotlin en el blog para ponerlo tan bonito como el de Java. O, aunque yo sea muy fan del análisis de código de IntelliJ Idea, no tenemos un analizador como Sonarqube para colocarlo en nuestro proceso de integración continua. Por ahora.

Se queda mucho en el tintero

Efectivamente, apenas hemos rascado la superficie. Espero, no obstante, haberos dejado con ganas de más. Nos vemos, si os apetece, en próximos artículos del blog donde podremos hablar de:

  • Más características del lenguaje. ¿Aún más? Sí, aún más, pero a cambio no os daré la tabarra con mis historias. ¡Clases selladas! ¡Funciones inline! ¡Delegación! ¡Singletons! ¡Genéricos! ¡Corutinas! ¡DSLs!
  • Android. Basta de trocitos de código, aquí haremos un tutorial completo para crear una app desde cero y sin usar nada de Java. Os gustará, os lo aseguro.
  • JavaScript. ¿No os acordábais de que Kotlin también servía para el navegador? ¿Conseguirá triunfar donde Google fracasó con Dart? Lo tiene complicado, pero quizás un ejemplo con React Native os pueda hacer poneros de su parte.
  • Spring Boot. Un microservicio con Spring Boot 1.5 funciona perfectamente, y os lo enseñaré, pero ¿sabíais que hay soporte oficial del lenguaje en Spring 5 y Spring Boot 2.0? El futuro de Kotlin es brillante en el backend.
  • Gradle. El sustituto de Maven me produce una reacción de amor-odio. Por una parte, bueno, no es Maven, pero mentiría si dijera que no me he pasado horas revisando scripts Groovy para encontrar alguna opción que no estaba definida justo donde debía estar y que descarrilaba toda mi compilación. ¡Benditos lenguajes dinámicos! No seré el único que se frustra por esto, porque la gente de Gradle está trabajando duro para permitir definir los scripts totalmente en Kotlin. Aleluya.
  • Testing. Si tu jefe es un cobardica y no te deja poner Kotlin en producción, piensa que seguro que no le importa cómo demonios hagas los tests. JUnit 4, JUnit 5, Mockito, Spek, Kluent… Podemos con todo.
  • Kotlin Native. En Jetbrains son ambiciosos. No sólo quieren que compartamos código entre la JVM y JavaScript, sino que ahora se animan a plataformas con compilación nativa e interoperabilidad con C. Con Kotlin Native apuntan a dispositivos de IoT o al iPhone, aunque por ahora tenemos una preview funcional para Raspberry Pi. Nos quitan el recolector de basura (que prometen reemplazar) y la librería estándar de Java, pero a cambio nos dan… el mundo. ¡Un lenguaje para dominarlos a todos!
  • Más frameworks, si aún no os habéis hartado. ¿Vert.X? ¿Kara? ¿ktor? Eso sí, no prometo que ninguno siga vivo el año que viene. Menos Vert.X, me temo.
  • Y muchas más sorpresas… supongo.

¿Y si no puedo esperar?

Pues mientras tanto, aquí hay recursos de todas las clases y colores:

Pero lo más importante si quieres aprender es… ¡remangarte y ponerte a ello! Ánimo. No es tan difícil.

9 comentarios

  1. Iván López dice:

    Hola,

    Sólo un par de puntualizaciones:

    Un par de comentarios:

    – Groovy es fuertemente tipado. El problema es que mucha gente en lugar de utilizar los tipos utiliza “def” que es lo mismo que utilizar “Object” en Java.

    Ejemplo con Groovy:

    $ cat Foo.groovy
    class Foo {
    static void main(String[] args) {
    def number = 5 // Igual que Object number = 5
    number = “Hola Groovy”
    println number
    }
    }

    $ groovy Foo.groovy
    Hola Groovy

    Y ahora con Java:

    $ cat Foo.java
    public class Foo {
    public static void main(String[] args) {
    Object number = 5;
    number = “Hola Java”;
    System.out.println(number);
    }
    }

    $ javac Foo.java
    $ java Foo
    Hola Java

    Como ves exactamente lo mismo.

    La diferencia es la siguiente:

    Java:
    $ cat Foo2.java
    public class Foo {

    public static void main(String[] args) {
    Integer number = 5;
    number = “Hola Java”;
    System.out.println(number);
    }
    }

    $ javac Foo2.java
    Foo2.java:1: error: class Foo is public, should be declared in a file named Foo.java
    public class Foo {
    ^
    Foo2.java:5: error: incompatible types
    number = “Hola”;
    ^
    required: Integer
    found: String
    2 errors

    Error de compilación.

    Y con Groovy:

    $ cat Foo2.groovy
    class Foo {
    @groovy.transform.CompileStatic
    static void main(String[] args) {
    Integer number = 5
    number = “asd”
    println number
    }
    }
    $ groovy Foo2.groovy
    org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
    /home/ivan/Foo2.groovy: 5: [Static type checking] – Cannot assign value of type java.lang.String to variable of type java.lang.Integer
    @ line 5, column 18.
    number = “asd”
    ^

    1 error

    Error de compilación con Groovy. Esto ocurre porque he añadido la anotación @CompileStatic, por lo que tenemos chequeos de tipos en tiempo de compilación. Sin la anotación estaremos usando “Groovy dinámico” y tendremos un error de tiempo de ejecución. Es por eso (además de por rendimiento) por lo que la anotación @CompileStatic es utilizada cada vez más.

    – Groovy en Android no tira: Groovy está soportado en Android desde hace 2 años. No niego que probablemente no es muy utilizado, pero funcionar funciona y conozco a gente que ha hecho aplicaciones Android en Groovy (yo incluido).

    No estoy diciendo que Kotlin no esté bien y no sea una alternativa magnífica a Java, porque lo es. Especialmente para Android, ya que el Java que hay que escribir en Android es “como Java 6” y es todo muy verboso. Es lo que ocurre cuando creas un nuevo lenguaje en el que “coges” lo mejor de otros: python, ruby, groovy,…

    Saludos, Iván.

    • Sergio Delgado dice:

      Sabía que alguien me iba a dar con el @CompileStatic en la boca y lleváis razón. Aunque os la doy en parte, porque @CompileStatic bien sabemos que fue un agregado a posteriori. Además, desde mi ignorancia, ¿se puede usar en todos lados? ¿En un script de Gradle por ejemplo? Me salvaría la vida…

      En cuanto a Groovy en Android, tengo muy malas referencias, pero poca experiencia de primera mano. Si es una alternativa factible hoy en día, trolleos aparte, me alegro bastante, en serio. Nos hacen falta opciones para todos los gustos aquí.

      • Iván López dice:

        Hombre, @CompileStatic fue añadido a posteri porque los lenguajes van evolucionando con el tiempo. Igual que, por ejemplo en Kotlin han añadido las corutinas en la versión 1.1. Aún así @CompileStatic fue añadido en la versión 2.0 que es de Junio de 2012, hace casi 5 años!

        Respondiendo a tu pregunta, @CompileStatic se puede utilizar en todos los sitios en los que no quieras/necesites Groovy dinámico. Esto, por regla general probablemente sea el 90% de tu código. De hecho ni siquiera necesitas añadirlo a todas tus clases o métodos. Justo ayer tuvimos una charla en Madrid-GUG en la que Mario García nos enseñó cómo añadir una opción al compilador de Groovy (vía Gradle) para que todo el código se compile estáticamente sin necesidad de anotarlo.

        En Gradle no lo puedes utilizar porque es un DSL y no está hecho para ser estático. Gradle prefirió añadir soporte a Kotlin en lugar de dedicar tiempo de Cédric Champeau (empleado de Gradle y committer de Groovy) en mejorar el DSL de Gradle para autocompletado, tipado estático,…

        Y sobre Groovy en Android, como digo no creo que sea muy utilizado. Está soportado pero no sé si es una a día de hoy es una alternativa real y factible o se ha quedado en un experimento nada más.

  2. José María dice:

    Muy buen artículo, ¡enhorabuena!

  3. Arnaldo Trujillo dice:

    El artículo más completo que he visto de Kotlin explicando sus muchas luces y pocas sombras., aunque le duela a la gente de Groovy. ¡Felicidades!

  4. Ruben Garcia dice:

    Pues siento mucho no entrar al hype pero todo lo que tiene Kotlin lo tiene Scala y más cosas. Kotlin es un buen lenguaje para aquellos que no están contentos con Java y son muy perezosos para aprender un lenguaje de verdad 😉

    • Sergio Delgado dice:

      En verdad, Scala lo tiene todo menos legibilidad… :P

    • Jairo dice:

      Scala no es tan potente como mucho piensan se queda supremamente corto cuando lo comparo con Haskell por ejemplo. Kotlin aunque también tiene una sintaxis desde mi punto de vista incorrecta (por tratar de hacerlo similar a JAVA) parece con más potencial en el mediano plazo.

    • Jairo G dice:

      Scala no es tan potente como mucho piensan se queda supremamente corto cuando lo comparo con Haskell por ejemplo. Kotlin aunque también tiene una sintaxis desde mi punto de vista incorrecta (por tratar de hacerlo similar a JAVA) parece con más potencial en el mediano plazo.

Escribe un comentario