Tutoriel Ceylon : Appels et arguments

Logan Mauzaize

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

- Titre : Tutoriel Ceylon : Appels et arguments

- Auteur : Logan Mauzaize

- Parution : 26 mars 2015

- Public visé :

- Licence : 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 ® 2015 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.

Sommaire

- I. Avant-propos

- II. Paramètres

- III. Fonctions d'ordre supérieur

- IV. Paramètres nommés

- V. Opérateurs

- V-A. Opérateurs primitifs

- V-B. Polymorphisme

- V-C. Et les autres…

- V-D. Invocation façon opérateur

- VI. Conclusion

- VII. Remerciements

- VIII. Annexes

- VIII-A. Sources des exemples

- VIII-B. Importer le projet sous Eclipse depuis Git

I - Avant-propos

Cet article est le quatriè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 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 :

Surcharge interdite

//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 :

Surcharge par union

//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 ? :

Paramètre par défaut

//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 :

Fonctions variadiques

//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 :

Paramètre par défaut et fonctions variadiques

//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 :

Variadique et 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.

Fonctions d'ordre supérieur

//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 :

Callable

//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 :

Callable - sucre syntaxique

//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

1. prend un et un seul paramètre correspondant au type référencé,

2. renvoie la fonction référencée de manière non statique

Référence statique de fonction

//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) :

Référence de méthode void

//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 :

Curryfication

//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 :

curry

//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.

Fonctions anonymes

//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 ; :

Paramètres nommés

//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 :

Paramètres par positions et/ou par noms

//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 ») :

Paramètres nommés variadiques

//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 :

Cas des itérables

//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 :

Syntaxe avancée

//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 :

Surcharge

//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 » :

Builder (ceylon)

//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; });
}

Builder (java)

//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 :

Programmation déclarative (ex: 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 :

Programmation déclarative (ex: 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 :

1. « . » (membre) : accéder à un membre dénommé par l'opérande droit et appartenant à l'opérande gauche ;

2. « = » (assignation) : assigner la valeur de l'opérande de droite à celui de gauche ;

3. « === » (identité) : vérifier que les deux opérandes désignent la même référence mémoire ;

4. « is » (type) : vérifier que la valeur est bien du type désigné (l'ordre des opérandes dépend de l'utilisation) ;

5. « of » (couverture) ;

6. « () » (appel par position) : invoque une méthode en faisant correspondre les arguments par leur position ;

7. « {} » (appel par nom) : 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 :

Polymorphisme d'opérateur et plus petit

//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é

Object

==

a == b

a.equals(b)

!=

a != b

!a.equals(b)

Comparaison

Object

<=>

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

Category

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

Summable

+

a + b

a.plus(b)

Différence

Numeric

-

a - b

a.minus(b)

Produit

*

a * b

a.times(b)

Quotient

/

a / b

a.divided(b)

Reste

Integral

%

a % b

a.remainder(b)

Inversion

Invertable

-

- a

a.negativeValue

+

+ a

a.positiveValue

Successeur

Ordinal

++

++a

a=a.successor

Prédécesseur

--

--a

a=a.predecessor

Incrément

++

a++

(++a).predecessor

Décrément

--

a--

(--a).successor

Puissance

Exponentiable

^

a^b

a.power(b)

Séquence

Recherche

Correspondence

[]

a[key]

a.item(key)

Segment

Range

[:]

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)

Scalable

**

a ** b

b.scale(a)

Ensemble

Union

Set

|

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 Callable dont la valeur de retour est un Sequential 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 Sequential 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 () :

Invocation façon opérateur

//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 :

Retour à l'article