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.

mardi 24 novembre 2015

Enfin une gestion uniforme, fiable et pratique des références "null"

Commençons par le plus excitant. Tout dévelopeur Java connait le cauchemar de la NullPointerException. Kotlin promet de la bannir à tout jamais !

Comment ?


Quand une variable est déclarée de type A, elle ne peut jamais contenir null. Quand elle est déclarée de type A?, elle peut être null.
Il est interdit d'appeler une fonction sur un type nullable ou d'affecter une valeur nullable à une variable qui ne l'est pas sans avoir explicité la marche à suivre si la valeur est nulle.
Nous verrons plus loin comment Kotlin vous propose de le faire élégamment dans le code.

Kotlin reprend ainsi la notation de C#, mais étendu à tous les types, références ou primitifs (int, double...). Alors que C# ne l'accepte que pour les types struct et primitifs.

Pourquoi ce choix est-il capital ?


Il y a plusieurs raisons qui rendent la NullPointerException quasi inévitable, peut-être la plus fréquente des exceptions en Java, et parfois assez délicate à analyser :

  • elle peut survenir n'importe où : tout appel de fonction sur une variable est source d'exception
  • vérifier le contenu de chaque variable ou retour de fonction systématiquement est illusoire et même néfaste pour la lisibilité du code
  • lorsqu'elle survient, il n'y aucun paramètre ou information disponible à part le numéro de ligne dans le code source, et parfois par malchance on trouvera plusieurs appels de fonctions à cette ligne

Des solutions existent en Java, dont les annotations @Nullable et @NotNull et en Java 8 l'annotation @NonNull. Ces annotations peuvent être rajoutées pour protéger la valeur d'une variable, mais surtout les entrées/sorties d'une fonction, c'est à dire ses paramètres et son type de retour. Grâce à ces annotations, l'environnement de développement peut détecter certains appels dangereux, et le compilateur peut aussi rajouter des tests automatiques pour lever l'exception de nullité le plus tôt possible.

Le défaut est que ce ne sont que des annotations, donc non obligatoires. En pratique, les librairies ne l'utilisent pas toutes. Et pour avoir essayé de me l'imposer dans mon propre code, on oublie vite les bonnes résolutions lorsque le rythme de développement s'accélère. L'autre défaut est d'alourdir considérablement le code.

Scala, l'alternative principale à Java sur JVM, n'apporte aucune solution, à part de ne plus utiliser de null dans son code, et d'encapsuler les valeurs nullable dans des instances de la classe Option. Je n'ai personnellement pas assez codé en Scala pour être catégorique, mais je n'y vois pas une solution pratique : le code qui gère les références nullable est du coup bien verbeux, et il faudrait en théorie protéger tout ce qui vient d'une libraire extérieure.

Une solution intégrée au langage était nécessaire, mais il fallait la rendre plaisante à utiliser.

La syntaxe dans le détail


Voyons d'abord les variables non nullable. Il suffit de définir la variable avec le nom du type sans rien d'autre. Pour appeler une fonction, on utilise la syntaxe familière avec le point :
val a: String = "hello world !"val l: String = a.substring(0, 6)

Notez que la syntaxe simple par défaut est réservée au cas majoritaire des variables non nullables, dont l'usage est ainsi fortement encouragé.

Maintenant lorsque la variable est nullable, il suffit de rajouter un ? à la fin du type :
val a: String? = f()
     
Comme annoncé plus haut, il est alors nécessaire de spécifier ce qu'il faut faire quand la variable contient null :

  • Pour appeler une fonction
    a?.substring(06retournera simplement null si a est null, c'est donc une expression de type String?
  • Pour convertir en type non nullable
    a ?: "bye
    retournera "byesi a est null, et a autrement, c'est donc une expression de type String 
  • Et un combiné des deux est possible
    a?.substring(06) ?: "byeretournera "bye" si a est null. c'est donc une expression de type String (non nullable)
  • L'expression après le ?: peut lever une exception
    a?.substring(06) ?: throw Exception()
    Dans ce cas, l'exception pourra être typée, enrichie avec des paramètres donc plus utile. 

Je crois qu'il est difficile de faire plus concis.
En revanche un appel direct à a.substring(06) sera clairement rejeté à la compilation.

Il est bien entendu possible de tester explicitement la nullité avec les opérateurs == et !=. Dans ce cas, la variable sera considérée comme non nulle dans le code exécuté après le test !
C'est à dire que l'on peut écrire :
val a: String? = f()
if (a != null){
   val l: String = a.substring(0, 6)
}

La variable a change automatiquement de type à l'intérieur du if, et évite ainsi de déclarer une variable supplémentaire.

Conclusion


La gestion des références nullable dans Kotlin est :

  • uniforme, car elle est utilisable pour tous les types y compris les types simples comme Int
  • fiable, car elle est obligatoire
  • pratique, car le cas majoritaire (non null) est la forme la plus concise, des opérateurs nouveaux permettent de gérer facilement les valeurs null, et enfin les variables deviennent automatiquement non nullable après un test.


Introduction à Kotlin

Ce blog a pour objectif de présenter un nouveau langage sur JVM : Kotlin.

Ce n'est pas un guide d'initiation ou d'utilisation, encore moins un cours en ligne, mais plutôt une liste de petites astuces que j'ai découvert dans ce langage pour simplifier le développement sous JVM.

Attention, la version 1.0 est encore en beta (novembre 2015).

Liens :
- site principal (en anglais ) : https://kotlinlang.org
- page wikipedia : https://fr.wikipedia.org/wiki/Kotlin_(langage)