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.

samedi 12 décembre 2015

Soyons fainéant

C'est une situation courante : une fonction qui retournera toujours le même résultat quand elle est appelé sur le même objet. Evidemment le développeur soucieux de performance préférera la transformer en un attribut calculé une fois pour toute dans le constructeur de la classe. Mais si le résultat n'est pas toujours utilisé, le développeur encore plus soucieux décidera d'en faire un cache : le calcul sera fait une fois au premier appel puis réutilisé ensuite. Le développeur plus malin trouvera ou codera un petit outil pour rendre le code plus propre.

Kotlin simplifie et unifie le code grâce au petit mot doux lazy :

val zeus by lazy {
   try {
      InetAddress.getByName("zeus")
   } catch (e: UnknownHostException) {
      null
   }
}

Ici, zeus est une propriété nullable, donc de type InetAddress?, mais l'important est que la requête DNS ne sera faite qu'une seule fois, qu'elle soit réussie ou pas.

mardi 8 décembre 2015

Pourquoi un mot clef inline ? (suite)

Il y a deuxième situation où inliner les fonctions est intéressante. La généricité dans Java a été introduite sans modification du bytecode, probablement pour garantir une compatibilité avec les librairies existantes. L'inconvénient est que les classes comme les fonctions n'ont pas accès au type réel utilisé à l'exécution. C'est à dire qu'on ne peut pas écrire en Java :
<T> void f() {
   System.out.println(T.class);
}

Et c'est parfois bien dommage. On ne peut pas non plus tester si un objet est une instance ou pas de T.

En Kotlin non plus... Sauf si on décide d'inliner la fonction, auquel cas le code est recopié dans la classe appelante, là où le type réel est connu :
inline fun <reified  T> f() {
   println (T::class)
}

Il faut ajouter le mot clef reified devant le type générique.

On peut imaginer plein d'utilisations réelles. Je vous propose un exemple concret. Voyons la version Java :
enum Genre {H, F}

interface A {
   void f(Genre genre);
}

<E extends Enum<E>> E getEnum(BufferedReader r, Class<E> enumClass)  
throws
IOException { return Enum.valueOf(enumClass, r.readLine()); }

La fonction getEnum retourne un enum à partir d'une source, ici un flux de données, mais on peut utiliser une base de données, un objet swing ou une URL, avec un traitement d'erreur, et donc un code plus complexe qu'on souhaite partager, et qui justifie l'écriture de la fonction.

Pour appeler cette fonction on doit expliciter le type réel :
a.f(b.getEnum(reader, Genre.class));

En Kotlin, on peut faire plus simple :
enum class Genre {H, F }

interface A {
   fun f(genre: Genre)
}

inline fun <reified E : Enum<E>> getEnum(r: BufferedReader): E {
   return java.lang.Enum.valueOf<E>(E::class.java, r.readLine())
}

Le second paramètre a disparu et l'appel devient :
a.f(getEnum(reader))

Si la fonction est souvent utilisée, économiser un paramètre obligatoire alors qu'il peut-être déduit de l'environnement, ce n'est pas négligeable pour la lisibilité du code.

vendredi 4 décembre 2015

Pourquoi un mot clef inline ?

En Kotlin, on peut forcer à "inliner" (quelqu'un a t'il une jolie traduction en français ?) une fonction.

Ca parait surprenant au début quand on sait que le JIT sait le faire automatiquement pour nous depuis des années. Ce concept ne devrait plus avoir sa place dans un langage de haut niveau d'abstraction.

Il y a malheureusement un cas où le JIT ne peut rien pour nous, et il faut donc le faire au niveau de la compilation Kotlin vers le bytecode. C'est le cas d'une fonction qui reçoit une autre fonction en paramètre, lorsqu'on l'appelle en lui donnant une expression lambda et non une fonction nommée. Exemple : 
fun f(a: (String) -> String) = println(a("coucou"))
f { a -> a.toUpperCase() }

Le code de f pourra être inliné si le JIT le décide, mais pas l'expression lambda { a -> a.toUppserCase () }. Cette expression si petite soit-elle, sera transformée en un petit objet qui sera passée en paramètre à f. Chaque appel sera couteux.

Kotlin nous permet de résoudre le problème en forçant le compilateur Kotlin à inliner à la fois f et le petit bout de lambda expression. C'est à dire que les deux bouts de code suivants auront la même performance :

inline fun f(a: (String) -> String) = println(a("coucou"))
f { a -> a.toUpperCase() }

println ("coucou".toUpperCase())

Cette astuce n'existe pas en Java 8, le code ne sera jamais inliné et l'usage des expressions lambda est donc coûteuse du point de vue des performances. Ce qui est bien dommage car le code est indéniablement plus concis et plus lisible.

J'ai vérifié avec quelques exemples : le gain de performance avec cette option est réel. Non seulement le code Kotlin avec inline est plus rapide que sans, mais il est sensiblement plus rapide que le code Java 8. Outre le gain, l'absence d'objets intermédiaires garantie un temps d'exécution plus constant avec l'inline. Je suppose que c'est lié au travail du garbage collector, moins sollicité du coup.

On peut quand même se demander pourquoi le compilateur Kotlin ne décide pas tout seul la meilleure stratégie. Je suppose que l'objectif est de pouvoir par défaut re-compiler individuellement chaque fichier Kotlin, comme on le fait en Java. L'inconvénient du bytecode inliné est que les classes appelantes doivent être re-compilées après une modification de la fonction appelée même sans changement de signature.

jeudi 3 décembre 2015

La délégation facile

La délégation est souvent considérée comme une alternative à l'héritage, parfois comme une meilleure pratique, mais c'est parfois la seule solution possible. C'est donc une opération courante.
Par contre, dès que le nombre de fonctions concernées explose, le code devient particulièrement stupide et pénible à maintenir. Malgré plusieurs propositions rien n'a été fait en Java pour simplifier le travail du développeur. Heureusement, les environnements de développement proposent un petit outil pour générer le code automatiquement, mais la maintenance reste plus ou moins manuelle.

Avec Kotlin, c'est on ne peut plus simple. La seule contrainte est de définir les fonctions qui seront déléguées dans une interface. La création d'interface est par ailleurs une pratique recommandée. Ensuite il suffit quand on hérite d'une interface de spécifier l'objet vers qui déléguer les appels et c'est fini !
interface I {
   fun travaille()
   fun pause()
}

class Ouvrier : I {
   override fun travaille() = println ("Je travaille")
   override fun pause() = println ("Je fais une pause")
}

open class Chef(employé: I) : I by employé

fun main(args: Array<String>): Unit {
   val c = Chef(Ouvrier())
   c.travaille()
   c.pause()
}

Un objet de la classe Chef délègue tous les appels aux fonctions définie dans I vers un autre objet. Il suffit de rajouter by employé pour déléguer les appels vers l'objet sous jacent.
Bien sûr la classe Chef peut définir d'autres méthodes, hériter d'autres classes ou interface, voir déléguer vers d'autres objets de la même manière.

Attention, pour redéfinir une fonction déléguée, on ne peut pas le faire directement dans la classe Chef. Une autre classe est alors nécessaire.
Exemple :
open class SuperViseur(équipe: I) : Chef(équipe) {
   override fun pause() {
      val début = Instant.now()
      super.pause()
      println ("Temps d'éxecution ${Duration.between(début, Instant.now())}")
   }
}

fun main(args: Array<String>): Unit {
   val m = SuperViseur(Chef(Ouvrier()))
   m.travaille()
   m.pause()
}

J'ai l'habitude d'encapsuler les resources externes dans des interfaces qui peuvent parfois enfler sans retenue. Après je sépare le code dans des couches bien séparées : l'une fait le lien avec la ressource, les autres gèrent les droits d'accès, les accès concurrents, des caches de données, des mesures de performance, etc... Typiquement, la première couche hérite directement de l'interface, les suivantes d'une classe de délégation pure dont je redéfinie uniquement certaines routines suivant le besoin. Je n'ai plus besoin maintenant de maintenir cette classe de délégation.