Tutoriel Ceylon : concepts de base

Image non disponible

Nombreux sont les langages dédiés à la JVM, mais aucun autre que Ceylon ne propose un système de typage aussi poussé ainsi qu'une compilation en JavaScript. À travers une série d'articles, je propose de vous faire découvrir tous les mystères de ce langage conçu par Gavin King.

Commentez Donner une note à l'article (5)

Article lu   fois.

L'auteur

Site personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Avant-propos

Cet article est le deuxième d'une série articulée sur la présentation du langage Ceylon :

  1. Présentation et installation ;
  2. Concepts de base ;
  3. Typage ;
  4. Appels et arguments ;
  5. Collections ;
  6. Modules ;
  7. Interopérabilité avec Java.

Cet article est dédié à la présentation des éléments de base du langage : les littéraux, les types de base, les structures de contrôle et la déclaration de types.

II. Modules

II-A. Présentation

Dans Ceylon, l'élément de plus haut niveau est le « module » qui délimite une librairie ; c'est l'équivalent des .so/.dll (natifs ou de .Net) ou des .jar (de Java).
Ces derniers sont constitués d'un ensemble de « packages », l'équivalent des « namespaces » du C++/C#. Et enfin viennent les « déclarations ». Les fichiers servent uniquement à répartir les déclarations selon vos goûts et vos couleurs. Ainsi, si vous créez les deux fichiers de source ci-dessous, vous obtiendrez une erreur de compilation vous signalant que maFonction est déclarée deux fois :

Doublon (Première déclaration)
Sélectionnez
1.
2.
3.
4.
//Source: tutoriel/a_modules/exemples/fonctionDupliquee1.ceylon
void fonctionDupliquee() {
  // Fonction dupliquée
}
Doublon (Seconde déclaration)
Sélectionnez
1.
2.
3.
4.
//Source: tutoriel/a_modules/exemples/fonctionDupliquee2.ceylon
void fonctionDupliquee() {
  // Fonction dupliquée
}

Et le résultat :

Doublon (sortie console)
Sélectionnez
1.
Duplicate declaration error: fonctionDupliquee is declared twice

Les déclarations comprennent :

  1. Les références que l'on pourrait appeler variable ou attribut dans d'autres langages ;
  2. Les fonctions qui contrairement aux méthodes n'appartiennent pas à une classe mais à un « package » ;
  3. Les classes.

Donc contrairement au Java, les « packages » de Ceylon ne contiennent pas uniquement des classes, ce qui est nécessaire, car Ceylon ne dispose pas de membres « static ». Les constantes, les singletons, etc. seront donc définis au niveau des « packages » et non des classes.

Je profite de la présentation des premiers éléments du langage pour attirer l'attention sur deux principes fondateurs de Ceylon.
Par défaut, tous les éléments sont « immuables » ; ainsi, les références ne sont pas modifiables et les méthodes ne peuvent être redéfinies.
La deuxième chose, c'est que la syntaxe utilise fortement le « sucre syntaxique » de manière intelligente et, nous le verrons, il est tout à fait possible d'écrire la même chose sans ce « sucre ». Dans ce cas, il faudra retourner aux lourdeurs que l'on connaît dans certains langages. Ainsi, il est possible en partie de redéfinir certains opérateurs grâce à ce « sucre syntaxique ».

II-B. Rédaction d'un module

Un module, qu'est-ce que c'est ? Il s'agit d'une bibliothèque avec un nom et une version ; c'est le pendant des bundles en OSGi. Contrairement au fonctionnement natif de Java, Ceylon dipose d'un système de gestion de version des bibliothèques : vous pouvez avoir plusieurs versions d'une même bibliothèque au sein de vos applications sans qu'il n'y ait de conflit.

Commençons donc par créer notre premier module. Dans le répertoire des sources, créez un package tutoriel.modules.demo1 (qui correspond à l'arborescence tutoriel/modules/demo1) et ajoutez le fichier suivant :

module.ceylon
Sélectionnez
//Source: tutoriel/a_modules/demo1/module.ceylon
module tutoriel.modules.demo1 "1.0.0" {}

Notez que le nom du module doit être le même que le package racine !

Vous avez désormais un module avec le nom tutoriel.modules.demo1 et la version 1.0.0. Celui-ci contient également un package non partagé du même nom.

Jusqu'à maintenant, nous avons uniquement utilisé le module du langage ceylon.language qui est une dépendance implicite à tous les modules Ceylon. Nous allons désormais utiliser le module ceylon.collectionceylon.collection :

Importer le module ceylon.collection
Sélectionnez
//Source: tutoriel/a_modules/demo1/module.ceylon
module tutoriel.modules.demo1 "1.0.0" {
  shared import ceylon.collection "1.0.0";
}

Vous pouvez « partager » vos imports (c'est-à-dire les annoter avec shared). Les modules qui importent le vôtre importeront alors automatiquement ceux qui sont partagés. Ceci est particulièrement utile si vous en faites usage dans l'API « publique » de votre module.

Maintenant, nous pouvons créer un module avec une API (partagée) et une implémentation (privée) :

tutoriel/modules/demo2/module.ceylon
Sélectionnez
//Source: tutoriel/a_modules/demo2/module.ceylon
module tutoriel.modules.demo2 "1.0.0" {
  import ceylon.collection "1.0.0";
}
tutoriel/modules/demo2/package.ceylon
Sélectionnez
//Source: tutoriel/a_modules/demo2/package.ceylon
shared package tutoriel.modules.demo2;
tutoriel/modules/demo2/Personne.ceylon
Sélectionnez
//Source: tutoriel/a_modules/demo2/Personne.ceylon
import ceylon.collection { MutableList }
import tutoriel.modules.demo2.impl { PersonneImpl }

shared interface Personne {
  shared formal String nom;
  shared formal String prenom;
  shared formal MutableList<Personne> enfants;
}

shared Personne creer(String nom, String prenom) {
  return PersonneImpl(nom, prenom);
}
tutoriel/modules/demo2/impl/package.ceylon
Sélectionnez
//Source: tutoriel/a_modules/demo2/impl/package.ceylon
shared package tutoriel.modules.demo2.impl;
tutoriel/modules/demo2/impl/PersonneImpl.ceylon
Sélectionnez
//Source: tutoriel/a_modules/demo2/impl/PersonneImpl.ceylon
import tutoriel.modules.demo2 { Personne }
import ceylon.collection { MutableList, LinkedList }

shared class PersonneImpl(shared actual String nom, shared actual String prenom) satisfies Personne {
  shared actual MutableList<Personne> enfants = LinkedList<Personne>();
  shared actual String string => nom + " " + prenom;
}

On remarquera que pour utiliser des éléments d'un package, il faut utiliser import suivi du nom du package, puis entre {} la liste des éléments à importer. Contrairement à Java, il n'est pas possible d'utiliser le nom complet au sein du code pour accéder à un élément. Pour gérer les collisions de nom (deux éléments de même nom, mais de packages différents) ou par convenance, il est possible de renommer un élément :

Renommer un import
Sélectionnez
//Source: tutoriel/a_modules/exemples/renommerImport.ceylon
import ceylon.collection { List=LinkedList }

void demoRenommageImport() {
  print(List({"Un", "Deux"}));
}

Il est également possible de renommer un membre d'un type :

Renommer un membre d'un import
Sélectionnez
//Source: tutoriel/a_modules/exemples/RenommerImportMembre.ceylon
import ceylon.collection { List=LinkedList { length=size } }

void demoRenommageImportMembre() {
  value list = List({"Un", "Deux"});
  print("Liste (``list.length``): ``list``");
}

Vous êtes désormais capable d'écrire vos propres modules. Avant de regarder plus en détail la génération et le contenu d'un module, voici quelques éléments où l'IDE peut vous aider.
Premièrement, dans les propriétés du module (depuis le package racine et non le fichier module.ceylon), il est possible d'avoir une interface de configuration du module et un assistant d'import :

Image non disponible

Il existe également plusieurs assistants de création de module, de package et de fichier de sources Ceylon. Si vous êtes dans la perspective « Ceylon », ils sont accessibles depuis le menu contextuel, puis « New » ou depuis le menu « File » → « New ». Autrement, depuis les mêmes menus, il vous faudra choisir « Other » pour afficher une fenêtre de sélection :

Image non disponible

II-C. Génération d'un module

L'avantage d'un IDE, c'est qu'il gère lui-même beaucoup de choses, mais en contrepartie bien des aspects sont masqués.
Pour commencer, voyons où trouver ce qu'il a généré. À la racine de votre projet, parcourez le dossier « modules ». Si celui-ci ne s'affiche pas, rafraîchissez votre projet.
Vous devriez trouver une arborescence (comme pour les packages) qui correspond aux composantes des noms des modules que vous avez créés. Voici un exemple :

Image non disponible

Globalement pour chaque module, on retrouve trois fichiers (ne vous souciez pas des autres) :

  • mon.module-1.0.car : il s'agit des binaires de votre module pour Java ;
  • mon.module-1.0.js  : il s'agit des « binaires » de votre module pour JavaScript ;
  • mon.module-1.0.src : il s'agit des sources de votre module pour les éditeurs de code.

Les fichiers « .sha1 » (prononcé « chawane ») sont des fichiers de contrôle qui assurent l'intégrité des fichiers du même nom sans « .sha1 ».

Désormais si vous souhaitez générer la même chose, suivez les étapes suivantes :

  • créez un nouveau dossier quelque part sur votre système ;
  • créez un sous-répertoire « source » ;
  • copiez les fichiers source du module tutoriel.modules.demo2 dans le répertoire « source » ;
  • ouvrez un interpréteur de commande ;
  • placez-vous dans le répertoire parent de « source » ;
  • exécutez la commande suivante :
Compilation
Sélectionnez
ceylon compile tutoriel.modules.demo2

Si cela ne fonctionne pas, pensez à vérifier que les binaires de Ceylon sont bien dans votre « PATH » et que vous utilisez bien Java en version 7 ou supérieure. Si besoin, vous pouvez spécifier la variable d'environnement JAVA_HOME pour forcer celle à utiliser.

Si tout s'est bien passé, le compilateur vous indique la création du module (avec son nom et sa version). Si vous parcourez l'arborescence, vous découvrirez que le compilateur a créé un répertoire « modules » avec en grande partie les mêmes éléments que vus précédemment.

Mais comment le compilateur a-t-il trouvé le module ceylon.collection ? En réalité, le système modulaire de Ceylon repose sur JBoss ModulesJBoss Modules (une des briques de base de JBoss AS alias Wildfly). Ceylon localise (à la compilation et à l'exécution) les modules dans des dépôts. Il en existe un certain nombre par défaut dont Ceylon HerdCeylon Herd.
Si vous souhaitez utiliser des dépôts supplémentaires, vous devez utiliser l'option --rep path de la ligne de commande. Si vous voulez exclure les dépôts utilisés par défaut : --no-default-repositories. Vous pouvez retrouver toutes les options iciceylon compile.

Pour finir, si vous souhaitez exécuter vous-mêmes vos programmes, il suffit d'exécuter la commande suivante :

Exécution d'un programme Ceylon
Sélectionnez
ceylon run tutoriel.modules.demo2/1.0.0

Néanmoins, rien ne devrait se produire étant donné que nous n'avons pas défini de point d'entrée à notre programme. Pour cela, il suffit de créer une méthode run() à la racine de notre module :

tutoriel/modules/demo2/run.ceylon
Sélectionnez
//Source: tutoriel/a_modules/demo2/run.ceylon
void run() {
  print("Bonjour depuis le module demo2 !");
  Personne auteur = creer("Mauzaize", "Logan");
  print("Mon créateur est ``auteur``");
}

Je vous laisse le soin de recompiler et d'exécuter pour apprécier le résultat.

III. Commentaires

En Ceylon, comme dans beaucoup d'autres langages, pour ajouter des commentaires :

  • jusqu'à la fin de la ligne en préfixant par des doubles barres obliques // ;
  • sur plusieurs lignes en encadrant avec les caractères /* et */.
Commentaires
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
//Source: tutoriel/b_commentaires/commentaire.ceylon

// Commentaire sur une seule ligne
void commentaire() {
 print("Instruction non commentée"); // Commentaire en fin de ligne
 // print("Instruction commentée");
 /* Commentaire
    sur
    plusieurs
    lignes
  */
}

IV. Documentation

Bien sûr, ajouter des commentaires à son code permet d'aider à sa compréhension. Mais hélas, ils ne sont pas très exploitables pour documenter vos modules :

Documentation par annotation
Sélectionnez
1.
2.
3.
4.
5.
6.
//Source: tutoriel/c_documentation/a_annotation.ceylon
doc("Ceci est une fonction documentée")
by("Logan")
see(`function documentation`)
void documentation() {
}

Ainsi doc, by et see sont des annotations qui sont exploitées par le générateur de documentation de Ceylon.

Il est également possible de documenter une déclaration (i.e. une référence, une fonction ou une classe) simplement en la faisant précéder par une chaîne de caractères :

Documentation avec une chaîne
Sélectionnez
1.
2.
3.
4.
//Source: tutoriel/c_documentation/b_chaine.ceylon
"Une autre façon de documenter une déclaration"
void documentation2() {
}

Enfin, il est important de noter que l'on peut formater la documentation à l'aide de la syntaxe MarkdownMarkdown :

Documentation avec Markdown
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
//Source: tutoriel/c_documentation/c_markdown.ceylon
"Ceci est une méthode écrite en [Ceylon][] et documentée avec la syntaxe [Markdown][].

 - - -
 Vous *pourrez* trouver énormément d'informations en lisant la documentation de [Markdown][].

 [Ceylon]: http://ceylon-lang.org/
 [Markdown]: http://daringfireball.net/projects/markdown/syntax"
void documentation3() {
}

V. Littéraux

Lorsque l'on démarre avec un langage, on commence souvent par les « littéraux ». Ce sont les valeurs que l'on écrit dans le code source (ex : 12, 3.14, 'a'), car il faut bien commencer par une donnée.

V-A. Booléen

Les booléens sont les types les plus simples que l'on puisse trouver dans un langage. Dans Ceylon, ils sont représentés par le type Boolean et les valeurs true et false. Voici un exemple de booléens :

Booléen
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
//Source: tutoriel/d_litteraux/a_booleen.ceylon
Boolean vrai = true;
Boolean faux = false;

Boolean ou = true || false;
Boolean et = true && false;
Boolean non = !true;

Attention !
En réalité, truetrue et falsefalse ne sont pas des littéraux mais les deux seules instances de la classe Boolean.

V-B. Nombres

Les nombres sont divisés en deux catégories : les nombres entiers du type IntegerInteger et les nombres décimaux du type FloatFloat. Le code suivant permet de déclarer un entier qui a la valeur 42 et un décimal qui a la valeur 3,14 :

Nombres
Sélectionnez
1.
2.
3.
//Source: tutoriel/d_litteraux/b_nombres/a_nombres.ceylon
Integer entier = 42;
Float pi = 3.14;

Vous pouvez bien sûr réaliser différentes opérations sur les nombres :

Opérations sur les nombres
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
//Source: tutoriel/d_litteraux/b_nombres/b_operationsEntieres.ceylon
void operationsEntieres() {
  print(1 + 1);   // Addition
  print(5 - 3);   // Soustraction
  print(2 * 3);   // Multiplication
  print(4 / 2 );  // Division entière
  print(5 / 2 );  //
  print(5 % 2 );  // Modulo (Reste de la division entière)
  print(2 ^ 3 );  // Élévation à la puissance
}

Comme vous l'aurez remarqué, les opérandes étant entiers, l'opération est dite « entière ». Ainsi 5 ÷ 2 donne 2 et non 2,5. À partir du moment où un opérande est décimal, alors Ceylon utilise les opérations « décimales » :

Opérations sur les décimaux
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
//Source: tutoriel/d_litteraux/b_nombres/c_operationsDecimales.ceylon
void operationsDecimales() {
  print(1.0 + 1);   // Addition
  print(5 - 3.0);   // Soustraction
  print(2.0 * 3);   // Multiplication
  print(4 / 2.0 );  // Division
  print(5.0 / 2 );  //
  print(2 ^ 3.0 );  // Élévation à la puissance
  print(2.0 ^ 3 );  //
}

On remarquera que l'opération « Modulo » n'est pas disponible pour les décimaux.

V-C. Chaînes de caractères

Les chaînes de caractères sont sûrement les types que l'on manipule le plus dans un programme et chaque langage a ses petites spécificités. Ici, on ne parlera que des littéraux. Une chaîne de caractères est du type String et se déclare simplement entre guillemets (").

Déclaration d'une chaîne de caractères
Sélectionnez
1.
2.
//Source: tutoriel/d_litteraux/c_chaines/a_declaration.ceylon
String chaine = "Bonjour !";

Contrairement à beaucoup d'autres langages, il est possible d'écrire une chaîne sur plusieurs lignes :

Chaîne de caractères sur plusieurs lignes
Sélectionnez
1.
2.
3.
//Source: tutoriel/d_litteraux/c_chaines/b_multiligne.ceylon
String multiligne = "Première ligne
                     deuxième ligne";

Chaque nouvelle ligne d'une chaîne multiligne doit être au minimum alignée avec la première ligne. Dans le cas contraire, vous aurez une erreur de compilation pour vous le rappeler.
Cela permet d'avoir des chaînes bien lisibles !

Chaîne multi-ligne bien lisible
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
//Source: tutoriel/d_litteraux/c_chaines/c_multiligneBienLisible.ceylon
String multiligneBienLisible = "Racine
                                |- A
                                |  |- A.1
                                |  |- A.2
                                |- B
                                |  |- B.1";

Mais comment écrire une chaîne de caractères qui contient un guillemet ? Pour cela, il faut utiliser une des séquences d'échappements :

  1. Le caractère d'échappement \ : \", \\, \n, etc. ;
  2. La valeur Unicode : \{#0022} ;
  3. Le nom Unicode : \{QUOTATION MARK}.

Ces deux dernières constructions permettent ainsi de traiter les caractères ésotériques non supportés par votre encodage tel que \{AIRPLANE}.

Vous pouvez trouver la liste complète des noms de caractères Unicode depuis cette liste : http://www.unicode.org/Public/UNIDATA/NamesList.txt

Une dernière possibilité pour écrire une chaîne qui contiendrait des guillemets est d'utiliser des chaînes « verbatim », que l'on traduit par « textuellement ». Comprenez que la chaîne sera interprétée telle quelle. Pour déclarer une chaîne « verbatim », il suffit de la délimiter par trois guillemets :

Chaîne "verbatim"
Sélectionnez
1.
2.
//Source: tutoriel/d_litteraux/c_chaines/e_verbatim.ceylon
String chaineTelleQuelle = """Bonjour, "Jean-Pierre"""";

Pour finir, on a souvent besoin de créer une chaîne de caractères à partir d'une (ou plusieurs) variable(s). En Ceylon, nous allons utiliser des patrons de chaîne :

Patron de chaîne
Sélectionnez
1.
2.
3.
//Source: tutoriel/d_litteraux/c_chaines/f_patron.ceylon
String nom = "Ceylon";
String patron = "Ceci est un programme écrit en ``nom``";

Comme dans de nombreux langages de programmation, il est également possible d'utiliser la concaténation :

Concaténation de chaîne
Sélectionnez
1.
2.
3.
//Source: tutoriel/d_litteraux/c_chaines/g_concatenation.ceylon
String nom = "Ceylon";
String patron = "Ceci est un programme écrit en " + nom;

Cependant, il faut noter une grande différence entre les deux écritures. Les patrons ne tiennent pas compte du type des variables alors que la concaténation ne fonctionne qu'avec des chaînes de caractères. Vous devrez donc convertir les variables en chaînes de caractères avant de les concaténer. Ce qui nous donne :

Patron contre concaténation
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
//Source: tutoriel/d_litteraux/c_chaines/h_patron_vs_concatenation.ceylon
String nom = "Ceylon";
String prenom = "Logan";
Integer age = 28;
Integer versionJava = 7;
Integer annee = 2013;

String concatenation2 = "Bonjour, je m'appelle " + prenom + " et j'ai " + age.string + ". Depuis " + annee.string + ", je pratique le langage " + nom + " qui nécessite Java " + versionJava.string + " au minimum";
String patron2        = "Bonjour, je m'appelle ``prenom`` et j'ai ``age``. Depuis ``annee``, je pratique le langage ``nom`` qui nécessite Java ``versionJava`` au minimum";

On notera que les patrons sont beaucoup plus lisibles, surtout si vous devez utiliser des chaînes multilignes.

V-D. Valeurs et variables

Comme je l'avais évoqué précédemment, Ceylon suppose que les choses sont immuables. Il distingue donc les variables et les valeurs. Par défaut lorsque vous déclarez une donnée, c'est une valeur (ou constante) et il n'est pas autorisé que vous la changiez :

valeur
Sélectionnez
1.
2.
3.
4.
5.
//Source: tutoriel/d_litteraux/d_valeur_variable/a_valeur.ceylon
void declarationValeur() {
  Integer valeur = 1;
  valeur = 2; // Erreur
}

Une valeur peut également être initialisée plus tard, mais elle ne pourra l'être qu'une seule fois :

initialisation tardive
Sélectionnez
1.
2.
3.
4.
5.
6.
//Source: tutoriel/d_litteraux/d_valeur_variable/b_initialisationTardive.ceylon
void declarationValeur2() {
  Integer valeur;
  valeur = 2; // Ok
  valeur = 3; // Erreur
}

Pour qu'une donnée puisse varier, il faut « annoter » (c'est-à-dire ajouter une précision) avec le terme variable :

variable
Sélectionnez
1.
2.
3.
4.
5.
//Source: tutoriel/d_litteraux/d_valeur_variable/c_variable.ceylon
void declarationVariable() {
  variable Integer var = 1;
  var = 2;
}

Dans Ceylon, de nombreux « mots clés » n'en sont en fait pas, mais sont des annotations. C'est le cas de variable mais aussi de shared, abstract, etc. que nous verrons plus tard.

VI. Structures de contrôles

VI-A. if-else

L'instruction if permet de conditionner l'exécution d'un bloc d'instructions :

if
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
//Source: tutoriel/e_structuresDeControles/a_if.ceylon
void demoIf() {
  Integer a = 4;
  Integer b = 2;

  if (b != 0) {    // Si b est différent de 0, on peut faire la division
    print("a/b=``a / b``");
  }
}

Et else (qui est nécessairement associé à un if) permet de définir une alternative. On peut également les enchaîner :

if-else
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
//Source: tutoriel/e_structuresDeControles/b_if_else.ceylon
void demoIfElse() {
  Integer a = 4;
  Integer b = 2;
  
  if (a == b) {
    print("a et b sont égaux");
  } else if (a > b) {
    print("a est plus grand que b");
  } else {
    print("b est plus grand que a");
  }
}

Attention, contrairement à de nombreux langages, les accolades sont obligatoires ; il n'y a pas de bloc implicite. Et cela est vrai pour toutes les structures de contrôles.

VI-B. then-else

Contrairement au couple if-else, les couples then-else ne reposent pas sur des blocs, mais des valeurs. Il n'y a donc pas d'accolades et on ne peut pas exécuter d'instruction (sauf appeler une méthode). Ainsi ils permettent moins de choses, mais sont plus concis :

then-else
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
//Source: tutoriel/e_structuresDeControles/c_then_else.ceylon
void demoThenElse() {
  Integer a = 4;
  Integer b = 2;
  
  String message = (a == b then "a et b sont égaux")
              else (a >  b then "a est plus grand que b")
              else              "b est plus grand que a";

  print(message);
}

VI-C. switch-case

L'instruction « switch » ressemble à ce que l'on peut trouver dans les autres langages :

switch
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
//Source: tutoriel/e_structuresDeControles/d_switch.ceylon
void demoSwitch() {
  Integer a = 0;
  
  switch (a)
    case (10) { print("10"); }
    else { print("Autre chose"); }
}

Les particularités sont :

  1. L'absence d'accolade pour le switch. Le bloc est délimité par les case successifs, optionnellement terminés par un else ;
  2. Les tests de chaque case doivent se trouver entre parenthèses ;
  3. Chaque case dispose de son propre bloc (comme pour le if, les accolades sont obligatoires). Il n'est donc pas possible d'enchaîner des instructions de deux cas différents comme on pourrait le faire en omettant le break dans d'autres langages ;
  4. Dans les autres langages, on utilise généralement le mot clé default pour désigner le cas qui ne correspond à aucun cas énuméré précédemment. Pour rester logique, Ceylon utilise le mot clé else comme pour toutes les autres alternatives ;
  5. Ceylon vous oblige à gérer tous les cas, ce qui permet d'identifier rapidement les instructions switch lorsque vous ajoutez un nouveau membre à une énumération ;
  6. L'instruction s'utilise avec des expressions de type Integer, Character, String ou les types énumérés (que nous présenterons lors d'un prochain article).

VI-D. assert

Une assertion se présente comme un if excepté qu'il n'est pas associé à un bloc d'instructions et lance une exception si au moins une des conditions n'est pas vérifiée :

Assert
Sélectionnez
//Source: tutoriel/e_structuresDeControles/e_assert.ceylon
void demoAssert() {
  Boolean estValide = false;
  assert (estValide);
  print("Ok, tout va bien");
}
Assert (sortie console)
Sélectionnez
ceylon run: Assertion failed
 violated estValide

Il est également possible de définir un message en utilisant une annotation doc sur l'assertion :

Assertion documentée
Sélectionnez
//Source: tutoriel/e_structuresDeControles/f_assertAvecMessage.ceylon
void demoAssertAvecMessage() {
  Boolean estValide = false;
  "Non cela ne va pas bien"
  assert (estValide);
  print("Ok, tout va bien");
}
Assertion documentée (sortie console)
Sélectionnez
ceylon run: Assertion failed: Non cela ne va pas bien
 violated estValide

Contrairement à Java, il n'est pas possible de désactiver les assertions.

VI-E. while

L'instruction while permet de répéter un bloc de code tant qu'une condition est vérifiée :

while
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
//Source: tutoriel/e_structuresDeControles/g_while.ceylon
void demoWhile() {
  variable Integer a = 0;
  while (a < 10) {
    print(a);
    a = a + 1;
  }
}

Contrairement à ce que l'on peut trouver dans les langages inspirés du C, il n'existe pas d'instructions « do-while ».

VI-F. for

Les boucles for permettent l'itération sur des « séquences » d'éléments :

for
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
//Source: tutoriel/e_structuresDeControles/h_for.ceylon
void demoForEach() {
  Integer[] sequence = [ 0, 1, 2, 3 ];
  for (Integer i in sequence) {
    print(i);
  }
}

La boucle for ne permet pas le triptyque « déclaration-condition-incrémentation ». Exemple :

for en Java
Sélectionnez
1.
2.
3.
for (int i = 0; i <= 3; i++) {
  System.out.println(i);
}

À la place, Ceylon utilise des séquences spéciales appelées « plage de valeurs » (« spanned range ») :

for avec plage de valeurs
Sélectionnez
1.
2.
3.
4.
5.
6.
//Source: tutoriel/e_structuresDeControles/i_for_plage_valeurs.ceylon
void demoFor0a3() {
  for (Integer i in 0..3) {
    print(i);
  }
}

Il est également possible de définir une plage inversée :

for avec une plage décrémentale
Sélectionnez
//Source: tutoriel/e_structuresDeControles/j_for_plage_decrementale.ceylon
void demoFor3a0() {
  for (Integer i in 3..0) {
    print(i);
  }
}

Alternativement, il est possible de définir une « plage en longueur » (« segmented range »), c'est-à-dire, avec un élément de départ et une longueur de séquence :

for avec plage en longueur
Sélectionnez
1.
2.
3.
4.
5.
6.
//Source: tutoriel/e_structuresDeControles/k_for_plage_longueur.ceylon
void demoForDe0Sur4() {
  for (Integer i in 0:4) {
    print(i);
  }
}

À noter qu'il n'est pas possible de définir de plage en longueur de manière décrémentale.

Un dernier point important est la possibilité d'exécuter un bloc de code en cas de sortie « normale » de la boucle (ni return, ni break) en utilisant l'instruction else :

for-else
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
//Source: tutoriel/e_structuresDeControles/l_for_else.ceylon
void demoForElse() {
  Integer cherche = 2;
  Boolean trouve;
  for (Integer i in 0..1) {
    if (cherche == i) {
      trouve = true;
      break;
    }
  } else {
    trouve = false;
  }
  print(trouve);
}

VI-G. try-catch-finally

La gestion des exceptions s'effectue avec l'habituel triplet try-catch-finally :

try-catch-finally
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
//Source: tutoriel/e_structuresDeControles/m_try_catch_finally.ceylon
void demoTryCatchFinally() {
  try {
    assert(1==0);
  } catch (AssertionException|NegativeNumberException e) {
    print(e.message);
  } finally {
    print("finally !");
  }
}

On remarquera que l'on peut utiliser une syntaxe similaire à celle du « multi-catch » apparue en Java 7. Il est également possible d'utiliser la syntaxe « try-with-resource » :

try-with-resource
Sélectionnez
1.
2.
3.
4.
//Source: tutoriel/e_structuresDeControles/n_try_with_resource.ceylon
try (File.Reader reader = file.Reader()) {
  // ...
}

Enfin pour capturer toutes les exceptions JavaScript ou les sous-classes de java.lang.Exception, il faudra utiliser l'exception ceylon.language.Exception. Cependant, il n'est pas possible de capturer les autres java.lang.Throwable (et notamment les java.lang.Error).

Dans ce cas, il est possible d'omettre le type de l'exception dans la clause catch :

catch avec type implicite
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
//Source: tutoriel/e_structuresDeControles/o_try_catch_type_implicite.ceylon
void demoTryCatchAll() {
  try {
    // Code à risque
  } catch (e) {
    e.printStackTrace();
  }
}

Vous remarquerez que, dans les codes donnés en exemple, les exceptions capturées ne sont pas nécessairement levées par le bloc protégé.
Contrairement à Java, Ceylon n'a pas de notion de « checked-exception ». Il n'y a aucune obligation de capturer ou de déclarer une exception qui pourrait se produire.
En revanche, je vous conseille fortement de documenter les exceptions levées à l'aide de l'annotation throws.

VI-H. Liste de conditions

Toutes les instructions qui reposent sur des conditions (if, assert, while) n'acceptent en réalité pas une unique condition mais une liste :

Liste de conditions
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
//Source: tutoriel/e_structuresDeControles/p_liste_conditions.ceylon
void demoListeDeConditions() {
  Integer a = 0;
  Integer b = 1;
  if (a == 0, b == 1) {
    print("ok !");
  } else {
    print("Tant pis ...");
  }
}

Chaque condition de la liste doit être vérifiée, comme si les conditions étaient connectées par un « et » logique. La différence avec une conjonction (enchaînement de « et » logiques), c'est que l'on peut utiliser la « conversion structurée de type » (« structured typecasting »).

Ce terme « barbare » désigne simplement le fait que si vous testez le type ou la non-nullité d'une référence, Ceylon prendra en compte le résultat de la conversion dans les instructions suivantes (y compris les conditions suivantes dans la liste). Voici un exemple :

Liste de conditions et conversion structuré
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
//Source: tutoriel/e_structuresDeControles/q_liste_conditions_conversion_structure.ceylon
void demoListeDeConditionsEtConversionStructure() {
  Number a = 12;
  if (is Integer a, a.remainder(5) == 2) {
    print("ok !");
  } else {
    print("désolé ...");
  }
}

VII. Types

VII-A. Classe

En Ceylon, comme en Java, les classes se déclarent à l'aide du mot clé class :

Déclaration d'une classe
Sélectionnez
//Source: tutoriel/f_types/a_classe/a_declaration.ceylon
class MaPremiereClasse() {}
MaPremiereClasse uneVariable = MaPremiereClasse();

La première chose qui saute aux yeux, c'est l'absence de mot clé new. Pour instancier un objet, il suffit d'appeler une classe comme s'il s'agissait d'une fonction. La seconde chose qui frappe est la présence de parenthèses après le nom de classe. En Ceylon, il n'y a pas de « constructeur ». Tout ce que vous allez spécifier dans le corps de votre classe va être utilisé pour construire les instances :

Constructeur
Sélectionnez
//Source: tutoriel/f_types/a_classe/b_constructeur.ceylon
String chaine = "Test";
class Constructeur() {
  String monPremierAttribut;
  if (chaine.size > 2) {
    monPremierAttribut = chaine;
  } else {
    monPremierAttribut = "Trop court";
  }
}

Du fait que le corps de votre constructeur va mélanger des instructions et des déclarations, je vous conseille fortement de regrouper l'initialisation de vos variables par groupes et de bien séparer la déclaration de vos méthodes. N'hésitez pas à utiliser les commentaires pour délimiter graphiquement vos groupes.

Bien entendu, il est possible de passer des paramètres. Mais en plus, on pourra les utiliser comme des attributs ; cela correspond exactement au comportement des fermetures (closure) :

Construction avec paramètre
Sélectionnez
//Source: tutoriel/f_types/a_classe/c_constructeur_parametre.ceylon
class ConstructeurAvecParametre(shared String unAttribut) {
  shared void afficher() {
    print(unAttribut);
  }
}
void testerConstructeurAvecParametre() {
  ConstructeurAvecParametre a = ConstructeurAvecParametre("Un attribut");
  print(a.unAttribut);
  a.afficher();
}

En Ceylon, il n'est pas utile de créer des méthodes getXXX/setXXX. En effet, il est possible de déclarer des accesseurs (getter) et des mutateurs (setter) qui pourront être utilisés comme des attributs. Commençons par le getter, il suffit de le déclarer comme un attribut sauf que l'on va y associer un bloc de code. Pour le setter, c'est un peu la même chose sauf que l'on remplace le type et les annotations par le mot clé assign et l'on accède à la valeur à définir en utilisant simplement le nom de la « propriété ». Voici un exemple :

Accesseur et mutateur
Sélectionnez
//Source: tutoriel/f_types/a_classe/d_accesseur_mutateur.ceylon
class Personne(shared variable String prenom, shared variable String nomFamille) {
  shared String nom { return prenom + " " + nomFamille; }
  assign nom {
    value prenomEtNom = nom.split().iterator();
    if (is String p = prenomEtNom.next()) {
      prenom = p;
    }
    if (is String n = prenomEtNom.next()) {
      nomFamille = n;
    }
  }
}

Maintenant que l'on a vu comment créer nos premières classes, nous allons voir comment créer une hiérarchie. Comme Java, Ceylon ne supporte pas l'héritage multiple, mais permet de satisfaire plusieurs interfaces (qui seront abordées dans la section suivante) :

Héritage
Sélectionnez
//Source: tutoriel/f_types/a_classe/e_heritage.ceylon
class Mere() {}
class Fille() extends Mere() {}

Il est bien sûr possible de déclarer une classe abstraite en utilisant l'annotation abstract. Pour les méthodes, ce sera avec l'annotation formal (théorique). Lorsque les méthodes seront définies dans une classe descendante, il faudra l'annoter avec actual (concret) ; c'est la même idée que l'annotation Override de Java ou le mot clé override de C#.

Abstraction
Sélectionnez
//Source: tutoriel/f_types/a_classe/f_abstraction.ceylon
abstract class Abstraite() {
  shared formal void uneMethode();
}
class Concrete() extends Abstraite() {
  shared actual void uneMethode() {
    print("Méthode concrète !");
  }
}

Précédemment, nous avons vu que, par défaut, les éléments sont immuables. Ainsi, une méthode ne peut pas être redéfinie. Pour rendre cela possible, il faut annoter la méthode avec default. La méthode redéfinie devra encore une fois être annotée avec actual :

Redéfinition de méthode
Sélectionnez
//Source: tutoriel/f_types/a_classe/g_redefinition.ceylon
class DefinirMethode() {
  shared default void uneMethode() {
    print("DefinirMethode");
  }
}
class RedefinirMethode() extends DefinirMethode() {
  shared actual void uneMethode() {
    print("RedefinirMethode");
  }
}

Il est également possible d'utiliser une construction raccourcie pour définir ou redéfinir des méthodes/accesseurs/mutateurs d'une seule instruction. Pour cela, il faut utiliser => à la place des accolades. Dans ce cas, il n'est pas possible de déclarer d'annotation ou de raffiner le type de retour :

Redéfinition rapide d'une méthode
Sélectionnez
//Source: tutoriel/f_types/a_classe/h_redefinition_rapide.ceylon
class RedefinirRapidementUneMethode() extends DefinirMethode() {
  uneMethode() => print("RedefinirRapidementUneMethode");
}

Un point que nous n'avons pas encore abordé : les classes membres. C'est l'encapsulation d'un type dans un autre ou « nested class » en Java. Une des améliorations qu'apporte Ceylon, c'est la possibilité de raffiner ces classes dans les classes filles. L'intérêt alors, c'est que l'on ne crée pas de nouvelles classes et du point de vue de l'utilisateur de la classe, cela reste abstrait : il se contente de créer une instance de la classe sans se soucier de la classe réelle :

Classe membre (Code source)
Sélectionnez
//Source: tutoriel/f_types/a_classe/i_classe_membre.ceylon
"Abstraction d'une ressource : fichier, url, etc."
abstract class Ressource() {
  "Permet de lire le contenu de la ressource"
  shared formal class Lecteur() {
    shared formal String lire();
  }
}
"Représente un contenu sur disque"
class Fichier(shared String chemin) extends Ressource() {
  "Gestionnaire de contenu spécifique au fichier"
  shared actual class Lecteur() extends super.Lecteur() {
    shared actual String lire() => "Contenu d'un fichier";
  }
}
"Représente un contenu en mémoire"
class ZoneMemoire() extends Ressource() {
  "Gestionnaire de contenu spécifique au contenu en mémoire"
  shared actual class Lecteur() extends super.Lecteur() {
    shared actual String lire() => "Contenu d'une zone mémoire";
  }
}
void demoRessource() {
  Ressource fichier = Fichier("/home/ceylon/fichier.txt");
  Ressource memoire = ZoneMemoire();

  print("Fichier : " + fichier.Lecteur().lire());
  print("Memoire : " + memoire.Lecteur().lire());
}
Classe membre (Sortie console)
Sélectionnez
Fichier : Contenu d'un fichier
Memoire : Contenu d'une zone mémoire

VII-B. Interface

Contrairement à Java (inférieur à 8), les interfaces de Ceylon peuvent contenir des méthodes concrètes mais aussi des accesseurs et des mutateurs ! Il y a cependant deux limitations : une interface ne peut pas définir d'attributs ou de constructeur.
Les interfaces se déclarent avec le mot-clé interface. Pour raffiner une interface, qu'il s'agisse d'une classe ou d'une autre interface, il faut utiliser le mot clé satisfies (satisfaire). S'il y a en plusieurs, il faudra les séparer par un & :

Interfaces
Sélectionnez
//Source: tutoriel/f_types/b_interface/a_interfaces.ceylon
interface Vehicule {
  shared variable formal String immatriculation;
}

interface Deplacable {
  shared variable formal Integer position;
  
  shared default void deplacer(Integer vitesse) {
    position += vitesse;
  }
}

class Voiture("Implémentation par un attribut" shared variable actual String immatriculation) satisfies Vehicule & Deplacable {
  "Implémentation par un accesseur et un mutateur"
  shared actual Integer position => 0;
  assign position {
  }
}

Si l'utilisation des mixin permet enfin d'hériter de comportements définis de manière générique, elle soulève toujours le problème de l'ambiguïté des noms. Si le cas se présente, le compilateur Ceylon vous avertira par une erreur précisant que votre classe ne peut pas hériter de deux déclarations qui n'ont pas un type parent en commun. Le compilateur vous indiquera également le nom de la déclaration et les interfaces concernées.

Code ambigu
Sélectionnez
//Source: tutoriel/f_types/b_interface/b_ambigue.ceylon
interface A {
  shared formal String nom;
}
interface B {
  shared formal String nom;
}
class AB() satisfies A&B {
  shared actual String nom = "toto";
}
Code non ambigu
Sélectionnez
//Source: tutoriel/f_types/b_interface/c_non_ambigue.ceylon
interface C {
  shared formal String foobar;
}
interface D satisfies C {
  shared actual default String foobar => "D";
}
interface E satisfies C {
  shared actual default String foobar => "E";
}
class CDE() satisfies D&E {
  shared actual String foobar => (super of D).foobar + (super of E).foobar;
}

On remarquera l'utilisation de la construction (super of) qui permet d'appeler le code défini dans un type parent.

VII-C. Objet

En Ceylon, pour déclarer anonymement des classes, on utilise le mot clé object. Si l'objet est défini au niveau du package, il formera un singleton. Dans un autre contexte, une classe ou une méthode (y compris un accesseur ou un mutateur), un nouvel objet sera créé respectivement à chaque instanciation ou appel.

Objet
Sélectionnez
//Source: tutoriel/f_types/c_objet/a_objet.ceylon
interface UneClasseAConstruire {
  shared formal void executer();
}

interface MaFabrique {
  shared formal UneClasseAConstruire construire(String s);
}

object createur satisfies MaFabrique {
  shared actual UneClasseAConstruire construire(String s) {
    object construction satisfies UneClasseAConstruire {
      shared actual void executer() {
        print("Une construction(``s``)");
      }
    }
    return construction;
  }
}

void demoCreateur() {
  UneClasseAConstruire a = createur.construire("a");
  UneClasseAConstruire b = createur.construire("b");
  a.executer();
  b.executer();
}

Dans le code ci-dessus, on remarque que l'objet construction a « capturé » le paramètre s de son contexte, comme une fermeture (closure).

Les objets peuvent également être utilisés pour raffiner un attribut :

Attribut raffiné avec un objet
Sélectionnez
//Source: tutoriel/f_types/c_objet/b_attribut_raffine.ceylon
interface Met { /* ... */ }
abstract class Repas() {
  shared formal Met entree;
  shared formal Met plat;
  shared formal Met dessert;
}
class MonRepas() extends Repas() {
  shared actual object entree satisfies Met { /* ... */ }
  shared actual object plat satisfies Met { /* ... */ }
  shared actual object dessert satisfies Met { /* ... */ }
}

VIII. Conclusion

Ceci conclut notre tour des principes de base du langage. Désormais, vous devriez avoir une bonne idée de comment coder une application telle que vous le feriez avec votre langage habituel (ex : Java). La partie suivante va se consacrer à ce qui fait l'une des forces de Ceylon et donc ce qui justifierait de l'employer dans vos projets : son système de typage (« Type system »).

IX. Remerciements

Je remercie toute l'équipe Ceylon pour la réalisation de ce nouveau langage, ainsi que pour leur disponibilité et leur patience pour répondre aux remarques et questions qu'on leur soumet.

Je remercie également Mickael Baron, Yann Caron alias CyaNnOrangehead, Thierry Leriche-Dessirier alias thierryler, Cédric Duprez alias ced et Jacques Théry alias jacques_jean pour leur relecture attentive, leurs remarques et leurs bons conseils.

Je tiens aussi à remercier la communauté Developpez.com qui a mis en place tous les outils, les procédures et l'hébergement nécessaires à la publication de cet article.

Enfin mon épouse et mes enfants pour leur patience et leur tolérance durant les nombreuses heures qui ont été nécessaires à la rédaction de cet article.

X. Annexes

X-A. Sources des exemples

Tous les exemples donnés dans cet article sont disponibles sous GitHub dans le répertoire src-02-bases.

X-B. Importer le projet sous Eclipse depuis Git

Pour importer le projet sous Eclipse, ouvrez la perspective « Git Repository Exploring » :

Image non disponible

Copiez l'URL https://github.com/loganmzz/ceylon-articles.git, assurez-vous que la vue active est « Git Repositories » (en cliquant sur l'onglet, par exemple) :

Image non disponible

Appuyez simplement sur la combinaison de touche « CTRL+V » pour faire apparaître la fenêtre « Clone Git Repository » :

Image non disponible

Après avoir appuyé sur « Next > », la fenêtre de sélection des branches apparaît. Sélectionnez uniquement « officiel » :

Image non disponible

Appuyez sur « Next > » pour faire apparaître la fenêtre de configuration du stockage local. Adaptez le chemin selon vos préférences, puis vérifiez que la branche est bien « officiel » et activez l'import des projets existants :

Image non disponible

Il n'y a plus qu'à terminer en cliquant sur « Finish ». Le dépôt est cloné, puis le projet « ceylon-articles » est importé dans l'espace de travail :

Image non disponible

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 Logan Mauzaize. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.