Présentation du langage Vala

La puissance du C et la simplicité du C# grâce à Vala

Vala est un nouveau langage de programmation. Enfin c'est ainsi qu'il est présenté sur le site officiel.
Dans ce tutoriel, nous allons donc découvrir ce qu'est vraiment Vala ainsi que les possibilités qu'il propose.

1 commentaire Donner une note à l'article (5)

Article lu   fois.

L'auteur

Profil ProSite personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Public concerné

Image non disponible

Je réserve ce tutoriel à un public averti, non pas que ce langage soit compliqué, mais il reste relativement jeune et il n'est pas rare de devoir mettre les mains dans le cambouis. Cependant cela devient de plus en plus rare et pour une application simple un débutant ne devrait pas rencontrer de problème.

II. Qu'est ce que Vala ?

Vala est un nouveau langage de programmation. Nouveau puisqu'il existe depuis 2006 (comparé au langage C qui existe depuis le début des années 70), et aussi nouveau par sa conception puisqu'il ne s'agit pas réellement d'un langage de programmation.

La première chose que l'on remarque lorsque l'on voit du code écrit en Vala c'est la forte similitude avec le C#, aussi bien au niveau syntaxe que fonctionnalités. La deuxième chose, plus étonnante, c'est que le compilateur Vala, va générer un code dans un langage intermédiaire, comme peuvent le faire le C# ou le Java.

A première vue, ça a l'odeur, la couleur et le goût du C#. S'il n'existait pas mono l'initiative serait intéressante. Mais alors où se trouve l'intérêt d'un tel langage ? Toute l'astuce de Vala réside dans le langage intermédiaire utilisé, comme je le laisse sous-entendre dans le titre de l'article, il s'agit bien sûr du C ! Du C à l'aide de la bibliothèque GObject.

Pour en finir avec cette présentation, le mariage entre le C#, pour l'écriture, et le C, pour la compilation, procure à Vala un certain nombre n'avantages, dont voici un résumé :

  • Une facilité d'écriture : il est indéniable que le C# reste plus lisible que le C,
  • Un langage réellement orienté object : alors que le C/GObject accuse parfois d'une syntaxe bancale vis-à-vis de la POO (en particulier pour la définition d'une classe), Vala permet de corriger ceci,
  • Une rapidité d'exécution proche du C pur : comme le code est traduit en C, on se retrouve avec des vitesses d'exécution proche de celle d'un programme écrit en C(1),
  • La compatibilité binaire avec le C est conservée ce qui permet de ré-utiliser de nombreux outils et bibliothèques éprouvées,
  • Ne dépend pas d'une machine binaire pour être exécuté.

Maintenant que les présentations sont faites, passons à la pratique.

III. Vala in a nuts

Nous nous contenterons ici d'une noix(2) pour aborder la syntaxe du langage Vala, tout simplement parce qu'elle est proche du C# et les habitués du C++ ou de Java devraient s'y retrouver très rapidement.

Voici la liste des fonctionnalités proposées par le langage :

  • Les classes
  • Les interfaces
  • Les classes abstraites
  • Les propriétés
  • Les génériques
  • Foreach
  • Les expressions lambda
  • Les signaux
  • Les types nullables
  • La gestion assistée de la mémoire
  • Les exceptions

Vous pourrez trouver l'ensemble des mots clés sur le wiki officiel : http://live.gnome.org/Vala/Syntax.

Histoire de vous familiariser avec ce langage et en guise de pense bête, le vais détailler rapidement chaque fonctionnalité avec un exemple de quelques lignes.

III-A. Les classes

Il est possible de créer des classes, qu'elles soient publiques ou privées (portée limitée au fichier). Il est aussi possible d'utiliser l'héritage (simple uniquement).

Pour créer une classe, il faut qu'elle hérite (directement ou par l'intermédiaire de sa classe mère) de GLib.Object (3) :

Pour finir, il est possible d'inclure une classe dans un espace de nom, soit en utilisant le mot clé namespace soit en préfixant le nom de la classe.

Voici quelques exemples pour illustrer ceci :

 
Sélectionnez
/* Une simple structure de données */
public MaClasse
{
 
Sélectionnez
/* Une vrai classe inclue dans l'espace de nom Dvp */
namespace Dvp
{
  public MaClasse : Object
  {
 
Sélectionnez
/* Une classe privée aussi dans l'espace de nom Dvp */
private Dvp.MaClasse : Object
{

La GLib étant la bibliothèque standard de Vala, il est inutile d'écrire GLib.Object, l'utilisation du namespace est implicite.

III-A-1. Constructeur

La construction d'un objet est quelque peu différente par rapport à ce qui existe dans d'autres langages.

Voici la séquence de création d'un objet :

  • L'utilisateur demande création d'un nouvel object grâce à l'opérateur new,
  • Le système appelle le constructeur de classe pour l'ensemble de la hiérarchie en partant de la classe de base,
  • Le système appelle le constructeur d'instance associé à notre classe.

Le constructeur de classe porte le nom construct et ne possède pas de portée, type de retour ou paramètre :

 
Sélectionnez
public class MaClasse
{
  construct
  {

Concernant le constructeur d'instance, il porte le nom de la classe sans type de retour :

 
Sélectionnez
public class MaClasse
{
  public MaClasse ()
  {

Il est possible de spécifier un ou plusieurs paramètres cependant il est uniquement possible de modifier les propriétés de l'objet dans cette partie (nous reviendrons plus tard sur les propriétés) :

 
Sélectionnez
public class MaClasse
{
  public string label { ... }

  public MaClasse (string label)
  {
    this.label = label;

Pour finir avec ce type de constructeur il est possible de le surcharger en utilisant un suffixe :

 
Sélectionnez
public class MaClasse
{
  public string label { ... }

  public MaClasse.with_label (string label)
  {
    this.label = label;
  }

  public MaClasse ()
  {
    this.label = "default";
  }

III-A-2. Destructeur

Comme très souvent, le destructeur porte le nom de la classe précédé d'un tild :

 
Sélectionnez
public class MaClasse
{
  ~MaClasse ()
  {
  }

III-B. Interfaces

La déclaration d'une interface reste très classique, les champs à implémenter étant spécifiés comme abstraits.

 
Sélectionnez
interface MonInterface
{
  public abstract void foo ();

Il est possible de profiter de l'héritage entre interface et une classe peut implémenter une ou plusieurs interfaces de la même manière que pour l'héritage :

 
Sélectionnez
class Dvp.SuperClass : MaClass, MonInterface
{
  public override void foo ()
  {

III-C. Les classes abstraites

Si vous avez compris les interfaces, inutile de s'étendre sur les classes abstraites :

 
Sélectionnez
abstract class AbstractClass
{
  public abstract void foo ();

  public void bar ()
  {

III-D. Les propriétés

La syntaxe des propriétés est très riche, je vous renvoie au manuel de référence pour un aperçu complet des possibilités.

Voici tout de même un exemple simple de propriété :

 
Sélectionnez
class MaClass : Object
{
  private string _prop;

  public string prop
  {
    get
    {
      return this._prop;
    }
    set
    {
      this._prop = value:
    }
  }

  public static int main (string[] args)
  {
    var c = new MaClass ();
    c.prop = "test";
    print ("%s\n", c.prop);
    return 0;
  }
}

Pour pouvoir bénéficier des propriétés, la classe doit hériter de GLib.Object.

Pour simplifier l'écriture de propriétés "standard" (c'est-à-dire comme ci-dessus, qui ne font que manipuler une variable privée), Vala propose un mécanisme de propriétés automatiques qui cache cette partie du code :

 
Sélectionnez
class MaClass : Object
{
  public string prop
  {
    get;
    set;
  }
}

en plus de la visibilité de la propriété, il est possible d'affiner la visibilité des assesseurs :

 
Sélectionnez
class MaClass : Object
{
  public string prop
  {
    get;
    private set;
  }
}

Ce qui rendra la propriété lisible par tous, mais modifiable uniquement pas les instances de la classe.

Pour finir, si vous souhaitez qu'une propriété ne soit modifiée uniquement lors de la construction de l'objet, vous pouvez remplacer l'assesseur set par construct :

 
Sélectionnez
class MaClass : Object
{
  public string prop
  {
    get;
    construct;
  }

  public MaClass (string prop)
  {
    this.prop = prop;
  }
}

De plus ceci permet de mettre en place la valeur de la propriété avant l'appel au constructeur de classe construct (par défaut, la valeur n'est modifiée qu'ensuite).

Ceci n'étant pas très clair, voici un exemple qui devrait vous convaincre de l'utilité de ce mot clés :

 
Sélectionnez
class MaClass : Object
{
  public string prop
  {
    get;
    construct;
  }

  public string prop2
  {
    get;
    set;
  }

  public MaClass (string prop, string prop2)
  {
    this.prop = prop;
    this.prop2 = prop2;
  }

  construct
  {
    print ("MaClass.prop = %s\n", this.prop);
    print ("MaClass.prop2 = %s\n", this.prop2);
  }
}


public class Main
{
  public static int main (string[] args)
  {
    new MaClass ("1", "2");
    return 0;
  }
}
 
Sélectionnez
MaClass.prop = 1
MaClass.prop2 = (null)

III-E. Génériques

Vala supporte les génériques (ou template pour les adeptes du C++), de manière très classique :

 
Sélectionnez
class Wrapper<T> : Object { ... }
new Wrapper<Object> ();

Les différentes bibliothèques (en particulier la glib), ont été enrichies avec les génériques qui n'existent pas en C.

Leur utilisation n'est pas obligatoire (par exemple pour les listes) mais cela améliore la lisibilité du code est permet d'utiliser certaines fonctionnalités du langage, comme le foreach.

III-F. Foreach

Pour illustrer mes propos précédents, voici un exemple avec les listes :

 
Sélectionnez
List<string> files;
/* ... */
foreach (string file in files)
{
 print ("%s\n", file);
}

Le foreach est utilisable avec les tableaux, les listes (simple ou doublement chaînées), les tables de hachages et toutes les classes qui implémente l'interface Gee.Iterable.

III-G. Délégué et expressions lambda

Les délégués (ou delegates, en anglais), sont utilisés à la place des pointeurs de fonctions et on les rencontre le plus fréquemment pour la gestion des événements (signaux). Pour associer une fonction callbask à un signal, il suffit de l'ajouter :

 
Sélectionnez
var win = new Gtk.Window ();

win.destroy += Gtk.main_quit;

La même syntaxe est utilisée pour déconnecter la fonction :

 
Sélectionnez
win.destroy -= Gtk.main_quit;

Et plutôt que de créer une fonction pour les signaux demandant peu de traitement, il est préférable d'utiliser les expressions lambda qui permettent d'inclure le corps de la fonction directement :

 
Sélectionnez
win.destroy += (s) => {
  print ("Quit\n");
  Gtk.main_quit ();
};

III-H. Les signaux

Nous venons de voir comment intercepter un signal, nous allons donc nous attarder sur la création d'un signal pour notre objet.

C'est extrêmement simple, il suffit de déclarer que votre objet envoi un signal à l'aide du code suivant :

 
Sélectionnez
public signal void sig_1 (int a);

Ensuite, votre objet émet ce signal tout naturellement :

 
Sélectionnez
this.sig_1 (10);

Et pour finir, si vous souhaitez intercepter le signal, il suffit d'utiliser les delegates ou les fonctions lambda :

 
Sélectionnez
o.sig_1 += (s, a) => {
    print ("sig_1 : %d\n", a);
};

Le premier paramètre correspond à l'objet qui à lancé le signal (s pour sender), il est passé explicitement.

III-I. Les exceptions

Pour ce qui est de la gestion des exceptions, nous retrouvons une syntaxe traditionnelle :

 
Sélectionnez
class MaClass : Object
{
  /* Fonction pouvant lancer une execption de type Error */
  public void foo () throws Error { ... }

  public static int main (string[] args)
  {
    try
    {
      foo ();
    }
    catch (Error e)
    {
      warning (e.message);
    }
    finally
    {
      /* ... */
    }
    return 0;
  }
}

Par contre pour la création et le lancement d'une exception, nous devons passer par les spécificités de la GLib(4).

Il faut commencer par créer un nouveau domaine d'erreur avec les différents types d'erreur possible :

 
Sélectionnez
public errordomain MyError
{
  ERROR_1,
  ERROR_2
}

Et ensuite, nous pouvons créer et lancer notre exception :

 
Sélectionnez
throw new MyError.ERROR_1 ("Error message");

IV. Utilisez vos anciennes bibliothèques

Comme précisé en introduction, il est possible de réutiliser vos bibliothèques écrites en C pour vos nouveaux développements en Vala. Pour cela, il suffit de créer un fichier d'en-tête vapi. Ce fichier contient simplement la déclaration des classes. Il est bien sûr nécessaire que votre interface corresponde à une approche objet (même s'il est possible de corriger certaines imperfections grâce à l'annotation CCode).

S'il est possible d'écrire ces fichiers à la main pour de petites bibliothèques, cela n'est pas envisageable pour les interfaces plus conséquentes (par exemple pour GTK+). Il existe donc le programme gobject-introspection qui pourra vous aider dans cette tâche.

V. FAQ

Voici une mini-FAQ qui vous permettra de répondre à quelques questions qui ne se trouvent pas forcement sur le site officiel.

V-A. Commet fonctionnent les callback ?

Comme précisé ci-dessus pour intercepter un signal, il faut utiliser les delegates. La connexion est extrêmement simple, cependant le prototype de la fonction callback possède quelques subtilités non précisées dans la documentation. Premièrement, il faut utiliser l'annotation Callback, ceci permet au compilateur de créer un swrapper afin d'assouplir la liste des arguments. Deuxièmement il est possible d'utiliser indifféremment une méthode de classe ou d'instance pour gérer le signal. Voici quelques exemples de callback :

 
Sélectionnez
public class TestCb : Object
{
  [Callback]
  private void cb_1 ()
  {
  }

  [Callback]
  private void cb_2 (Gtk.Button sender)
  {
  }

  [Callback]
  private static void cb_3 ()
  {
  }

  public static int main (string[] args)
  {
    /* ... */

    Gtk.Button btn = new Gtk.Button.with_label ("Click!");

    // Méthode d'instance sans paramètre
    btn.clicked += this.cb_1;

    // Méthode d'instance avec un paramètre
    btn.clicked += this.cb_2;

    // Méthode de classe
    btn.clicked += TestCb.cb_3;

    /* ... */
  }
}

Le compilateur se chargera de passer la référence vers l'instance si nécessaire et de compléter la liste des arguments.

V-B. ref, out, weak et transfert de propriété, de quoi s'agit-il ?

Voici des mots clés peu connus des développeurs C.

Le mot clé ref est utilisé pour spécifier qu'un paramètre est passé par référence, donc peut être modifié par la fonction.

Le mot clé out précise que le paramètre est utilisé pour stocker une valeur de retour.

Le mot clé weak désigne une référence en la définissant comme faible, c'est à dire que si toutes les références sur un objet sont faibles, l'objet sera détruit.

Le transfert de propriété, indiqué par le caractère #, permet de transférer la propriété d'une référence d'une variable à une autre. Par exemple :

 
Sélectionnez
Foo foo = #bar;

Normalement, la référence de l'objet bar est copiée dans foo et le nombre de références augmentée d'un. Ici, foo va contenir la référence de bar puis bar sera mis à null sans que le compteur de référence soit incrémenté.

Ceci est particulièrement utilisé lorsqu'une fonction retourne une chaîne de caractère créée localement, afin que celle-ci continue d'exister après le retour de la fonction.

Ceci se retrouve en C# dans les propriétés avec le mot clés new.

V-C. L'avenir de vala

Vala est encore jeune et cela se ressent lorsqu'on développe une application importante : il n'est pas rare de devoir modifier les fichiers vapi à la main pour corriger un problème généralement dû à une ambiguïté dans le nommage des fonctions en C.

Au fils de mes développements j'ai constitué un patch pour le fichier gtk+-2.0.vapi que vous pouvez trouver ici. Il contient les bugs qu'il est difficile de corriger.

V-D. Comment utiliser le fichier config.h ?

Les développeurs du monde Linux, en particulier ceux familiers des autotools connaissent le fameux fichier config.h. Pour pouvoir l'utiliser, il suffit de créer un fichier d'interface vapi ressemblant à l'exemple suivant :

 
Sélectionnez
[CCode (cprefix = "", lower_case_cprefix = "", cheader_filename = "config.h")]
namespace Config
{
  public const string PACKAGE;
  public const string PACKAGE_BUGREPORT;
  public const string PACKAGE_NAME;
  public const string PACKAGE_STRING;
}

V-E. Quel système de construction utiliser ?

Pour les adeptes des autotools, il est parfaitement possible de les utiliser avec vala. Le programme vala-gen-project vous génère tous les fichiers nécessaires pour débuter un projet avec les autotools.

Ou, si comme moi, vous trouvez les autotools repoussant, il existe waf qui prend aussi en compte le langage Vala.

VI. Références

VII. Remerciements

Merci à Adrien Artero pour la relecture attentive de cet article.

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


En référence à la série In a nutsheel des éditions O'Reilly.
Il est possible de s'en affranchir mais dans ce cas nous obtenons une simple structure de données et devont nous passer de nombreuses fonctionnalités.
Vous pouvez lire Gestion des erreurs en C avec la GLib afin de comprendre comment gérer les exceptions en C

  

Copyright © 2008 Nicolas Joseph. 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. Droits de diffusion permanents accordés à Developpez LLC.