I. Avant-propos▲
Cet article est le quatriè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 se focalise sur la gestion des appels et des arguments, ce qui inclut également la façon dont Ceylon gère les opérateurs.
II. Paramètres▲
Avant de commencer, il est important de noter que Ceylon ne supporte pas la surcharge de fonctions. En effet, le nom doit être unique, quels que soient le nombre et le type des arguments :
//Source: tutoriel/a_parametres/a_surcharge.ceylon
void
tentativeDeSurcharge() {} // Ok
void
tentativeDeSurcharge(String
foobar) {} // Erreur
class
ClasseAvecTentativeDeSurCharge
() {
void
tentativeDeSurcharge() {} // Ok
void
tentativeDeSurcharge(String
foobar) {} // Erreur
}
Pour composer avec cette limitation, vous pouvez user de différents mécanismes. Le premier de ces mécanismes, vous le connaissez déjà, il s'agit des unions de types. En effet, il est commun dans une API d'offrir la surcharge pour faciliter les appels et de gérer en interne la conversion de type :
//Source: tutoriel/a_parametres/b_union.ceylon
void
surchargeParUnion(String
|Integer
parametre) {
String
chaine;
if
(is
String
parametre) {
chaine = parametre;
} else
{
chaine = parametre.string;
}
print("le parametre est "
+ chaine);
}
Une autre problématique commune est de rendre des paramètres optionnels. Ceylon permet d'affecter une valeur par défaut à un paramètre, à ne pas confondre avec le ? :
//Source: tutoriel/a_parametres/c_defaut.ceylon
void
parametreParDefaut(String
foobar = ""
) {}
void
parametreNullable(String
? foobar ) {}
void
demoParametreOptionel() {
parametreParDefaut("foobar"
);
parametreParDefaut();
parametreNullable("foobar"
);
parametreNullable(); // Erreur
}
Il peut y avoir plusieurs paramètres par défaut. Cependant, ils doivent tous se situer après les paramètres obligatoires.
Ceylon supporte les fonctions variadiques, c'est-à-dire des fonctions qui acceptent un nombre variable d'arguments. Pour cela le type doit être suivi de *, si la liste peut être vide, ou + dans le cas contraire :
//Source: tutoriel/a_parametres/d_variadique.ceylon
void
variadiqueOptionnel(String
* chaines) {
print("Variadique optionnel"
);
printAll(chaines);
}
void
variadiqueObligatoire(String
+ chaines) {
print("Variadique obligatoire"
);
printAll(chaines);
}
void
demoVariadique() {
variadiqueOptionnel();
variadiqueOptionnel("foo"
, "bar"
);
variadiqueObligatoire(); // Erreur
variadiqueObligatoire("foo"
, "bar"
);
}
Il ne peut y avoir qu'un seul paramètre variadique et il doit être absolument le dernier. Il est possible de combiner avec les paramètres par défaut à condition donc qu'ils soient placés avant et que le variadique puisse être vide :
//Source: tutoriel/a_parametres/e_defaut_et_variadique.ceylon
void
defautVariadique1(String
defaut = ""
, String
* variadique ) {} // Ok
void
defautVariadique2(String
defaut = ""
, String
+ variadique ) {} // Erreur
void
defautVariadique3(String
* variadique , String
defaut = ""
) {} // Erreur
Enfin il est possible de passer en paramètre une liste comme paramètre variadique, en utilisant l'opérateur d'expansion :
//Source: tutoriel/a_parametres/f_variadique_et_expansion.ceylon
void
variadiqueEtExpansion(String
* liste) {
print("variadiqueEtExpansion"
);
printAll(liste);
}
void
demoVariadiqueEtExpansion() {
String
[] liste = [ "foo"
, "bar"
];
variadiqueEtExpansion(*liste);
}
III. Fonctions d'ordre supérieur▲
Ceylon n'est pas à strictement parler un langage fonctionnel, en effet la mutation des données n'est pas permise dans un langage purement fonctionnel. En revanche, et contrairement à Java<8, les fonctions sont des objets de première classe, c'est-à-dire que vous pouvez manipuler des fonctions comme des valeurs. Vous pouvez les assigner, les passer en paramètre et même les renvoyer en tant que résultat d'une autre fonction.
//Source: tutoriel/b_fonctions/a_ordre_superieur.ceylon
class
FonctionOrdreSuperieur
(String
param) {
"Fonction classique"
shared
String
fonction1() { return
param; }
"Fonction qui renvoie une autre fonction"
shared
String
() fonction2() { return
fonction1; }
"Fonction qui prend une fonction en paramètre"
shared
String
fonction3(String
() fonction) { return
fonction(); }
"Démo"
shared
String
demo() {
return
fonction3(fonction2());
}
}
On peut noter deux choses. La première c'est que l'on référence une méthode simplement par son nom sans les parenthèses. La deuxième chose c'est que le type d'une fonction se déclare en indiquant le type du retour suivi d'une parenthèse et de la liste des types des paramètres. Le type exact d'une fonction est Callable. Il s'agit d'un type paramétré dont le premier paramètre de type est la valeur de retour et le second la liste des types des paramètres :
//Source: tutoriel/b_fonctions/b_Callable.ceylon
void
demoCallable() {
FonctionOrdreSuperieur
demo = FonctionOrdreSuperieur
("foobar"
);
Callable
<String
,[]> fonction1 = demo.fonction1;
Callable
<Callable
<String
,[]>,[]> fonction2 = demo.fonction2;
Callable
<String
,[Callable
<String
,[]>]> fonction3 = demo.fonction3;
}
Comme pour les tuples, le sucre syntaxique est bien plus lisible :
//Source: tutoriel/b_fonctions/c_Callable_sucre_syntaxique.ceylon
void
demoCallableAvecSucreSyntaxique() {
FonctionOrdreSuperieur
demo = FonctionOrdreSuperieur
("foobar"
);
String
() fonction1 = demo.fonction1;
String
()() fonction2 = demo.fonction2;
String
(String
()) fonction3 = demo.fonction3;
}
On peut également référencer les fonctions de manière statique (un peu comme en Java 8). En Java 8, un paramètre s'ajoute (en première position) pour définir l'instance sur laquelle s'appliquera la fonction, alors qu'en Ceylon les références statiques renvoient une fonction qui
- prend un et un seul paramètre correspondant au type référencé,
- renvoie la fonction référencée de manière non statique
//Source: tutoriel/b_fonctions/d_reference_statique_fonction.ceylon
void
referenceStatiqueDeFonction() {
FonctionOrdreSuperieur
demo = FonctionOrdreSuperieur
("foobar"
);
// Sans référence statique
String
() fonction1 = demo.fonction1;
// Avec référence statique
String
()(FonctionOrdreSuperieur
) fonction1statique = FonctionOrdreSuperieur
.fonction1;
String
() fonction1bis = fonction1statique(demo);
}
Si une méthode renvoie void, alors le type utilisé pour symboliser la valeur de retour sera Anything. Le système de type vous autorisera alors à utiliser n'importe quelle méthode (tant que les types des paramètres correspondent) :
//Source: tutoriel/b_fonctions/e_reference_methode_void.ceylon
void
retourneVoid(String
foobar) {
print("Retourne void"
);
}
String
retourneString(String
foobar) {
print("Retourn String"
);
return
foobar;
}
void
demoRetourneVoid() {
variable
Anything
(String
) fn = retourneVoid;
fn("foobar"
);
fn = retourneString;
fn("foobar"
);
}
Allons un peu plus loin dans le domaine fonctionnel grâce à la curryfication. Il s'agit de passer d'une fonction avec différents arguments à une autre fonction qui prendrait moins d'arguments, mais renverrait une fonction. Voici un exemple tout simple :
//Source: tutoriel/b_fonctions/f_curryfication.ceylon
void
demoCurryfication() {
// Fonction String(String,Integer)
String
repeter(String
chaine, Integer
repetition) => chaine.repeat(repetition);
// Figeons un paramètre
String
repeterUneFois(String
chaine) => repeter(chaine, 1
);
String
repeterFoobar(Integer
repetition) => repeter("Foobar"
, repetition);
// Utilisation
print(repeter("foobar"
, 1
));
print(repeterUneFois("raboof"
));
print(repeterFoobar(2
));
// Avec la curryfication
String
repeterCurryfie(String
chaine)(Integer
repetition) => repeter(chaine, repetition);
String
(Integer
)(String
) repeterCurryfieRef = repeterCurryfie;
String
(Integer
) repeterFoobarCurryfie = repeterCurryfie("Foobar"
);
// Appels
print(repeterCurryfie("foobar"
)(1
));
print(repeterFoobarCurryfie(2
));
}
La curryfication permet ainsi de figer certains arguments (exemple repeterFoobarCurryfie), c'est ce qu'on appelle « l'application partielle ».
L'avantage offert par Ceylon tient dans la facilité de déclaration. En effet, l'ordre des paramètres de la version curryfiée est conservé. Alors que si l'on regarde le type de la référence (repeterCurryfieRef), l'ordre des paramètres est inversé.
L'API Ceylon vient avec deux fonctions pour jouer avec la curryfication. La première, curry, permet de passer de la forme standard à la forme curryfiée ; et la seconde uncurry permet de faire l'inverse. Voici un exemple :
//Source: tutoriel/b_fonctions/g_curry.ceylon
void
demoCurry() {
String
fonction1(String
a, Integer
b, Float
c) => "{a: \"
``a``\", b: ``b``, c: ``c``}";
String(Integer, Float)(String) fonction2 = curry(fonction1);
String(String, Integer, Float) fonction3 = uncurry(fonction2);
print("
fonction1: ``fonction1("a"
, 2
,3.0
)``");
print("
fonction2: ``fonction2("d"
)(5
,6.0
)``");
print("
fonction3: ``fonction3("g"
, 8
,9.0
)``");
}
Pour en finir avec les fonctions, sachez qu'il est possible également de déclarer des fonctions anonymes. Les fonctions anonymes sont des fonctions :
- sans noms (!) ;
- avec, optionnellement, le type de retour void ou function (inférence) ;
- avec, obligatoirement, la liste des paramètres (éventuellement vide) ;
-
qui contiennent :
- soit une grosse flèche (=>) suivie d'une expression (pas de return),
- soit un bloc.
//Source: tutoriel/b_fonctions/h_anonymes.ceylon
void
demoAnonymes() {
// Fonction "fonction"
String
applique(String
(String
) fn) {
return
fn("foo"
);
}
// Anonyme - sans type de retour - expression
print("fonction1:
``applique((String
a) => a.uppercased)``"
);
// Anonyme - sans type de retour - bloc
print("fonction2:
``applique(
(String
a) { return
a.reversed; })``"
);
// Anonyme - function - expression
print("fonction3:
``applique(function
(String
a) => a.repeat(2
))``"
);
// (ERREUR) Anonyme - String - expression
print("fonction4:
``applique(String
(String
a) => a.repeat(2
))``"
);
// Anonyme - void - expression
void
fait(Anything
(String
) fn) => fn("fonction 5: void"
);
fait(void
(String
a) => print(a));
// Référence sur fonction anonyme
String
(String
) fonction6 = (String
a) => a.rest;
print("fonction6:
``applique(fonction6)``"
);
}
Pour des problèmes de lisibilité et de formatage de code, il est déconseillé d'utiliser les fonctions anonymes avec des blocs. Si votre code ne tient pas dans une simple expression, externalisez-le dans une fonction locale (au bloc, à la classe ou au package).
IV. Paramètres nommés▲
Par défaut, les paramètres effectifs sont passés « par position ». C'est-à-dire que c'est l'ordre dans lequel sont passés les paramètres effectifs qui permet leur association avec les paramètres formels de la fonction. L'utilisation des paramètres nommés est la possibilité offerte par Ceylon d'associer les paramètres à l'aide de leur nom plutôt que leur position. Dans ce cas, la liste des paramètres (y compris les parenthèses) est remplacée par un bloc avec une liste d'assignations séparées par des ; :
//Source: tutoriel/c_parametres_nommes/a_parametres_nommes.ceylon
void
demoParametresNommes() {
String
fonction(String
a, String
b, String
c) => "{a:
``a``, b:
``b``, c:
``c``}"
;
String
position = fonction("1"
, "2"
, "3"
);
String
nommes = fonction { a = "4"
; b = "5"
; c = "6"
; };
}
Même si ce n'est pas recommandé, il est possible de mixer avec et sans nom :
//Source: tutoriel/c_parametres_nommes/b_mix.ceylon
void
demoMix() {
String
fonction(String
a, String
b, String
c) => "{a:
``a``, b:
``b``, c:
``c``}"
;
print("a=1; b=2; c=3; =>
``fonction { "1"
; "2"
; "3"
; }``"
);
print("a=1; c=2; b=3; =>
``fonction { "1"
; c="2"
; "3"
; }``"
);
print("a=1; b=3; c=2; =>
``fonction { "1"
; b="3"
; "2"
; }``"
);
}
Pour les paramètres variadiques, il est nécessaire de les encapsuler dans une séquence (les séquences seront présentées dans le chapitre « Collections ») :
//Source: tutoriel/c_parametres_nommes/c_variadique.ceylon
void
demoVariadique() {
String
fonction(String
a, String
+ others) => "{a:
``a``, others:
``others``}"
;
print("a=1; others=2,3; =>
``fonction { "1"
; ["2"
, "3"
]; }``"
);
print("a=4; others=5,6; =>
``fonction { "4"
; others=["5"
, "6"
]; }``"
);
print("a=7; others=8,9; =>
``fonction ( "7"
, "8"
, "9"
)``"
);
}
Pour les « itérables » (également présentés dans le chapitre « Collections »), avec l'utilisation des paramètres nommés, il est possible d'omettre le nom, les accolades ({}) et le point-virgule (;), s'il s'agit du dernier paramètre effectif :
//Source: tutoriel/c_parametres_nommes/d_iterable.ceylon
void
demoIterable() {
String
fonction(String
a, {String
+} others, String
b) => "{a:
``a``, b:
``b``, others:
``others``}"
;
print("a=01; b=02; others=02,03; =>
``fonction { "01"
; b="02"
; "03"
, "04"
}``"
);
print("a=05; b=08; others=06,07; =>
``fonction { "05"
; { "06"
, "07"
}; "08"
; }``"
);
}
Le choix d'utiliser des blocs ({}) pour matérialiser ce type d'appel n'est pas dû au hasard. La syntaxe est ainsi similaire à celle de la redéfinition d'un membre, les constructions suivantes sont donc valides :
- déclaration d'une fonction ;
- déclaration d'un objet (object) ;
- déclaration d'un « getter » (=>).
Voyons quelques exemples :
//Source: tutoriel/c_parametres_nommes/e_syntaxe_avancee.ceylon
void
demoSyntaxeAvancée() {
// Déclaration d'une fonction
String
applique(String
s, String
(String
) fn) => fn(s);
String
resultat = applique {
s = "foobar"
;
String
fn(String
p) => p.uppercased;
};
print("Fonction :
``resultat``"
);
// Déclaration d'un objet
void
affiche(String
nom, Identifiable
obj) => print("
``nom`` :
``obj.hash``"
);
affiche {
"Objet"
;
object
obj satisfies
Identifiable
{
shared
actual
Integer
hash => 0
;
shared
actual
Boolean
equals(Object
that) => that == this
;
}
};
// Déclaration d'un "getter"
Integer
carre(Integer
a) => a * a;
variable
Integer
x = 1
;
Integer
fauxCarre = carre {
a => x++;
};
print("Getter :
``fauxCarre``"
);
}
Les paramètres nommés forment Le pattern de construction en Ceylon. En effet, en l'absence de surcharge, c'est un moyen de définir plusieurs manières de construire un objet :
//Source: tutoriel/c_parametres_nommes/f_surcharge.ceylon
void
demoSurcharge() {
// Constructeur
class
Valeur
(String
? s = null, Integer
? i = null) {
Integer
entier;
String
chaine;
if
(exists
s) {
"s et i ne doivent pas être définis tous les deux"
assert
(i is
Null
);
chaine = s;
Integer
? parse = parseInteger(chaine);
assert
(exists
parse);
entier = parse;
} else
{
entier = i else
0
;
chaine = entier.string;
}
shared
actual
String
string => "{chaine: '
``chaine``'; entier:
``entier`` }"
;
}
Valeur
vide = Valeur
{};
Valeur
chaine = Valeur
{ s = "10_000"
; };
Valeur
entier = Valeur
{ i = 10
; };
print("vide :
``vide``"
);
print("chaine :
``chaine``"
);
print("entier :
``entier``"
);
}
Les paramètres nommés sont également utilisés en remplacement des « Fluent interfaces ». Il peut s'agir soit d'un simple « Builder » :
//Source: tutoriel/c_parametres_nommes/g_builder.ceylon
void
demoBuilder() {
class
Personne
(shared
String
nom, shared
String
prenom, shared
Integer
age) {
shared
actual
String
string => "{nom:
``nom``; prenom:
``prenom``; age:
``age``}"
;
}
print(Personne
{ nom="DUPOND"
; prenom="Jean"
; age=30
; });
}
//Source: tutoriel/c_parametres_nommes/h_builder.java
public
class
Personne {
String nom;
String prenom;
int
age;
private
Personne
(
String nom, String prenom, int
age) {
this
.nom =
nom;
this
.prenom =
prenom;
this
.age =
age;
}
public
String getNom
(
) {
return
nom;
}
public
String getPrenom
(
) {
return
prenom;
}
public
int
getAge
(
) {
return
age;
}
public
String toString
(
) {
return
"{nom: "
+
getNom
(
) +
"; prenom: "
+
getPrenom
(
) +
"; age: "
+
getAge
(
) +
"}"
;
}
}
public
class
PersonneBuilder {
String nom;
String prenom;
int
age;
public
PersonneBuilder withNom
(
String nom) {
this
.nom =
nom;
return
this
;
}
public
PersonneBuilder withPrenom
(
String prenom) {
this
.prenom =
prenom;
return
this
;
}
public
PersonneBuilder withAge
(
int
age) {
this
.age =
age;
return
this
;
}
public
Personne build
(
) {
return
new
Personne
(
nom, prenom, age);
}
}
public
static
void
main
(
String[] args) {
System.out.println
(
new
PersonneBuilder
(
)
.withNom
(
"DUPOND"
)
.withPrenom
(
"Jean"
)
.withAge
(
30
)
.build
(
));
}
On appréciera la concision de l'exemple en Ceylon comparé à la version de Java. Voyons désormais comment se servir de cette syntaxe pour définir un minilangage (DSL). Les paramètres nommés offrent une profonde sémantique en apportant la programmation déclarative. Voici un exemple définissant une structure déclarative pour SQL :
//Source: tutoriel/c_parametres_nommes/i_declaratif.ceylon
void
demoDeclaratif() {
class
Expression
() {}
object
all extends
Expression
() {
shared
actual
String
string => "*"
;
}
Expression
nvl(String
|Expression
+ expressions) {
object
nvl extends
Expression
() {
shared
actual
String
string => "nvl(
``expressions``)"
;
}
return
nvl;
}
Expression
distance(String
|Integer
x1, String
|Integer
y1, String
|Integer
x2, String
|Integer
y2) {
object
distance extends
Expression
() {
shared
actual
String
string => "distance(
``x1``,
``y1``,
``x2``,
``y2``)"
;
}
return
distance;
}
class
Select
(shared
{String
|Expression
+} expressions) {
shared
actual
String
string => "select
``expressions``"
;
}
class
From
(shared
{String
|Sql
+} collections) {
shared
actual
String
string => "from
``collections``"
;
}
class
Where
(shared
{String
|Expression
+} conditions) {
shared
actual
String
string => "where
``conditions``"
;
}
class
OrderBy
(shared
{String
|Expression
+} expressions) {
shared
actual
String
string => "order by
``expressions``"
;
}
class
Sql
(
shared
Select
select,
shared
From
from,
shared
Where
? where = null,
shared
OrderBy
? orderBy = null) {
shared
actual
String
string {
String
whereStr;
if
(exists
where) {
whereStr = "\n
``where``"
;
} else
{
whereStr = ""
;
}
String
orderStr;
if
(exists
orderBy) {
orderStr = "\n
``orderBy``"
;
} else
{
orderStr = ""
;
}
return
"
``select``\n
``from````whereStr````orderStr``"
;
}
}
Sql
sql = Sql
{
Select
{ "x"
, nvl("y"
, "Default.y"
) };
From
{
Sql
{ Select
{ all }; From
{ "Coordonnées"
}; },
"Default"
};
Where
{ "x < 10"
, "y != 20"
};
OrderBy
{ distance("x"
, "y"
, 0
, 0
) };
};
print(sql);
}
Je vous épargne la version Java, mais vous pouvez la retrouver dans les codes sources sous « tutoriel/c_parametres_nommes/j_declaratif.java ».
Il est ainsi intéressant de noter que cette syntaxe est exploitée avantageusement par le module ceylon.html :
//Source: tutoriel/c_parametres_nommes/k_html.ceylon
import
ceylon.html { ... }
void
demoHtml() {
Html
{ //
doctype = xhtml11; // <!DOCTYPE HTML PUBLIC
// "-W3CDTD XHTML 1.1EN"
// "http:www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
// <html>
Head
{ // <head
title = "Ma première page HTML"
; // title="Ma première page HTML"
// >
CharsetMeta
{ charset = "UTF-8"
; } // <meta charset="UTF-8"/>
}; // </head>
Body
{ // <body>
H1
( "Mon premier entête"
), // <h1>Mon premier entête</h1>
P
( "Mon premier paragraphe."
) // <p>Mon premier paragraphe.</p>
}; // </body>
}; // </html>
}
Pour finir avec la programmation déclarative et les DSL, il est intéressant de noter que l'ensemble de l'API (interfaces, classes, objets, fonctions, etc.) forme la grammaire de votre langage. Ainsi si elle est bien documentée, cela constitue une aide très appréciable au même titre que les XSD.
V. Opérateurs▲
V-A. Opérateurs primitifs▲
Dans Ceylon, la plupart des opérateurs sont du sucre syntaxique et consistent en l'utilisation d'opérateurs et/ou l'invocation de méthodes ; sauf les opérateurs primitifs. Il existe sept opérateurs de base :
- « . » (membre)Member expression : accéder à un membre dénommé par l'opérande droit et appartenant à l'opérande gauche ;
- « = » (assignation)Assignment : assigner la valeur de l'opérande de droite à celui de gauche ;
- « === » (identité)Identity : vérifier que les deux opérandes désignent la même référence mémoire ;
- « is » (type)Assignability : vérifier que la valeur est bien du type désigné (l'ordre des opérandes dépend de l'utilisation) ;
- « of » (couverture)Coverage ;
- « () » (appel par position)Invocation : invoque une méthode en faisant correspondre les arguments par leur position ;
- « {} » (appel par nom)Invocation : invoque une méthode en faisant correspondre les arguments par leur position et/ou leur nom.
V-B. Polymorphisme▲
Le polymorphisme des opérateurs est un moyen élégant et sûr d'apporter la surcharge d'opérateur, sans ses inconvénients. On ne dénombre plus les critiques sur l'utilisation abusive de la surcharge d'opérateur en C++ ; certains se livrant plus à un exercice d'art que de programmation.
Pour pallier ce problème, la plupart des opérateurs (ex. : +) sont associés à une méthode d'une interface (ex. : Summable.plus) et utiliser des opérateurs n'est que du sucre syntaxique pour appeler la méthode associée.
Ainsi les deux expressions suivantes sont équivalentes :
//Source: tutoriel/d_operateurs/a_polymorphisme.ceylon
void
demoPolymorphismePlusPetit() {
Boolean
operateur = 8
< 9
;
Boolean
methode = 8
.compare(9
) === smaller;
}
Voici une matrice de définition des opérateurs polymorphiques :
Nom | Type | Opérateur | Utilisation | Définition |
---|---|---|---|---|
Comparaison | ||||
Égalité | ObjectObject | == | a == b | a.equals(b) |
!= | a != b | !a.equals(b) | ||
Comparaison | ObjectObject | <=> | a <=> b | a.compare(b) |
< | a < b | a.compare(b) == smaller | ||
> | a > b | a.compare(b) == larger | ||
<= | a <= b | a.compare(b) != larger | ||
>= | a >= b | a.compare(b) != larger | ||
Contenance | CategoryCategory | in | a in b | b.contains(a) |
Il existe également les opérateurs de « comparaison bornée » (bounded comparison). Il s'agit de combiner deux opérateurs de comparaison ; par exemple, a<b<c. Cette expression se traduit par b>a && b<c
Nom | Type | Opérateur | Utilisation | Définition |
---|---|---|---|---|
Arithmétique | ||||
Somme | SummableSummable | + | a + b | a.plus(b) |
Différence | NumericNumeric | - | a - b | a.minus(b) |
Produit | * | a * b | a.times(b) | |
Quotient | / | a / b | a.divided(b) | |
Reste | IntegralIntegral | % | a % b | a.remainder(b) |
Inversion | InvertableInvertable | - | - a | a.negativeValue |
+ | + a | a.positiveValue | ||
Successeur | OrdinalOrdinal | ++ | ++a | a=a.successor |
Prédécesseur | -- | --a | a=a.predecessor | |
Incrément | ++ | a++ | (++a).predecessor | |
Décrément | -- | a-- | (--a).successor | |
Puissance | ExponentiableExponentiable | ^ | a^b | a.power(b) |
Séquence | ||||
Recherche | CorrespondenceCorrespondence | [] | a[key] | a.item(key) |
Segment | RangeRange | [:] | a[from:length] | a.segment(b) |
Portée | [..] | a[from..to] | a.span(from,to) | |
[...] | a[from...] | a.spanFrom(from) | ||
a[...to] | a.spanTo(to) | |||
Extension/Réduction (scale) | ScalableScalable | ** | a ** b | b.scale(a) |
Ensemble | ||||
Union | SetSet | | | a | b | a.union(b) |
Intersection | & | a & b | a.intersection(b) | |
Complément | ~ | a ~ b | a.complement(b) |
V-C. Et les autres…▲
Il y a les opérateurs primitifs, les opérateurs polymorphiques, et… les autres ! Pour ne pas changer, voici une matrice :
Nom | Opérateur | Utilisation | Définition |
---|---|---|---|
Logique | |||
Non | ! | !a | if (a) then false else true |
Ou | || | a || b | if (a) then true else b |
Et | && | a && b | if (a) then b else false |
Null | |||
Existe postfixé | exists | a exists | is Object a |
Non-vide postfixé | nonempty | a nonempty | is [Type+] a |
Accès sûr | ?. | a?.b | if (exists a) then a.b else null |
Séquence | |||
Expansion d'attribut | *. | sequence*.membre | [ for (X x in sequence) x.membre ] |
Expansion de méthode | *. | sequence*.methode | Renvoie un CallableCallable dont la valeur de retour est un SequentialSequential du type renvoyé par methode et les mêmes types de paramètres. |
Création | |||
Plage par portée | .. | from..to | Range(from,to) |
Plage par segment | : | from:length | Créer un SequentialSequential de taille length en commençant à partir de from. |
Entrée (pour un dictionnaire) | -> | key->value | Entry(key,value) |
Condition | |||
Alors | then | a then b | if (a) then b else null |
Sinon | else | a else b | if (exists a) then a else b |
V-D. Invocation façon opérateur▲
Un dernier mot concernant les opérateurs est la possibilité d'utiliser une notation « infixe » pour n'importe quelle méthode si celle-ci possède au plus un argument. Il est alors possible de retirer les opérateurs d'accès . et d'appel () :
//Source: tutoriel/d_operateurs/b_style_operateur.ceylon
class
DemoOperatorStyle
(Integer
a) {
shared
Integer
op0() {
return
a;
}
shared
Integer
op1(Integer
b) {
return
a + b;
}
shared
Integer
op2(Integer
b, Integer
c) {
return
b + c;
}
}
void
demoOperatorStyle() {
DemoOperatorStyle
demo = DemoOperatorStyle
(1
);
print(demo op0);
print(demo op1 2
);
//Two arguments
print(demo.op2(*[2
,3
])); // Ok
print(demo op2 2
3
);
print(demo op2 2
,3
);
print(demo op2 (2
3
));
print(demo op2 (2
,3
));
print(demo op2 [2
,3
]);
print(demo op2 *[2
,3
]);
}
VI. Conclusion▲
Au cours de cet article, nous avons vu comment exploiter au mieux la syntaxe de Ceylon pour vous aider à écrire, mais également à concevoir de belles API. La prochaine partie est consacrée aux conteneurs de données (listes, dictionnaires, etc.), autrement dit les collections.
VII. 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 et Claude Leloup 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.
VIII. Annexes▲
VIII-A. Sources des exemples▲
Tous les exemples donnés dans cet article sont disponibles sous GitHub dans le répertoire src-04-appels.
VIII-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 :