I. Avant-propos▲
Cet article est le deuxième d'une série articulée sur la présentation du langage Ceylon :
- Présentation et installation ;
- Concepts de base ;
- Typage ;
- Appels et arguments ;
- Collections ;
- Modules ;
- 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 :
2.
3.
4.
//Source: tutoriel/a_modules/exemples/fonctionDupliquee1.ceylon
void
fonctionDupliquee() {
// Fonction dupliquée
}
2.
3.
4.
//Source: tutoriel/a_modules/exemples/fonctionDupliquee2.ceylon
void
fonctionDupliquee() {
// Fonction dupliquée
}
Et le résultat :
Duplicate declaration error: fonctionDupliquee is declared twice
Les déclarations comprennent :
- Les références que l'on pourrait appeler variable ou attribut dans d'autres langages ;
- Les fonctions qui contrairement aux méthodes n'appartiennent pas à une classe, mais à un « package » ;
- 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 dispose 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 :
//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 :
//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) :
//Source: tutoriel/a_modules/demo2/module.ceylon
module tutoriel.modules.demo2 "1.0.0"
{
import
ceylon.collection "1.0.0"
;
}
//Source: tutoriel/a_modules/demo2/package.ceylon
shared
package
tutoriel.modules.demo2;
//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);
}
//Source: tutoriel/a_modules/demo2/impl/package.ceylon
shared
package
tutoriel.modules.demo2.impl;
//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 :
//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 :
//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 :
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 :
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 :
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 :
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 :
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 :
//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 */.
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 :
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 chaine de caractères :
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 :
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 :
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 :
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 :
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 » :
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. Chaines de caractères▲
Les chaines 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 chaine de caractères est du type String et se déclare simplement entre guillemets (").
2.
//Source: tutoriel/d_litteraux/c_chaines/a_declaration.ceylon
String
chaine = "Bonjour !"
;
Contrairement à beaucoup d'autres langages, il est possible d'écrire une chaine sur plusieurs lignes :
2.
3.
//Source: tutoriel/d_litteraux/c_chaines/b_multiligne.ceylon
String
multiligne = "Première ligne
deuxième ligne"
;
Chaque nouvelle ligne d'une chaine 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 chaines bien lisibles !
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 chaine de caractères qui contient un guillemet ? Pour cela, il faut utiliser une des séquences d'échappements :
- Le caractère d'échappement \ : \", \\, \n, etc. ;
- La valeur Unicode : \{#0022} ;
- 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 chaine qui contiendrait des guillemets est d'utiliser des chaines « verbatim », que l'on traduit par « textuellement ». Comprenez que la chaine sera interprétée telle quelle. Pour déclarer une chaine « verbatim », il suffit de la délimiter par trois guillemets :
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 chaine de caractères à partir d'une (ou plusieurs) variable(s). En Ceylon, nous allons utiliser des patrons de chaine :
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 :
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 chaines de caractères. Vous devrez donc convertir les variables en chaines de caractères avant de les concaténer. Ce qui nous donne :
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 chaines 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 :
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 :
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 :
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 « mot-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 :
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 :
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 :
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 :
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 :
- L'absence d'accolade pour le switch. Le bloc est délimité par les case successifs, optionnellement terminés par un else ;
- Les tests de chaque case doivent se trouver entre parenthèses ;
- 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 ;
- 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 ;
- 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 ;
- 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 :
//Source: tutoriel/e_structuresDeControles/e_assert.ceylon
void
demoAssert() {
Boolean
estValide = false;
assert
(estValide);
print("Ok, tout va bien"
);
}
ceylon run: Assertion failed
violated estValide
Il est également possible de définir un message en utilisant une annotation doc sur l'assertion :
//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"
);
}
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 :
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 :
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 :
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 ») :
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 :
//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 :
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 :
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 :
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 « multicatch » apparue en Java 7. Il est également possible d'utiliser la syntaxe « try-with-resource » :
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 :
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 :
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 :
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 :
//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 :
//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) :
//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 :
//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) :
//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#.
//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 :
//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 :
//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 :
//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());
}
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 & :
//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.
//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"
;
}
//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.
//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 :
//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 » :
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) :
Appuyez simplement sur la combinaison de touche « CTRL+V » pour faire apparaître la fenêtre « Clone Git Repository » :
Après avoir appuyé sur « Next > », la fenêtre de sélection des branches apparaît. Sélectionnez uniquement « officiel » :
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 :
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 :