Tutoriel Ceylon : Appels et arguments

Image non disponible

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

Commentez Donner une note à l'article (5)

Article lu   fois.

L'auteur

Profil ProSite personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

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
Sélectionnez
//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
Sélectionnez
//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
Sélectionnez
//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
Sélectionnez
//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
Sélectionnez
//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
Sélectionnez
//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
Sélectionnez
//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
Sélectionnez
//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
Sélectionnez
//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
Sélectionnez
//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
Sélectionnez
//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
Sélectionnez
//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
Sélectionnez
//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
Sélectionnez
//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
Sélectionnez
//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
Sélectionnez
//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
Sélectionnez
//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
Sélectionnez
//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
Sélectionnez
//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
Sélectionnez
//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)
Sélectionnez
//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)
Sélectionnez
//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)
Sélectionnez
//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)
Sélectionnez
//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)Member expression : accéder à un membre dénommé par l'opérande droit et appartenant à l'opérande gauche ;
  2. « = » (assignation)Assignment : assigner la valeur de l'opérande de droite à celui de gauche ;
  3. « === » (identité)Identity : vérifier que les deux opérandes désignent la même référence mémoire ;
  4. « is » (type)Assignability : vérifier que la valeur est bien du type désigné (l'ordre des opérandes dépend de l'utilisation) ;
  5. « of » (couverture)Coverage ;
  6. « () » (appel par position)Invocation : invoque une méthode en faisant correspondre les arguments par leur position ;
  7. « {} » (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 :

Polymorphisme d'opérateur et plus petit
Sélectionnez
//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 () :

Invocation façon opérateur
Sélectionnez
//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 » :

Image non disponible

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

Image non disponible

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

Image non disponible

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

Image non disponible

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

Image non disponible

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

Image non disponible

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

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 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.