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.

Aucun commentaire:

Enregistrer un commentaire