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.

mercredi 2 décembre 2015

Quelques bases de la syntaxe

Après les petites astuces vues précédemment, voyons un peu en gros la syntaxe de Kotlin. C'est un mélange de Java, de Scala, de C# et certainement d'autres encore.

Il n'y a plus besoin de point-virgule, et c'est tellement simple qu'on se demande pourquoi on en avait besoin...

Plus déroutant, il n'y a plus de fonctions statiques. A la place, on les écrit où on veut mais hors d'une classe, mais quand même dans un package. Pour les appeler, on les importe individuellement ou en groupe (import ...*), puis on les appelle sans nom de classe, comme un bon vieux C.

Comme dans Scala, chaque déclaration de variable (de fonction ou de classe) doit être précédée de val ou var, et suivi du type et si nécessaire d'une valeur. Avec val, la variable devient invariable :-). C'est le mot clé final de Java. On pourra constater qu'en pratique la plupart de nos variables sont en réalité des constantes. En plus, Kotlin permet de retarder le calcul de certaines constantes, ce qui permet de déclarer encore plus de constantes qu'en Scala.
Une variable dont le type est suivi d'un point interrogation peut contenir null, les autres ne peuvent jamais contenir null. Plusieurs opérateurs originaux permet de contrôler la nullité d'un variable afin de ne jamais cracher avec un NullPointerException.
Très pratique, comme en Scala, le type peut-être très omis pour être déduit par le compilateur. Non seulement le code est plus concis, plus lisible, mais il est aussi plus facile à maintenir lorsqu'on change un type.
Exemples :
val hello: String = "bonjour"
var x: String?
var y = hello

La déclaration des fonctions ressemble à Scala en remplaçant le def par fun. Pour le corps, en revanche on peut choisir entre un style impératif comme java ou un style fonction comme Scala. Les deux ont leur avantage selon l'objectif et selon l'auteur, le choix est appréciable.
Comme plus haut, le type de retour peut-être souvent omis. Un type de retour correspond à un procédure qui ne retourne aucune valeur (void en Java). Enfin les paramètres accepter des valeurs par défaut.
Les fonctions peuvent aussi être imbriquées, c'est à dire définie à l'intérieur d'une autre fonction, afin de limiter leur visibilité, de les rapprocher de leur lieu d'usage, et de pouvoir utiliser les paramètres de la fonction englobante sans devoir les passer explicitement en paramètre.
Exemples :
fun square(x: Double): Double {
   return x * x
}

fun square(x: Double) = { x * x}

fun square(x: Double) = x * x

fun displaySquare(x: Double = 2.0): Unit = println (square(x))

Comme dans Scala et Java 8, les paramètres et variables peuvent référencer des fonctions. Pour faire simple, leur type s'écrit (paramètres) -> Retour. Juste les parenthèses vides désigne une fonction sans paramètre, et un retour Unit une procédure qui ne retourne rien.
Exemples :
val f: (String, Int) -> String
val g: (String) -> Unit
val h: () -> Int

On peut aussi étendre un type, c'est à dire ajouter une fonction dans une classe mais sans modifier celle-ci. On peut donc ajouter une fonction à la classe String. C'est principalement un artifice syntaxique pour avoir un code plus consistant, puisque les fonctions ajoutées sont appelées avec la même notation que les fonctions réellement définie dans la classe.
Exemple :
fun String.wordCount() = this.split(' ').size
val wc = "coucou la compagnie".wordCount()

Les structures de contrôles sont classiques à l'exception de la boucle for et du branchement multi-directionnel when. La boucle peut être appelé sur une liste ou un segment. Le when accepte quasiment n'importe quelle condition.
Exemple :
for (i in 0..5) println(i)
for (s in "coucou la compagnie".split(' ')) println (s)
val b = when (a) {
   is Int -> "un entier"
   "trésor" -> "trouvé"
   else -> "par défaut"
}

Evidemment, c'est très loin d'être exhaustif, le but de ce post était de donner un aperçu des syntaxes pour mieux suivre les prochains articles.

mardi 1 décembre 2015

Construire élégamment un arbre

C'est une opération classique, banale, et en même temps souvent très mal faite.
Les arbres d'objets sont courants : Swing, Html, rapports, Json. Mais en Java, ils sont souvent construits linéairement parce que Java est un langage impératif. Le résultat est peu lisible, source d'erreur dû aux confusions entre noeuds, et surtout pénible à maintenir.

Kotlin est aussi un langage impératif, mais il propose une petite astuce en associant les extensions de types et le passage de fonction en paramètre.

Je vous propose de créer un arbre de données pour Json, utile par exemple dans une servlet.

Une extension de type c'est ajouter une fonction sur un type déjà existant. Par exemple, créons une fonction pour récupérer le second élément d'une liste :
fun List<*>.second() = this.get(1)
val l = listOf("un", "deux", "trois")
println (l.second())
Ce code affiche "deux".

Le passage de fonction en paramètre est classique et reprend la syntaxe de Java 8.

L'astuce est de définir une fonction qui créé une HashMap et de lui passer en argument une extension de Map :
fun sub(init: MutableMap<String, Any>.() -> Unit): Map<String, Any> {
    val res = java.util.HashMap<String, Any>()
    res.init()
    return res
}

La génération de l'arbre est devenu arborescente, bien plus lisible et fiable :
val a = sub {
   put ("nom", "Abraham")
   put ("age", 70)
   put ("enfants", listOf(
      sub () {
         put ("nom", "Homer")
         put ("age", 40)
         put ("enfants", listOf(
            sub() {
               put ("nom", "Bart")
               put ("age", 10)
            },
            sub() {
               put ("nom", "Lisa")
               put ("age", 8)
            },
            sub() {
               put ("nom", "Lisa")
               put ("age", 1)
            }
         ))
      }
   ))
}
println(org.json.simple.JSONObject.toJSONString(a))

Ce code affiche :
{"enfants":[{"enfants":[{"nom":"Bart","age":10},{"nom":"Lisa","age":8},{"nom":"Maggie","age":1}],"nom":"Homer","age":40}],"nom":"Abraham","age":70}

La génération reste assez verbeuse parce que générique à tout document Json. Elle pourrait-être simplifiée en fonction des données à stocker dans le document.

Notez enfin que les listOf peuvent être remplacée par des boucles ou tout autre code de génération de données.