Blog Arolla

La bataille des langages à Devoxx

La semaine dernière, à Devoxx France 2012, c’était la battle des langages de programmation sur la JVM. Des petits nouveaux essaient de s’imposer face aux anciens, Scala, Groovy et Clojure,  qui sont relativement bien installés dans le paysage. Dans cet article, je fais un retour sur les deux nouveaux.

Deux constats s’imposent au vu de la prolifération des langages sur la JVM. D’abord le langage,  Java n’arrive plus à satisfaire l’ensemble de la communauté qui semble être à la recherche d’un meilleur Java. Les langages alternatifs déjà installés ne font pas l’unanimité non plus: Scala pour cause de sa complexité (réelle ou supposée), Groovy à cause du typage dynamique (cela est en train de changer). Ensuite, la JVM est une plateforme mature à laquelle sont attachés les développeurs Java et les prétendants à la succession de Java (le langage) l'ont compris et se veulent tous interopérables avec ce dernier.


L’origine des deux langages

Kotlin et Ceylon viennent respectivement de JetBrains et de Red Hat, de deux gros utilisateurs de Java ayant une longue pratique du langage. Cette expérience pratique devrait influencer le design de Kotlin et de Ceylon.

Les objectifs affichés 

Ceylon

  • Syntaxe assez proche de celle de Java : l'idée est de faciliter l'apprentissage du langage pour tous ceux qui connaissent déjà un langage dérivé du C
  • Faciliter l'écriture de frameworks grâce notamment à la méta-programmation
  • Immutable par défaut

Kotlin

  • Compilation aussi rapide qu'en Java
  • Plus simple que Scala qui est considéré comme étant le compétiteur le plus mature

Les buts en commun

Faire un langage compatible avec Java mais plus sûr et plus concis

  • Système de type meilleur que celui de Java
  • Facile à apprendre
  • Modulaire par construction

Les deux langages gagnent en concision notamment grâce l'inférence de type.

Morceaux choisis

Maintenant voyons quelques fonctionnalités plus en détail.

Commençons par le classique « Hello world » !

Voici un hello en Ceylon :

class Hello() {
  print("Hello Ceylon!");
}

En Kotlin cela donne :

fun main(args : Array) {
    println("Hello Kotlin")
}

Rien de révolutionnaire jusque-là ! Une remarque toutefois: en Ceylon le point-virgule est obligatoire à la fin d'une instruction contrairement à Kotlin.

Null est le mal (null-safety)

Ceylon et Kotlin obligent tous les deux le développeur à préciser si le retour d'une fonction (ou méthode) ou une référence quelconque peut être null.

Par exemple, supposons que nous souhaitons définir une fonction getNumberOfPosts() qui retourne le nombre de posts d'un blog. La définition suivante passerait en Java mais en Kotlin ou en Ceylon.

En Java :

Integer getNumberOfPosts() {
  return null;
}

En Kotlin :

fun getNumberOfPosts(): Int? {
  return null ;
}

En Ceylon :

Integer? GetNumberOfPosts() {
  return null;
}

Les deux langages ont fait le choix de faire suivre le type d'une référence d'un point d'interrogation pour indiquer qu'elle peut être null.

Supposons que vous souhaitez, pour une raison qui m'est inconnue, écrire le double du nombre de posts dans une variable. Vous feriez cela comme suit en Kotlin :

val numberOfPosts = getNumberOfPosts()
val doubledNumberOfPosts = numberOfPosts?.times(2)

En Ceylon les choses ne sont guère différentes :

Integer? numberOfPosts = getNumberOfPosts();
Integer? doubledNumberOfPosts = numberOfPosts?.times(2);

Cela nous amène à une autre feature intéressante des deux langages, le cast intelligent (smart cast).

Le smart cast

En Ceylon :

if(exists numberOfPosts) {
  Integer doubledNumberOfPosts = numberOfPosts.times(2);
}

En Kotlin :

if (numberOfPosts is Int) {
  val doubledNumberOfPosts = numberOfPosts.times(2)
}

Comme vous l'avez remarqué, une fois que le test de non-nullité réussit, nous pouvons invoquer les méthodes en toute sûreté.

Et l'héritage dans tout ça

Nous allons définir une simple hiérarchie de classes pour voir comment on définit et utilise une classe en Kotlin et Ceylon. Notre système est constitué de 3 classes : une classe de base Message avec deux implémentations, SmsMessage et EmailMessage.

En Ceylon :

abstract class Message() {
  shared formal String sender();
  shared formal String receiver();
  shared formal String message();

  shared default String asString() {
    return "Generic message : # " + message();
  }
}

class SmsMessage(String senderNumber, String receiverNumber, String content) extends   Message() {
  shared actual String sender() {
    return senderNumber;
  }

  shared actual String receiver() {
    return receiverNumber;
  }

  shared actual String message() {
    return content;
  }

  shared actual String asString() {
    return "Sms message : # " + message();
  }
}

class EmailMessage(String senderEmail, String receiverEmail, String content) extends Message() {
  shared actual String sender() {
    return senderEmail;
  }

  shared actual String receiver() {
    return receiverEmail;
  }

  shared actual String message() {
    return content;
  }

  shared actual String asString() {
    return "Sms message : # " + message();
  }
}

Plusieurs nouveaux mots-clés font leur apparition ici, formal, default, actual et shared.

  • shared :Les règles en Ceylon sont ridiculement simples : tout est privé par défaut et shared est utilisé pour rendre publique une méthode.
  • formal spécifie que le membre annoté ne fournit pas d'implémentation.
  • default précise une implémentation par défaut.
  • actual permet de préciser qu'on est en train de redéfinir une méthode.

Si vous codez en Java,vous ne devrez pas vous sentir dépaysé ici. Même s'ils pourraient empêcher de faire des erreurs d'inattention, je trouve que tous ces mots-clés rendent la syntaxe un peu verbeuse. Maintenant, attaquons-nous à la classe qui envoie les messages :

class MessageSender() {
  shared void send(Message message) {
    switch(message)
    case (is SmsMessage) {
      print("Sending SMS message..." + message.asString());
    }
    case (is EmailMessage) {
      print("Sending email message..." + message.asString());
    } else {
      print("Unknown message type");
    }
  }
}

Le switch n'étant pas encore implémenté, ce code ne s'exécutera pas. Je vous l'ai montré afin que vous voyiez l'élégance du 'switch-case' qui devrait à terme être implémenté dans Ceylon. Je vous donne une version simple qui a le mérite de fonctionner (véridique) :

class MessageSender() {
  shared void send(Message message) {
    print("Sending SMS message..." + message.asString());
  }
}

Envoyons deux p'tits messages à nos amis avant d'entamer l'implémentation de notre système avec Kotlin. Notez l'absence du mot-clé new!

MessageSender messageSender = MessageSender();
messageSender.send(SmsMessage("01234", "04321", "Lol! Je te vois sur Facebook!"));
messageSender.send(EmailMessage("toto@wesh.fr", "titi@mdr.fr", "Mdr! T'as vu ça!?"));

En Kotlin :

abstract class Message {
    abstract fun sender(): String
    abstract fun receiver(): String
    abstract fun message(): String
    open fun asString() = "Generic message # " + message()
}

class SmsMessage( val sender: String, val receiver: String, val message: String) : Message() {
    override fun sender() = sender
    override fun receiver(): String = receiver
    override fun message(): String = message
    override fun asString(): String = "SMS : " + message()
}

class EmailMessage(val sender: String, val receiver: String, val message: String) : Message() {
    override fun sender() = sender
    override fun receiver(): String = receiver
    override fun message(): String = message
    override fun asString(): String = "Email : " + message()
}

Là, un nouveau mot-clé a fait apparition: open. Les classes et les méthodes sont par défault final en Kotlin. C'est la raison pour laquelle la méthode est asString() est annotée avec open pour qu'on puisse la redéfinir.

Le sender :

class MessageSender {
    fun send(message: Message) {
        when (message) {
            is SmsMessage -> println("Sending SMS message : # " + message.asString())
            is EmailMessage -> println("Sending email message : # " + message.asString())
            else -> println("Unknown message type.")
        }
    }
}

Renvoyons nos messages avec le nouveau code :

val messageSender = MessageSender()
messageSender.send(SmsMessage("01234", "04321", "Lol! Je te vois sur Facebook!"))

Une impression de déjà vu ? Vous avez raison: la seule chose qui a changé est la déclaration de la variable messageSender.

Les outils

Sans surprise le support de Kotlin est excellent. Quant à Ceylon, le plugin est fonctionnel.

Nous allons nous arrêter là, car cet article commence à être trop long même s'il reste de nombreuses fonctionnalités intéressantes (les traits, les type union, les closures etc.) non mentionnées.

Conclusion

Comme vous avez pu le constater, ces deux langages sont assez similaires à la fois dans les objectifs et dans la syntaxe. Pour voir de réelles différences, il faudrait les étudier davantage. L'avantage qu'ils ont face aux anciens y compris Java, est qu'ils peuvent apprendre des erreurs du passé. La bataille des langages sur la JVM n'est pas prêt de s'arrêter et Java, le vétéran, n'a pas dit son dernier mot car ses futures versions pourraient changer la donne. Le chemin est encore long pour rivaliser réellement avec Groovy et Scala, qui sont déjàdéployés en production chez des acteurs assez sérieux.

J'espère que ce bref aperçu de quelques fonctionnalités vous a donné envie d'aller plus loin avec Kotlin et Ceylon. Un examen plus poussé est nécessaire pour mieux cerner les choix effectués par les auteurs. Je note toutefois une plus grande verbosité dans la syntaxe de Ceylon par rapport à Kotlin notamment à cause des mots-clés shared, actual etc.

Plus de publications

3 comments for “La bataille des langages à Devoxx

  1. Pingback: Devoxx France 2012 : On en parle sur le web | NormandyJUG
  2. Gaetan
    2 mai 2012 at 11 h 08 min

    Une modification apportée aujourd’hui pour la M3 de Ceylon. Kotlin se confirme comme étant moins verbeux.

    https://plus.google.com/u/0/114693277542356668702/posts/9hu5kyQiAd9

  3. Pingback: Discussion avec Nouhoum Traoré | Arolla