vendredi 29 avril 2016

Analyse des paramètres pour une commande en ligne

Si, comme moi, vous aimez le shell Unix, vous avez régulièrement des petits projets pour un programme à l'ancienne, avec une commande en ligne.

A la base, comme java tout commence avec la fonction main :
fun main(args: Array<String>) {...}

Les paramètres sont donc une simple list de strings, sans aucune structure. Or vous souhaitez pouvoir configurer facilement votre programme avec des options, obligatoires ou facultatives.
L'analyse des options avec une simple boucle sur "args" est triviale, et tout à fait respectable.
Si néanmoins, vous voulez faire pro, avec une aide en ligne, bien présentée, et surtout écrire un code un peu plus haut-niveau, il vous faut une librairie.
J'ai choisi la première venue de chez Apache : commons-cli. J'ai commencé évidemment à l'utiliser telle quelle, et comme toute librairie Java, on l'intègre sans souci dans le projet Kotlin.
Mais le résultat n'est pas très optimal, et ne tire aucun avantage de Kotlin. J'ai voulu encapsuler cette libraire en Kotlin. Voici le résultat:

fun main(args: Array<String>) {
   options {
      option("h", "help", false, "Display help")
      option("t", "to", true, "List of recipients")
      option("m", "maxNbOfPosts", true, "Maximum number of posts")
   }.parse("Notif [options]", args) {
      if (hasOption("help")) printHelp()
      else execute (stringOption("to"), intOption("maxNbOfPosts", 50))
   }
}

Le début est une construction avec une syntaxe déclarative. Le sujet a été traité ici.
La commande parse est suivie par l'exécution réelle du programme. Le paramètre 50 est la valeur par défaut en cas d'absence.
Toute erreur de syntaxe lors de l'appel du programme affichera l'aide ("printHelp") :

usage: Notif [options]
 -h,--help                 Display help
 -m,--maxNbOfPosts <arg>   Maximum number of posts
 -t,--to <arg>             List of recipients

La syntaxe de l'API est certainement perfectible, mais le résultat est à la hauteur de mes ambitions.

Le code de l'encapsulation Kotlin est librement disponible ici
.

jeudi 28 avril 2016

Kotlin vs streams dans Java 8

Java connait enfin des closures à partir de java 8. Et avec les closures, nous pouvons résumer de nombreuses lignes de code purement techniques en une seule ligne. En plus d'être plus court, l'objectif sémantique est bien mieux exprimé.

Alors plus besoin de Kotlin ?

En fait on trouve deux avantages avec Kotlin concernant la manipulation de collections.
Premièrement, Kotlin génère un byte code java 6 compatible avec Android, alors que Java 8 ne l'est pas.
Ensuite, la syntaxe proposée par les streams de Java 8 est certes puissante, mais horriblement verbeuse quand on la compare avec Kotlin.

Java 8 a choisi de ne pas surcharger les interfaces historiques. Par conséquent il faut systématiquement convertir la collection en "stream" avant manipulation. Et si le résultat attendu est aussi une collection, une deuxième conversion est nécessaire. Passons aux exemples.

Pour filtrer une liste sur une valeur, il faut donc écrire ce genre de code :
List<String> result = source.stream().filter (
      i -> i.endsWith(".txt")).collect(Collectors.toList());

Vous objecterez que c'est mieux qu'en Java 7, mais comparez avec le code équivalent en Kotlin :
val result = source.filter {it.endsWith(".txt")}

Pour générer une string avec une liste, Java 8 propose un collector spécifique :
String result = source.stream().collect(Collectors.joining(","));

Avec Kotlin, l'opération est simplement une nouvelle fonction de la collection :
val result = source.joinToString(",")

On pourrait multiplier les examples avec les autres opérations : map, minBy, groupBy...

Les streams ont quand même un intérêt : la performance. Prenons le code Kotlin suivant :
val result = source.filter {it.endsWith(".txt")}.first()

Ici, la liste sera entièrement parcourue et filtrée. Une nouvelle liste sera ensuite construite pour contenir tous les éléments qui auront passé le filtre, pour ensuite ne garder que le premier élément ! C'est à l'évidence un gâchis de mémoire et de temps.
Avec les stream Java 8, la fonction "filter" ne fait rien d'autre que bâtir un autre stream. C'est l'appel à la fonction "findFirst" qui déclenchera le travail, pour en faire le moins possible. Mais heureusement, Kotlin peut aussi travailler avec des streams :
val result = source.asSequence().filter {it.endsWith(".txt")}.first()

D'ailleurs, utiliser les stream peut être souvent judicieux pour économiser la mémoire en évitant les résultats intermédiaires lors de chaînes d'opération.

vendredi 1 avril 2016

Utilisation de "let"

La librairie fournit avec Kotlin est riche. Et tout n'est pas documenté dans le site d'Intellij.

J'ai découvert récemment le fichier Standard.kt dans le runtime de Kotlin qui contient une série de toutes petites routines, dont "let" :
/**
 * Calls the specified function [block] with `this` value as its argument and returns its result.
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R = block(this)

Honnêtement, je n'ai pas bien compris à quoi ça pouvait servir sur le moment. Pourtant je m'en suis servi à peine une semaine plus tard.

Supposons une construction assez classique dans certaines bibliothèques java :
interface Builder {
   Builder name (String n);
}

La routine name renvoie une nouvelle instance du builder légèrement modifiée. Cela permet de construire des données avec un code lisible par un enchainement de modifications d'objets immuables, donc thread-safe (ex b = new Builder().name("toto").size(14).add("tutu")).

Supposons le code, Kotlin bien sûr, appelant :
val toto: String? = [...];
fun build() {
   var builder: Builder = [...]
   if (toto != null) builder = builder.name(toto)
}

L'idée est de modifier le builder uniquement si le paramètre n'est pas null.
Le code ci-dessus est correct.

Mais si la propriété "toto" cesse d'être une constante ?
var toto: String? = [...];

Le simple "if" devient dangereux dans un programme multi-thread car la propriété pourrait changer de contenu entre le test et l'appel à la routine "name". Elle pourrait par exemple devenir null et faire une action illégale dans la bibliothèque.

D'ailleurs si l'interface est réécrite en Kotlin :
interface Builder {
   fun name (n: String): Builder
}

Le code avec un var ne compilera même plus ! Car name ne peut être appelée avec null  Alors que si "toto" est un val, le compilateur considère que dans le "if" "toto" ne sera jamais null.

La solution bourin est de passer par une variable locale :
   val localToto = toto
   if (localToto != null) builder = builder.name(localToto)

Mais cette verbosité sera vite désagréable quand on multipliera les attributs modifiables.

Mais une solution existe pour revenir à une seule ligne :
   builder = toto?.let { builder.name(it) } ?: builder

Si "toto" n'est pas null, alors on appelle le fameux "let", qui exécutera l'expression dans laquelle "it" contiendra la valeur de "toto". Si "toto" est null le code ira chercher le "?:" et la variable "builder" sera réaffectée à elle-même.