[Atom] [Mail] [Twitter]
Liens : git · hacks · divers · cabale · buzz! · à propos +
Au menu
\/

Votre premier .emacs

#. Par rz0. Mis à jour le 20.02 2010 à 10:02. Aucun commentaire.
configuration débutants elisp emacs tutorial

Cela faisait un moment que je voulais migrer tous mes modestes écrits sur la même plateforme ; voilà un pas de plus dans cette direction avec la réédition de ce petit classique du fond de mon tiroir ! —rz0

Ceci n’est pas une introduction à Emacs en soi mais à sa configuration. Si vous connaissez les bases, avez lu le tutorial intégré ou quelque chose d’équivalent, que vous arrivez à vous déplacer, écrire du texte, et éditer des fichiers dans Emacs, mais souhaitez changer certains paramètres de son comportement, cet article est peut-être pour vous.

Cette introduction à la configuration vous apprendra au passage les bases de la syntaxe utilisée pour écrire le fichier .emacs ; si vous voulez juste copier et coller des bouts de code, cet article n’est pas pour vous.

Je ne documente ici ni XEmacs, ni NTEmacs, seulement GNU Emacs, soyez averti. Ce n’est pas que j’aie quoi que ce soit contre ceux-ci, mais je ne connais pas bien les autres emacs.

Comment configurer Emacs ?

Il existe trois moyens de configurer Emacs : par le menu, par une application intégrée appelée customize ou en éditant le fichier .emacs, situé dans votre dossier personnel ($HOME pour les étourdis).

Cet article porte sur l’édition du fichier .emacs, mais je vais tout de même passer en revue les autres possibilités pour vous expliquer pourquoi nous n’allons pas y avoir recours.

Par le menu

La première méthode n’a pas besoin d’explications ; si vous lisez ceci, vous savez probablement vous servir d’une souris, ou d’un clavier, ce qui est encore mieux, et quoi qu’il en soit, c’est suffisant pour configurer Emacs par le menu. Cette approche est toutefois très limitée, les options du menu étant ce qu’elles sont, et rien de plus.

Par customize

La deuxième offre un choix beaucoup plus important d’options ; quasiment tout ce qui est utile en terme de configuration directe peut être réalisé par cette interface. Pour l’invoquer, il suffit d’utiliser la commande customize (invoquée par M-x customize comme vous vous en doutez si vous avez eu une introduction décente à Emacs ; sinon, sachez que M-x signifie « la touche X en maintenant la touche Meta, Alt sur les claviers de PC, enfoncée »).

À partir de là, l’interface est assez simple : il y a des options, avec leur description, et leur valeur actuelle, modifiable, ainsi que des dossiers avec plus d’options et des sous-dossiers. Pour tout sauvegarder, il y a un bouton en haut du buffer. Cela écrira dans le fichier .emacs, par défaut.

Cette méthode a cependant un défaut : les modifications que vous apportez à votre configuration sont ensuite difficilement rectifiables directement en éditant le fichier produit par l’outil. De même, les changements effectués en dehors du mode customize, tels que ceux opérés sur le fichier .emacs, se marient assez mal avec celui-ci.

De plus, d’une version à l’autre d’Emacs, customize ne semble pas demeurer totalement rétro-compatible dans le sens où le fichier généré par customize dans une version récente risque de ne pas fonctionner correctement avec une version précédente.

Pour illustrer mon propos, je vais prendre mon exemple. J’ai un thème de couleurs grandement personnalisé. Je l’avais débuté étant sous Emacs 21, puis suis passé à Emacs 22 dès sa sortie. Je n’ai rencontré aucun problème de transfert vers la nouvelle version. Cependant, lorsque j’ai voulu prêter le fichier à quelqu’un qui était encore sous Emacs 21, celui-ci n’a pas pu le lire correctement !

Il y a peut-être une façon ninja de faire cette manipulation mais je ne suis pas au courant.

Par le fichier .emacs

Le fichier .emacs est un simple fichier contenant du texte. Simple ? pas vraiment en vérité. Le format du fichier est loin de ressembler à ce que vous avez sans doute l’habitude de voir avec d’autres configurations. La configuration d’Emacs est écrite en Lisp, plus précisément en Emacs Lisp. Si vous ne le savez pas, le Lisp est un langage de programmation à part entière !

En effet, la spécificité d’Emacs est d’être entièrement (ou presque) programmable, grâce à son langage intégré. Tout (ou presque) dans Emacs est Lisp. Les divers modes et applications dans Emacs sont codés en Emacs Lisp, et de même la configuration est écrite en Lisp.

Même lorsque vous éditez votre configuration à l’aide des menus ou de customize, Emacs retranscrit vos modifications en Lisp et les sauvegarde dans un fichier.

En réalité, le fichier .emacs est aussi appelé fichier d’initialisation car c’est simplement un programme écrit en Emacs Lisp qui est exécuté au démarrage d’Emacs.

L’intérêt de cette dernière méthode réside dans sa flexibilité ; vous pouvez presque tout faire rien qu’en écrivant du code Lisp dans votre .emacs, et même si toutes ces possibilités ne vous seront pas tout de suite utiles, c’est également l’approche qui s’adapte le mieux à une évolution vers une utilisation confirmée d’Emacs, qui implique l’apprentissage de l’Emacs Lisp.

Une approche conseillée

Vous l’aurez compris, je vous recommande chaudement de configurer votre Emacs en éditant le fichier .emacs par vous-même. Cependant, le mode customize a un usage très important : parcourir l’ensemble des paramètres disponibles pour une catégorie donnée. Pour cela, vous pouvez utiliser la commande customize-group qui vous permet d’aller directement à la configuration d’un groupe d’options particulier, dont vous devez donner le nom. Généralement, c’est le nom correspondant à l’application ou au mode que vous voulez configurer.

Par exemple, pour voir les possibilités de personnalisation offertes par ERC, le client IRC, vous exécuteriez la commande customize-group erc. Nous verrons plus loin comment mieux exploiter ces informations ; pour l’heure, vous ne pouvez sans doute que lire les descriptions d’options.

Une autre utilisation de customize que j’admets sans vraiment conseiller est la mise en place d’un thème de couleurs. Le faire sans customize est vraiment très pénible ; je l’ai fait, une fois, pour mon thème de couleurs basique, pour la console, mais je ne le referai pas, je crois. Nous verrons plus loin comment changer les polices et les décorations du texte, pour créer un environnement un peu plus agréable que le look par défaut.

Premiers pas

Aperçu de la syntaxe Lisp

Configurer Emacs ne requiert pas de connaître parfaitement l’Emacs Lisp, mais quelques rapides notions sont toutefois recommandées pour comprendre la syntaxe des lignes d’exemples à venir.

Le Lisp est un langage possédant une syntaxe extrêmement régulière. Toutes les constructions (ou presque) prennent la forme suivante :

(OPÉRATEUR OPÉRANDE ...)

Si vous avez déjà un peu programmé, sachez qu’OPERATEUR peut être aussi bien une fonction qu’une instruction spéciale (un if, un while, etc.). Sinon, retenez simplement que le premier mot dans la liste indique l’action à effectuer.

Et sans le vouloir, nous venons d’introduire la notion de liste. Une liste, c’est exactement ce que je viens de vous montrer, une suite d’éléments entre parenthèses, séparés par des blancs. Tout le code écrit en Emacs Lisp est essentiellement constitué de listes donc !

Commentaires

Tous les langages de programmation civilisés possèdent leur syntaxe pour inclure des commentaires dans le code, des lignes qui ne seront pas interprétées par Emacs. Il suffit de pour cela de précéder le texte à commenter par un point-virgule (ou plusieurs).

;; Ceci est un commentaire.

Environnement

La première chose à savoir est qu’il existe plusieurs fichiers similaires au .emacs qui contiennent divers morceaux de configuration. Principalement, on peut prendre note des fichiers suivants :

$HOME/.emacs.el ou $HOME/.emacs

Votre fichier d’initialisation principal. Les fichiers de code Emacs Lisp sont habituellement nommés avec un suffixe .el mais le .emacs peut faire exception. Il existe quelque différences, mais elles ne nous gêneront pas ici et je me référerai toujours à ce fichier comme le .emacs bien que je l’ai moi-même appelé .emacs.el.

$PREFIX/share/emacs/site-lisp/site-start.el

Le fichier global d’initialisation. Il joue le même rôle que le .emacs dans votre dossier personnel mais est lu avant votre .emacs. Il est souvent rempli à l’avance par votre installation dans le cas des gestionnaire de paquets, qui s’en servent pour charger des informations utiles sur les applications tierces qu’ils installent pour Emacs.

$PREFIX est la racine où vous avez installé Emacs. Généralement $PREFIX vaut /usr ou /usr/local ; sur certains systèmes, cela peut être /usr/pkg ou, si vous l’avez installé dans votre dossier personnel, $HOME/local.

fichiers de configuration des applications

Emacs est un environnement de travail qui peut accueillir des applications (spécifiquement développées pour Emacs). Chaque application possède souvent son propre fichier qui est lu au chargement de celle-ci. Par exemple, Mew utilise $HOME/mew.el.

fichiers de configuration auxiliaires

Vous pouvez également scinder votre .emacs en plusieurs fichiers, lorsque celui-ci deviendra relativement volumineux. Ce ne sera pas le cas aujourd’hui, aussi, nous n’aborderons pas ce sujet dans cet article.

Premier réglage : le système de codage de caractères

Emacs étant avant tout un éditeur de texte, son interface ainsi que la plupart des informations qu’il lit, stocke et échange demeurent sous forme textuelle. Un réglage très global et très important est donc le système de codage par défaut.

Pour ceux d’entre vous qui ne savent pas ce qu’est un système de codage de caractères, c’est simplement la représentation numérique (« binaire ») des caractères (lettres, chiffres, symboles) qui forment le texte. Pour plus d’informations, vous pouvez consulter la page Wikipédia sur le codage de caractères.

Il existe plusieurs options pour configurer en finesse les différents codages ; le réglage principal est obtenu par la commande set-language-environment. La syntaxe est la suivante :

(set-language-environment "NOM")

Il faut bien sûr remplacer NOM par le nom de l’environnement de votre choix. Les valeurs d’intérêt pour nous seront "UTF-8" si vous souhaitez avoir un environnement utilisant principalement l’UTF-8, ou "Latin-1" si vous voulez de l’ISO-8859-1. Les deux sont capables de représenter les caractères français ; l’UTF-8 permet en plus les caractères internationaux, provenant d’autres langues. Si vous ne savez pas pour lequel opter, je vous conseille l’UTF-8 ; la tendance actuelle va vers UTF-8 et les deux environnements de bureau dominants, à savoir GNOME et KDE, l’utilisent, par défaut.

Rajoutez la ligne qui convient à votre .emacs. S’il n’existe pas, créez le.

Organisation de vos fichiers

Votre .emacs ne contenant actuellement qu’une seule ligne, il semble superflu d’essayer de le diviser en plusieurs fichiers. Toutefois, en prévision des modifications que nous allons effectuer par le biais de customize, afin de mettre en place un thème de couleurs, un petit arrangement s’impose.

Par défaut, customize s’amuse à écrire dans votre .emacs, ce qui est très mal venu, surtout si vous utilisez un gestionnaire de versions comme RCS ou Git.

Il est très simple cependant de l’instruire de sorte à ce que le contenu soit placé dans un fichier réservé, de manière civilisée. Le nom de ce fichier est donné par la variable custom-file.

Variables dans Emacs

Dans Emacs, une variable est l’association d’une valeur (« quelque chose ») à un nom (un symbole dans le vocabulaire emacsien).

Ici, le symbole est custom-file et la valeur est le nom du fichier.

On change la valeur d’une variable avec la forme :

(setq SYMBOLE VALEUR)

Dans notre cas, la ligne devient :

(setq custom-file "NOM-DU-FICHIER")

Remarquez que le nom du fichier est entre guillemets droits ; c’est la manière d’écrire du texte (des chaînes de caractères pour ceux qui ont quelque expérience en programmation) en Emacs Lisp.

Par exemple, vous pourriez appeler ce fichier .emacs-custom.el et le placer dans votre dossier personnel, comme ceci :

(setq custom-file "~/.emacs-custom.el")

J’ai fait exprès d’utiliser le tilde, au cas où vous douteriez qu’Emacs le reconnaisse à l’instar du shell.

La syntaxe d’affectation (changement de valeur d’une variable) est très utile pour la configuration et n’est pas propre à la variable custom-file ; beaucoup d’options sont modifiables de cette manière. J’aurais pu vous donner directement l’instruction tout entière, mais, désormais, vous saurez reconnaître une affectation. C’est un premier pas sur la voie du programmeur Emacs Lisp ! :)

Toutefois, dans ce cas précis, simplement changer la valeur de la variable ne suffit pas. Cette manipulation va dire à customize d’inscrire ses modifications dans le fichier, mais celui-ci ne sera pas lu par Emacs lui-même comme l’est votre .emacs (et par voie de conséquence, comme l’étaient les ajouts faits par customize à celui-ci, avant que nous ne décidions de les détourner).

Pour charger ce fichier dans la mémoire d’Emacs et l’exécuter, nous allons employer la fonction load comme ceci :

(load custom-file)

Cette ligne est à placer après avoir donné à la variable custom-file sa valeur, bien entendue ; en effet, on demande ici à Emacs de lire le fichier dont le nom est donné par la variable custom-file. On aurait pu écrire :

(load "~/.emacs-custom.el")

Variables dans customize

Maintenant que nous avons eu un bref aperçu des variables, rappelez-vous customize et de son utilisation en tant qu’explorateur d’options.

Dans l’interface de customize, vous trouverez beaucoup d’options de configurations ; chacune porte un nom, qui, en règle générale, correspond à celui d’une variable, avec des espaces à la place des tirets et des majuscules au début des mots. L’option Ceci Est Une Option est en vérité la variable ceci-est-une-option.

La description ainsi que les menus vous donnerons une petite idée des valeurs acceptables et vous pourrez modifier la variable dans votre .emacs. Remarquez que le menu offert par le bouton State vous propose de voir l’expression Lisp telle que vous pourriez vouloir l’inscrire dans votre .emacs.

Récapitulatif

Nous avons vu, dans cette partie, quelques réglages fondamentaux, ainsi que la syntaxe pour affecter une valeur à une variable. Votre .emacs devrait maintenant ressembler à ceci :

(set-language-environment "UTF-8")

(setq custom-file "~/.emacs-custom.el")
(load custom-file)

Changer l’apparence d’Emacs

Ce n’est certainement pas le plus utile, mais c’est une question qui revient fréquemment : comment changer l’apparence d’Emacs. La méthode présentée ici n’est peut-être pas la plus propre, mais c’est celle que je trouve la plus pratique, et pour quelque chose de secondaire tel que l’affichage, elle conviendra bien.

Changer la police d’affichage

Cette section n’a d’intérêt que si vous utilisez Emacs en mode graphique. Autrement, il s’agit de configurer votre console et non Emacs.

L’interface d’Emacs est majoritairement constituée de texte, il est important de configurer une police qui vous est agréable. La police par défaut doit être monospacée, mais cela mis à part, vous pouvez utiliser n’importe quelle police supportée par les applications graphiques. Sous X, cependant, la version 22 ne supporte pas encore le lissage des caractères. Je vous conseille donc de prendre une police disponible en format bitmap. Personnellement, j’utilise terminus pour Emacs 22.

Comme je l’ai indiqué auparavant, nous allons utiliser customize pour modifier les paramètres d’affichage. La commande M-x customize-face réalise cela.

Dans Emacs, « face » est le nom donné à un jeu d’attributs d’affichage. Il y en a pour chaque type de caractère que vous pouvez rencontrer dans un buffer : les caractères normaux, les mots-clés ou encore les commentaires, dans du code.

Emacs vous demande alors quel type de caractères modifier. Entrez default pour personnaliser le type par défaut, dont les attributs sont hérités par tous les autres, si ceux-ci ne fournissent pas les leurs. Vous devriez alors obtenir une interface semblable à celle représentée ci-dessous :

Default face: (sample) Hide Face
   State: SAVED and set.
Basic default face.
Parent groups: Basic Faces
Choice: Value Menu
 Attributes: [X] Font Family: xos4-terminus
             [X] Width: Value Menu medium
             [X] Height: Value Menu Height in 1/10 pt: 151
             [X] Weight: Value Menu medium
             [X] Slant: Value Menu normal
             [X] Underline: Value Menu Off
             [X] Overline: Value Menu Off
             [X] Strike-through: Value Menu Off
             [X] Box around text: Value Menu Off
             [X] Inverse-video: Value Menu Off
             [X] Foreground: gray88     (sample)
             [X] Background: black      (sample)
             [X] Stipple: Value Menu None
             [ ] Inherit: *

La plupart des champs devraient parler d’eux-mêmes. En vérité, pour le type par défaut, nous ne voulons sans doute pas altérer d’autres champs que Font Family, Height, Foreground et Background. Vous pouvez obtenir une jolie liste des couleurs et leurs noms avec la commande M-x list-colors-display.

Expérimentez un peu pour obtenir ce qui vous convient. Pour appliquer vos changements, actionnez le bouton State ; les choix proposés ne posent aucune difficulté. Faites plusieurs fois Set for Current Session, puis, lorsque vous êtes content de vos paramètres, choisissez Save for Future Sessions.

Cette manipulation est réalisable pour n’importe quel type ; pour avoir accès à la liste complète, validez sans fournir de nom, lors de l’invite offerte par M-x customize-face. Veuillez prendre note du fait que seuls les types existants seront ainsi disponibles. En particulier, si vous souhaitez personnaliser un module particulier, il faudra le charger avant d’utiliser M-x customize-face. La plupart des modules se chargent automatiquement lorsque vous tentez d’accéder à un de leurs modes.

Si vous voulez changer la manière dont s’affichent les titres sous le mode LaTeX, tentez de visiter un fichier LaTeX avant d’utiliser M-x customize-face.

Un moyen pratique de savoir quel type personnaliser est de placer votre curseur sur un caractère présentant les propriétés que vous voulez changer et d’invoquer M-x customize-face ; l’invite vous proposera alors par défaut de changer le type dont est affecté ce caractère !

Composants de l’interface

Après ce petit intermède en compagnie de customize, revenons à l’édition de notre .emacs.

Par défaut, Emacs affiche beaucoup de composants sans grand intérêt dans son interface : un menu, une barre d’outils et même des barres de défilement, mais oublie certaines informations importantes telles que le numéro de la colonne à laquelle le curseur se situe. Nous allons au plus vite remédier à cela.

Le menu, les barres d’outils et de défilement peuvent être supprimés avec les instructions suivantes :

(menu-bar-mode -1)
(tool-bar-mode -1)
(scroll-bar-mode -1)

L’affichage du numéro de la colonne est obtenu par l’instruction :

(column-number-mode 1)

Le tout est bien sûr à placer dans votre .emacs, à la suite des changements précédents. Voilà déjà quelques lignes de gagnées dans votre espace de travail !

Autres paramètres d’affichage

Terminons notre petit tour d’horizon des options de configuration de l’interface d’Emacs par quelques paramètres que vous pourriez vouloir et qui adressent chacune un aspect différent de l’affichage d’Emacs.

  1. La première est truncate-partial-width-windows. Par défaut, Emacs replie automatiquement les lignes de texte dans les fenêtres en plein écran, mais, si vous décidez de partager horizontalement votre Emacs en deux fenêtres côte à côte, vous vous apercevrez qu’Emacs tronque à présent les lignes trop longues. Pour empêcher cela, il faut changer la valeur de la variable truncate-partial-width-windows de t (qui signifie « vrai » en termes booléens) à nil (qui signifie « faux »).

    (setq truncate-partial-width-windows nil)
    

    Emacs possède de même tout un tas de paramètres qui vous permettent de changer la manière dont il affiche les éléments. N’hésitez pas à fouiller pour en trouver d’autres !

    Mentionnons, par exemple, un petit réglage de confort sous la forme de la variable ring-bell-function. Elle vous permet de spécifier une fonction à appeler au lieu de faire sonner la petite alarme (qui fait « bip ») interne de votre ordinateur. Il y a plusieurs valeurs possibles, mais un choix convenable est le suivant :

    (setq ring-bell-function 'ignore)
    

    La fonction ignore ne fait rien ; du coup, vous n’aurez plus droit à ces alarmes que personnellement je trouve perturbatrices.

  2. En deuxième, regardons comment on peut avoir l’heure tout en restant dans Emacs. Cela fait partie des nombreux gadgets (mais qui peuvent s’avérer utiles ; en l’occurrence, j’aime avoir l’heure dans Emacs) disponibles sous Emacs.

    Pour activer l’affichage de l’heure dans la barre de modes, inscrivez la ligne suivante dans votre .emacs.

    (display-time-mode t)
    

    Vous l’aurez peut-être remarqué, nous avons déjà vu plusieurs fonctions dont le nom se termine par -mode. Ici, ce sont des modes mineurs globaux. En d’autres termes, ce sont des fonctionnalités que vous pouvez activer ou désactiver. À la différence des variables, les modes fonctionnent dans une optique plus binaire : un mode est actif ou il ne l’est pas. Les variables, quant à elles, permettent de spécifier des valeurs particulières pour contrôler certains aspects du fonctionnement d’Emacs.

Changer les combinaisons au clavier

Maintenant que vous savez comment configurer l’apparence d’Emacs, nous allons nous intéresser aux combinaisons au clavier. Emacs étant un éditeur « orienté clavier », tout peut se faire au clavier. Mais cela signifie aussi qu’il est important d’avoir des raccourcis pratiques.

Cela ne signifie pas essayer de mettre Ctrl-C et Ctrl-V de partout. Cela ne marchera pas comme vous voudrez. Emacs a ses conventions et ses manières. Cette section vous apprend à ajouter des commandes facilement accessibles par une combinaison de touches ; elle n’est pas là pour vous inciter à faire de votre Emacs un Notepad++ ou je ne sais quoi encore.

Notations des combinaisons dans Emacs

Si vous avez un minimum d’expérience sous Emacs, vous le savez déjà, mais la notation des combinaisons de touches sous Emacs n’est pas la même que sous d’autres environnements. Chaque touche modificatrice (Ctrl, Alt, etc.) est représentée par une lettre et une combinaison est notée Mod-ResteMod est la lettre correspondant à la touche modificatrice et Reste est une combinaison.

Les correspondances sont les suivantes :

Shift S
Ctrl C
Meta (ou Alt) M

Par exemple, C-a est obtenu en maintenant Ctrl et en appuyant sur a, et C-M-f est la combinaison de Ctrl, Alt et f sur un clavier français usuel.

Combinaisons réservées

Emacs possède des conventions quant aux combinaisons que vous pouvez utiliser et à celles qui sont réservées à différentes parties de l’éditeur.

Il vous faut simplement retenir que vous êtes autorisé à créer des raccourcis débutant par C-c suivi d’une lettre pour vos besoins personnels, en tant qu’utilisateur. Les autres combinaisons commençant par C-c sont réservées aux modes majeurs et mineurs (ce n’est pas important si vous ne savez pas bien ce que sont les uns et les autres) et les autres espaces sont pris par Emacs lui-même ou des composants spécialisés.

Fonctions, commandes et combinaisons

Pour pouvoir associer des commandes aux combinaisons de touches, il faut d’une part savoir comment noter la combinaison et d’autre part connaître la fonction à lui associer.

Pour Emacs, chaque combinaison de touches invoque une commande (encore appelée fonction interactive). Une commande est une fonction, c’est-à-dire, dit simplement, une suite d’actions à effectuer, soit écrite en Emacs Lisp, soit écrite en C et connue nativement d’Emacs. Mais toutes les fonctions ne sont pas des commandes. Il faut en effet qu’elles soient prévues pour être appelées de manière interactive, par l’utilisateur. Toute fonction qui peut être appelée via M-x NOM-DE-LA-FONCTION est une commande ; le but de cette section est de vous permettre d’éviter d’avoir à taper le nom de la commande à chaque fois, en lui associant un raccourci clavier.

La fonction global-set-key

C’est la fonction permettant d’attacher une combinaison de touches à une commande, au niveau global, c’est-à-dire disponible partout dans Emacs. La syntaxe est la suivante :

(global-set-key COMBINAISON COMMANDE)

COMBINAISON est une séquence de touches. La manière la plus simple de l’écrire est d’utiliser la fonction kbd comme ceci :

(kbd DESCRIPTION)

DESCRIPTION est alors la description de touches telle qu’elle a été présentée ci-dessus, entre guillemets droits. Par exemple :

(kbd "C-c g")

Le tout est à mettre à la place de COMBINAISON.

COMMANDE est, dans sa forme la plus simple, et la seule que nous verrons dans cet article, le nom d’une fonction précédé d’une apostrophe ; par exemple : 'replace-string.

Imaginons que vous vouliez attacher la commande replace-string à la combinaison C-c h. Vous pourriez écrire la ligne suivante dans le .emacs :

(global-set-key (kbd "C-c h") 'replace-string)

Dorénavant, lorsque vous rencontrerez une commande intéressante, sur un wiki, un blog, ou dans le .emacs de quelqu’un d’autre, vous saurez comment l’affecter aux touches de votre choix !

Quelques commandes utiles qui ne demandent qu’à être affectées

Commande Description rapide
replace-string remplace un texte par un autre dans le buffer
replace-regexp pareil que replace-string avec une regexp
bury-buffer cycle parmi vos buffers
kill-this-buffer détruit le buffer et ferme le fichier
compile invoque make pour compiler vos fichiers sources
gdb invoque gdb pour déboguer vos programmes

Configurer Emacs pour programmer en C

Si vous avez lu et appliqué mes instructions jusqu’ici, votre Emacs devrait être suffisamment configuré pour l’édition de fichiers de texte. La plupart des utilisateurs d’Emacs le sont cependant pour leurs besoins en matière de programmation. Aussi, cette avant-dernière section adresse quelques points de configuration d’Emacs pour pouvoir écrire du C dans de bonnes conditions.

D’autres modes de langages de programmation ont des paramétrages semblables, mais il n’y a pas de règle vraiment générale. Il vous faudra lire les manuels correspondants.

Style d’indentation

L’une des fonctionnalités les plus appréciées d’Emacs est sa capacité à indenter intelligemment le code selon le contexte. Le mode d’édition du C, notamment, possède un indenteur relativement performant. Cependant, par défaut, celui-ci suit les règles de présentation du GNU. Si comme moi cela vous contrarie fortement, il est aisé de changer de style. La variable c-default-style contient le nom du style à utiliser, sous forme d’une chaîne de caractères (entre guillemets). Les valeurs habituelles sont les suivantes :

"k&r" le style K&R, aussi appelé style kernel
"bsd" le style Allman, aussi appelé style BSD
"whitesmith" le style Whitesmiths
"gnu" le style GNU, celui par défaut

Par exemple, si vous utilisez le style K&R, vous écrirez :

(setq c-default-style "k&r")

Pas d’indentation, espaces et tabulations

En plus du style, il y a également une histoire de pas (ou largeur) d’indentation. Par exemple, par défaut, Emacs indente vos blocs de 4, en style K&R. Personnellement, j’utilise un pas de 8. Cela est obtenu en changeant la valeur de la variable c-basic-offset. Pour moi :

(setq c-basic-offset 8)

De plus, la variable indent-tabs-mode contrôle l’usage des tabulations pour l’indentation. C’est une variable booléenne. Une valeur vraie (t) signifie que les tabulations seront utilisées dès que possible tandis qu’une valeur fausse (nil) obligera Emacs à n’utiliser que des espaces pour indenter. Par défaut, elle vaut t. Je vous laisse deviner l’instruction à placer dans votre .emacs si vous voulez des espaces !

Autres options

Le mode C étant l’un des plus populaires, il possède son lot de paramètres intéressants ou obscurs que je vous laisse découvrir en compagnie de la documentation. Vous pourriez vouloir personnaliser un peu plus le style d’indentation en modifiant la variable c-offsets-alist. Je ne détaillerai pas son fonctionnement ici, mais n’hésitez pas à consulter mon .emacs pour un petit exemple.

Avec tout cela, l’édition du code C devrait maintenant être tout à fait satisfaisante. Ainsi, votre Emacs devrait maintenant être relativement complet pour un début. Cet article s’achève ici et je vous laisse comparer le résultat de votre apprentissage avec un exemple de fichier .emacs écrit d’après ce tutoriel.

Exemple final d’un fichier .emacs basique

;; Environnement
(set-language-environment "UTF-8")

(setq custom-file "~/.emacs-custom.el")
(load custom-file)

;; Affichage
(menu-bar-mode -1)
(tool-bar-mode -1)
(scroll-bar-mode -1)

(column-number-mode t)

(setq truncate-partial-width-windows nil)
(setq ring-bell-function 'ignore)

(display-time-mode t)

;; Raccourcis
(global-set-key (kbd "C-c h") 'replace-string)
(global-set-key (kbd "C-c j") 'replace-regexp)
(global-set-key (kbd "C-c o") 'bury-buffer)
(global-set-key (kbd "C-c k") 'kill-this-buffer)
(global-set-key (kbd "C-c c") 'compile)
(global-set-key (kbd "C-c g") 'gdb)
[ tag:blog.huoc.org,2009:posts/39 ]
Aucun commentaire · Commenter

/\ \/

Écrire du code C avec Emacs

#. Par rz0. Mis à jour le 20.02 2010 à 10:02. Aucun commentaire.
c débutants emacs pratique programmation tutorial

Et de deux ! —rz0

Cet article d’introduction s’intéresse aux aspects basiques liés à l’écriture et à la gestion du code C basique avec Emacs, et, bien sûr, aux fonctionnalités qui vous y aideront.

Édition du code avec le c-mode

Le mode d’édition du code C dans Emacs se nomme très simplement le c-mode et est inclus par défaut avec l’éditeur ; vous pouvez aussi obtenir la dernière version du mode (qui est développé en dehors d’Emacs lui-même) sur le site du cc-mode. Le c-mode offre plusieurs fonctionnalités et commandes intéressantes.

Indentation automatique

Tout d’abord, comme tous les modes de programmation dans Emacs, le c-mode possède l’indentation automatique. Cela signifie que la touche TAB (ou, de manière équivalente, C-i) n’est pas utilisée pour insérer un caractère « tabulation » mais pour indenter la ligne actuelle, c’est-à-dire aligner le début de celle-ci sur la colonne adéquate, comme vous le feriez vous-même si Emacs n’était pas là. Il est important de remarquer que cette opération ne tient absolument pas compte de l’indentation actuelle de la ligne : qu’elle soit trop décalée à gauche ou à droite, Emacs la replacera au bon endroit.

Emacs indentera la ligne selon deux informations : le contexte syntaxique (si vous êtes au début d’une instruction, au milieu d’un appel de fonction, etc.) et le style sélectionné (par exemple, K&R ou BSD ; voyez mon introduction à la configuration d’Emacs pour plus d’informations).

Comme il peut devenir lassant de devoir appuyer (quoique ce ne soit qu’une seule fois) sur la touche TAB à chaque début de ligne, sachez que vous pouvez aller à la ligne et indenter en une seule commande : C-j (newline-and-indent).

Mais l’indentation d’Emacs ne se limite pas à la ligne actuelle ! Vous pouvez également lui demander d’indenter tout un bloc ou une fonction pour vous. Il s’agit respectivement des commandes C-M-\ (indent-region) et C-c C-q (c-indent-defun), qui indentent la région actuelle (la zone de texte située entre la marque, usuellement apposée par C-SPC, et le curseur) et la fonction dans laquelle se trouve le curseur. En théorie, vous pourriez indenter tout le fichier ainsi, mais je vous le déconseille ; lalgorithme est lent et perd en précision si les constructions sont trop vilaines ou complexes. En revanche, si vous effectuez une modification au milieu d'un bloc, et que, par exemple, vous supprimez un niveau d'indentation, ces commandes peuvent s'avérer très utiles. Parfois, en revanche, vous voulez aligner des éléments (comme le ferait la toucheTABen temps normal) sans qu'Emacs n'essaie d'indenter la ligne. Pour cela, vous pouvez utiliser la commandeM-i(tab-to-tab-stop). C'est le cas, par exemple, si vous alignez vos déclarations ou vos instructions dans unswitch. Pour résumer, voici un tableau des commandes d'indentation et d'alignement : |TAB | indente la ligne actuelle | |C-j | retour à la ligne et indentation simultanés | |C-M-‘ | indente la région actuelle

C-c C-q indente la fonction actuelle
M-i aligne le curseur sur la prochaine colonne de tabulation

Gestion des commentaires

De même qu’Emacs sait indenter le code automatiquement, il sait insérer des commentaires au bon endroit ; pour se faire, il vous suffit d’actionner la commande M-; (comment-dwim). Que vous soyez sur une ligne vide ou non, Emacs déterminera la syntaxe à adopter. C’est la commande idéale pour ceux qui trouvent les commentaires de type C (/* ... */) trop longs à taper.

De plus, Emacs dispose des commandes de traitement de régions suivantes : comment-region et uncomment-region. Elles sont accessibles par M-x et qui peuvent être associées à une combinaison de votre choix si vous en sentez le besoin. Je vous laisse deviner ce qu’elles font. Elles peuvent être utiles à ceux qui aiment isoler des morceaux de code avec des commentaires (personnellement, je préfère utiliser #if 0).

M-; insère un commentaire

Déplacements contextuels

Enfin, le c-mode dispose de mécanismes de navigation contextuelle. La plupart des commandes utilisées sont communes à tous les modes d’édition mais leur fonctionnement a été adapté à la structure du code C.

Vous pouvez, comme toujours, vous déplacer de mot en mot avec les habituels M-b (backward-word) et M-f (forward-word).

Juste au niveau supérieur, les commandes C-M-b (backward-sexp) et C-M-f (forward-sexp) vous permettent de sauter d’un bout à l’autre d’expressions équilibrées (un groupe entre parenthèses, crochets ou accolades). En complément, C-M-u (backward-up-list) et C-M-d (down-list) naviguent à travers les niveaux de parenthésage : C-M-u vous amène au début du groupe dans lequel se situe le curseur et C-M-d vous transporte au début du prochain groupe à l’intérieur de l’expression actuelle. Le plus simple est encore de les essayer pour en voir les effets !

Vous pouvez également vous déplacer au début et à la fin des instructions avec M-a (c-beginning-of-statement) et M-e (c-end-of-statement), respectivement.

Enfin, les commandes beginning-of-defun et end-of-defun font, sans surprise, le voyage au début et à la fin d’une définition de fonction.

Récapitulons les commandes directement accessibles :

C-b, C-f déplacement au mot
C-M-b, C-M-f déplacement à l’expression (équilibrée)
C-M-u, C-M-d déplacement à l’expression (équilibrée)
M-a, M-e déplacement à l’instruction

Plusieurs fichiers sources

Avec ce que nous venons juste de voir, nous sommes maintenant capables d’éditer un fichier source C. Toutefois, la plupart des projets ne sont pas constitués d’un seul fichier, aussi, nous allons nous pencher sur la gestion de plusieurs fichiers dans Emacs. Cette section n’est pas directement liée à la programmation en C, mais la question revient fréquemment chez les novices, et c’est pourquoi j’ai décidé de la traiter ici.

Hiérarchies de fichiers

Typiquement, le scénario est le suivant : vous possédez plusieurs fichiers sources regroupés dans un dossier. Quelque chose que beaucoup de gens aiment faire est pouvoir regarder la hiérarchie des fichiers et naviguer de fichier en fichier. Pour cela, Emacs offre deux mécanismes : dired, un explorateur de fichiers, et la speedbar, une barre latérale.

Selon vos goûts, vous préférerez sans doute l’un ou l’autre. Les différences sont multiples : dired possède plus de facilités pour la gestion des hiérarchies de fichiers et de dossiers, avec toutes les fonctions classiques que l’on pourrait attendre d’un explorateur de fichiers, et se présente comme une application Emacs à part entière, utilisant ses propres buffers. À l’opposé, la speedbar s’affiche dans un cadre (frame) à part, et son contenu est constamment recalculé de sorte que le dossier affiché dans la barre soit toujours celui du fichier actuel.

Si vous n’êtes pas habitué à la terminologie emacsienne, sachez qu’une fenêtre correspond à une division de l’affichage d’Emacs (ce que vous obtenez avec C-x 2 par exemple) tandis qu’un cadre est un ensemble de fenêtres indépendant, ce qui, en mode graphique, correspond, pour la plupart des gestionnaires de fenêtres, à ce qu’ils appellent une fenêtre. Sauf mention contraire, nous utiliserons ici le vocabulaire d’Emacs.

À l’usage, l’un ou l’autre est assez naturel. Pour lancer dired, utilisez la commande C-x d (dired) ou C-x C-f (find-file) avec le chemin d’un dossier. Pour lancer la speedbar, tapez M-x speedbar ; un cadre devrait apparaître à côté de votre cadre principal.

Une fois à l’intérieur de l’un ou l’autre, placez-vous sur un fichier et tapez RET (dired-advertised-find-file ou speedbar-edit-line) pour le visiter. Sous dired, vous avez l’option de visiter le fichier dans une autre fenêtre avec o (dired-find-file-other-window).

Si vous voulez parcourir des sous-dossiers, sachez que dired prône une disposition plane tandis que la speedbar affiche les dossiers et les fichiers sous forme arborescente. Lorsque votre curseur est sur une ligne correspondant à un sous-dossier, tapez i sous dired ou + dans la speedbar (respectivement, dired-maybe-insert-subdir et speedbar-expand-line) pour insérer son listage dans le buffer actuel. Pour supprimer l’affichage d’un sous-dossier, placez-vous sur son en-tête (son chemin, juste au-dessus du listage, pour dired, ou son nom dans l’arborescence, pour la speedbar) et exécutez la commande M-x dired-kill-subdir ou - (speedbar-contract-line), pour dired et la speedbar respectivement. Au lieu de l’effacer complètement, dired vous propose aussi d’utiliser la commande $ (dired-hide-subdir), qui, malgré son nom, permet de cacher et de découvrir le contenu du dossier.

Si votre gestionnaire de fenêtres ne vous permet pas de déplacer facilement le focus au clavier, sachez qu’Emacs vous permet d’aller de cadre en cadre avec C-x 5 o (other-frame).

Et comme précédemment, un petit récapitulatif :

  dired speedbar
parcourir un dossier C-x d automatique
visiter un fichier RET RET
insérer un sous-dossier i +
cacher un sous-dossier $ -

Plusieurs fichiers dans Emacs

Maintenant que vous savez accéder facilement à plusieurs fichiers de votre projet, vous risquez fort d’être vite submergé par le grand nombre de buffers ouverts. Emacs ne dispose pas de tabs (ce n’est pas comme si vous alliez vous servir de votre souris pour y accéder, de toute manière). En revanche, plusieurs modes sont disponibles pour gérer vos buffers. Je ne vais pas tous les passer en revue ici, je ne donnerai qu’une seule solution.

La première commande à connaître pour bien gérer vos multiples buffers est sans doute C-x C-b (list-buffers). Elle démarre le gestionnaire de buffers. Ce dernier se présente comme une simple liste des buffers ouverts. Vous pouvez y effectuer les opérations de navigation habituelles, dont la recherche (avec C-s (isearch-forward) pour les étourdis). RET (Buffer-menu-this-window) vous permet de visiter le buffer à la ligne actuelle. Une autre utilisation très pratique est de supprimer plusieurs buffers à la fois. Pour cela, marquez chaque buffer à détruire avec la commande d (Buffer-menu-delete). La lettre D apparaît alors à gauche du nom. La commande x (Buffer-menu-execute) effectue l’opération de nettoyage en elle-même. Ainsi, si vous vous êtes trompé dans votre marquage, vous pouvez revenir en arrière en utilisant la commande u (Buffer-menu-unmark) sur la même ligne.

Tout cela est bien utile pour faire le ménage mais ne vous aide guère pour votre besoin le plus simple : naviguer de buffer en buffer. Pour cela, il y a bien sûr la commande C-x b (switch-to-buffer) par défaut d’Emacs pour changer de buffer. Mais elle n’est pas très pratique ; en effet, elle demande d’entrer le début du nom pour accéder au buffer. Fort heureusement, il existe (au moins) un mode qui vous permet de changer de buffer en tapant quelques lettres n’importe où dans le nom. Je vous présente là l’iswitchb-mode. Pour l’essayer, il vous suffit d’effectuer la commande M-x iswitchb-mode ; de même pour le désactiver. Pour l’adopter définitivement, ajoutez à votre .emacs la ligne :

(iswitchb-mode t)

La combinaison C-x b invoquera à présent iswitchb-buffer. Si vous l’essayez, vous remarquerez que la liste des buffers qui sont encore candidats à la sélection est maintenant affichée à côté de votre saisie. Dans ce mode, les commandes C-r (iswitchb-prev-match) et C-s (iswitchb-next-match) vous permettent de faire défiler les éléments de la liste de manière cyclique, et RET sélectionne le premier buffer de la liste. Ainsi, vous n’aurez plus à taper le début exact du nom que vous voulez, il vous suffira simplement d’entrer quelques lettres remarquables et, moyennant quelques rotations de la liste, vous arriverez rapidement à destination.

Vous pouvez aussi demander à utiliser des expressions régulières. La commande C-t (iswitchb-toggle-regexp), dans l’iswitchb-mode, vous permet cela. Par exemple, pour obtenir une liste des fichiers C ouverts, vous pourriez utiliser le motif \.c$. Veuillez cependant prendre note du fait que les expressions régulières d’Emacs diffèrent d’autres syntaxes, soyez donc averti et pensez à lire le manuel (avec C-h i (info), par exemple) si vous désirez en faire bon usage.

Avec l’habitude, ce système très flexible remplace avantageusement les tabs. Vous n’êtes plus lié à une liste prédéfinie ; vous avez la possibilité de fabriquer rapidement la vôtre, adaptée à votre requête. Du reste, il vous permettra de même d’aller de buffer en buffer. Et tout cela pour un encombrement nul dans vos autres actions !

Et pour les petits étourdis :

C-x C-b gestionnaire de buffers
C-x b change de buffer
C-r et C-s défilement de la liste (dans iswitchb)
C-t activation des regexps (dans iswitchb)

Édition simultanée de plusieurs fichiers

Bien sûr, vous n’êtes pas encore capable de vous dédoubler pour éditer plusieurs fichiers à la fois, mais parfois, vous pourriez vouloir afficher deux fichiers (voire plus) côte à côte.

Pour se faire, rien de plus simple. Il vous suffit de partager votre affichage en deux : verticalement avec C-x 2 (split-window-vertically) ou horizontalement avec C-x 3 (split-window-horizontally). Pour passer d’une fenêtre à l’autre, utilisez C-x o (other-window). Enfin, C-x 0 (delete-window) et C-x 1 (delete-other-windows) vous permettent respectivement de supprimer la fenêtre sur laquelle se trouve le focus et de ne laisser qu’elle.

Ainsi, vous pourrez plus facilement comparer ou transférer du code d’un fichier à un autre.

Sachez également qu’il existe des commandes qui marient une action donnée à un changement de fenêtre, telles queC-x 4 f (find-file-other-window) et C-x 4 b (switch-to-buffer-other-window). La première ouvre un fichier, à l’instar de C-x C-f (find-file) mais l’affiche dans une fenêtre séparée. La seconde agit comme C-x b (switch-to-buffer) mais, de même, place le contenu dans une autre fenêtre. Si le cadre n’est constitué que d’une fenêtre, celui-ci est automatiquement scindé en deux.

C-x 2, C-x 3 création de fenêtres
C-x o déplacement de fenêtre à fenêtre
C-x 0, C-x 1 suppression de fenêtres
C-x 4 f visite un fichier dans une autre fenêtre
C-x 4 b affiche un buffer dans une autre fenêtre

Gestion de l’historique des modifications avec VC

Le dernier outil essentiel à tout projet sérieux est un gestionnaire de versions. Si vous n’êtes pas familier avec ce concept, voici une brève introduction. Vos fichiers seront amenés à grandement évoluer avec les phases d’essai, de correction, d’essais publics, d’amélioration, etc. Il est donc primordial de contrôler ces changements sous forme de versions. Des logiciels spécialisés appelés « systèmes de contrôle de versions » (VCS, Version Control Systems en anglais) permettent de faire cela. Nous n’entrerons pas ici dans le détail du fonctionnement de ceux-ci, mais le principe général est le suivant : chaque fichier est associé à une copie persistante, qui représente tous ses états depuis le début, toutes ses versions, sous forme de différences. Cela signifie qu’une version n’est pas stockée en entier mais uniquement sous une forme relative à une version voisine. Dans sa réalisation la plus classique, par exemple, si vous ajoutez une ligne à un fichier, le fichier de contrôle, contenant la copie persistante, enregistrera une différence d’une ligne par rapport à la version précédente.

Emacs intègre un support de certains de ces systèmes, dont RCS, CVS, SVN et Git. Pour illustrer cette intégration, qui porte le petit nom de VC pour, vous l’aurez deviné, Version Control, nous allons nous intéresser au plus simple d’entre eux : RCS. Mais les commandes demeurent approximativement les mêmes quel que soit le programme de gestion de versions.

Traditionnellement, les fichiers de contrôle de RCS sont stockés localement dans un sous-dossier de celui où se situent vos fichiers sources, ce sous-dossier portant le nom RCS. Si vous ne le créez pas par vous-même, Emacs vous le proposera en temps voulu.

L’utilisation de VC repose sur la commande C-x v v (vc-next-action). Comme son nom l’indique, elle fait… des choses, selon l’état actuel du fichier. Avec RCS, un fichier peut avoir plusieurs états. Dans son utilisation classique, ce sont les suivants.

  1. Au départ, le fichier est inconnu de RCS. Lorsque vous l’enregistrez auprès de ce dernier (avec C-x v v), celui-ci s’approprie le contenu. Le fichier et la version la plus récente connue de RCS sont alors les mêmes.

  2. À ce stade, vous ne pouvez pas modifier le fichier à nouveau. Il vous faut prévenir RCS de votre intention, ceci afin d’empêcher que plusieurs personnes ne modifient le même fichier en même temps. Cette action s’appelle le verrouillage et s’effectue aussi par C-x v v. Une fois le fichier verrouillé, vous êtes sûr qu’il est la seule copie de travail existante.

  3. Une fois vos modifications effectuées, C-x v v synchronise votre brouillon avec la copie de contrôle. VC vous demande alors de fournir un commentaire décrivant les changements, commentaire qui sera inscrit dans une espèce de journal de bord du fichier. La fenêtre d’édition du message est tout à fait banale et la validation est obtenue par le traditionnel C-c C-c, valable pour la plupart des formulaires sous Emacs. Après cette étape, le fichier et la version la plus récente connue de RCS sont à nouveau les mêmes.

Tout ceci est bien joli, mais à quoi serviraient un historique et un journal que l’on ne peut consulter ?

La commande C-x v ~ (vc-version-other-window) vous permet d’accéder au contenu d’une version antérieure de votre fichier. Le numéro de la version vous sera demandé. Mais que faire si vous ne vous rappelez plus de la bonne version ? C’est à cela que servent les commentaires que vous avez pris soin d’entrer tout au long de votre travail ! Pour voir la liste des versions ainsi que les messages associés, tapez C-x v l (vc-print-log).

Une fois la version voulue récupérée dans un buffer secondaire, vous pouvez afficher les deux buffers en parallèle comme nous avons vu précédemment. Mais VC propose une commande intéressante pour vous aider à voir les différences entre vos versions : C-x v = (vc-diff). Elle vous propose une comparaison rapide de deux versions, avec la différence placée dans un buffer à part.

Cela peut sembler beaucoup de choses à enregistrer si vous n’avez jamais utilisé de VCS auparavant, mais j’ai moi-même débuté dans ce domaine avec VC qui est, à mon avis, un moyen simple de se familiariser avec le concept tout en restant bien au chaud dans son éditeur préféré. Et pour vous y aider, voici un… récapitulatif ! Avouez que vous ne vous y attendiez pas.

C-x v v change d’état
C-x v ~ ressuscite une version
C-x v l invoque la liste des versions
C-x v = compare deux versions

Cette présentation de VC et de RCS achève ce premier article. Dans le prochain chapitre (peut-être…α), nous parlerons de la compilation de fichiers et de projets écrits en C, à l’aide de makefiles, le tout depuis Emacs. Contrairement à ce premier tutoriel, il sera bien moins porté sur Emacs lui-même que sur les outils à mettre en œuvre pour compiler depuis Emacs, c’est-à-dire GCC et make.

α : L’histoire nous a montré que ce « peut-être » a su se transformer en « non ». :)

Je remercie Metzgermeister et nicofrand pour la relecture de cet article.

[ tag:blog.huoc.org,2009:posts/40 ]
Aucun commentaire · Commenter

/\ \/

Conventions : le retour

#. Par rz0 dans Le code et ses raisons. Publié le 25.08 2009 à 14:46. Aucun commentaire.
>. Le code et ses raisons : goto en C
c débutants idiomes pratique programmation

Comme il ne coûte pas cher de déterrer des brouillons presques prêts pour en faire des articles, voici le retour de l’article sur les conventions que j’avais commencé à écrire, et duquel avait été tiré mon petit poème.

Ceci est donc un article orienté débutants. « Woah, rz0 il fait des articles de vulgarisation maintenant. » Et ouais les cocos, ce sont des choses qui arrivent, dans la vie. Parfois, en se levant le matin, et qu’il est déjà midi, on n’a pas envie de plancher tout de suite sur la refactorisation qui a échoué la veille. Bref, trève de bavardage.

Les conventions, c’est quoi ? Le principe est simple, c’est celui de la bonne ptite ouvrière : faire la même chose toujours de la même manière. Oubliez vos pulsions d’artiste et embrassez pleinement la voie de la machine (l’autre voie de la machine, pour ceux qui sont déjà en prépa) !

Et je ne parle pas ici de ne pas nommer une variable prgcnt et une autre MyFckngVisitorCount juste à côté, ça c’est la différence entre lEs GenS Ki ÉkrIv KoM sA et le monde civilisé.

Non, l’idée est que si une variable assure un certain rôle plusieurs fois, alors elle devrait avoir le même nom à chaque fois. Un peu comme les païens du C++ appellent toujours (sont toujours forcés d’appeler) le pointeur sur l’objet manipulé this. Si vous écrivez une bibliothèque de tableaux dynamiques, vous pouvez par exemple décider que n correspond toujours au nombre d’éléments du tableau actuellement considéré.

Heureusement, Le Créateur a pensé à vous, et les anciens vous ont légué une pelleté de noms prêts à l’emploi. La bonne nouvelle, c’est que vous pouvez les utiliser. La mauvaise, c’est que vous devez les utiliser. (Et c’est ici qu’intervenaient mes talents poétiques.)

Car pour la descendance, Dieu l’ordre dicta :
Pour les itérations, les compteurs i, j, k ;
n, m : des quantités ;
p, q devront pointer ;
c pour les caractères ;
s et t pour les chaînes.
Mais ni o ni l, les suppôts du binaire,
Pour que le programmeur toujours lise sans peine.

Mais il est souvent tout aussi utile de définir vos propres conventions, pour vos objets, vos types, vos règles du jeu. Ou simplement de préciser les règles traditionnelles (comme nous l’avons vu juste au-dessus avec n).

Toujours utiliser les mêmes noms pour les variables de mêmes rôles vous évitera d’avoir à commenter leur déclaration avec des banalités du genre :

int lol;      /* Nombre de cailloux dans la marre. */

Remarquez aussi, souvent, vous pourrez choisir des noms conventionnels courts. Si après avoir lu mon chant à la gloire du Grand, vous écrivez toujours indiceCaillou au lieu d’i dans votre module de gestion des cailloux dans les marres, ma foi, je ne peux rien pour vous (sinon prier pour votre âme, pour lui éviter le knout).

Un point de débat possible sur la question, cependant, est le suivant : faut-il privilégier la précision ou la convention ? (Et non, indiceCaillou ne relève pas de la précision à mes yeux.)

Imaginez, vous utilisez p pour toutes vos variables de type Meuh, en entrée des fonctions de votre module. Cela tombe bien, elles ne prennent chacun qu’un seul objet à examiner ! Parfois, en interne, vous avez besoin d’un second pointeur, que vous appelez naturellement q ; tout va bien, vous avez spécialisé les conventions existantes, parfait. Maintenant, imaginez que vous vouliez ajouter une fonction copier qui copie la valeur d’un Meuh dans un autre. Elle aura deux paramètres. Doit-on les appeler dest et src, pour la précision ? Ou p et q pour suivre la convention ? Cela nous amène au point suivant.

Tout comme il est important de se fixer des conventions pour les noms de variables, il est souvent très utile de vous définir des ordres de passage d’arguments respectés par toutes les interfaces de votre unité. Ce sont des règles du type « l’objet ayant le rôle X précède toujours cela de rôle Y ».

L’exemple typique ici est la fonction de copie. La destination doit-elle précéder ou suivre la source ? Il n’y a pas de réponse universelle, et chacun a ses préférences, mais il est important de toujours utiliser la même stratégie de classement de vos paramètres.

Pour revenir au problème précédent, s’il semble d’abord aberrant de vouloir appeler ses paramètres p et q, si l’on considère que l’ordre de passage est bien établi, alors l’usage ne fait aucun doute : p est toujours le premier pointeur et q le second, l’ordre des arguments se chargeant de donner son sens à cette position. Les deux alternatives sont donc viables ; à vous de choisir celle qui colle le plus à votre style.

Notez enfin que le point de vue est souvent influencé (un peu ou beaucoup) par le paradigme dominant. Si, disons, vous réfléchissez en orienté objet et avez codifié que le paramètre numéro un était l’objet sur lequel on opérait, alors le fait de toujours nommer cet objet de la même façon prend tout son sens. Si au contraire, vous adoptez une vision plus procédurale, un autre schéma de noms vous sera peut-être plus naturel…

Et voilà pour les conventions ! Notez que j’ai fait exprès d’éluder le sujet des idiomes, sujet fort proche. La raison ? La flemme, bien sûr. Mais pourquoi pas un jour prochain, ou un autre encore. Qui sait, tant qu’il y a du désir, et des gens pour me lire. Lalala.

[ tag:blog.huoc.org,2009:posts/25 ]
Aucun commentaire · Commenter

/\ \/

Le code et ses raisons : goto en C

#. Par rz0 dans Le code et ses raisons. Publié le 10.09 2009 à 13:06. 14 commentaires.
<. Conventions : le retour
>. Le code et ses raisons : typedef en C
c débutants goto idiomes pratique programmation

Tak tak, aujourd’hui Ours répond ! Quelqu’un m’a demandé hier pourquoi mon code contenait autant d’instructions goto, la malfamée. Comme c’est une question qui revient souvent, je me suis dit que cela pouvait intéresser notre audience moins expérimentée en C. Here comes!

Ahem, commençons. goto, c’est un truc un peu vite fait obscur du C, un peu comme les trucs Caml obscurs de bluestorm mais quand même pas au même niveau de louchitude. goto, ça déchaîne les jeunes comme les vieux, tout ça au final pour une pauvre instruction qui sautille. Bref, goto ça transporte le flot d’exécution d’un endroit à un autre d’une fonction. Ça, c’est la description objective, notre ptit Get the Facts avant d’embrailler sur la vraie question : pourquoi diable utilise-t-on goto ? Je ne pourrai pas répondre pour « on » mais je peux répondre pour moi !

goto est avant tout l’instrument du pauvre. Voilà qui est dit. La raison principale derrière l’utilisation de goto est que celui-ci permet d’émuler des flots d’exécution atypiques et indisponibles en C. On parle souvent de gestion des erreurs, d’exceptions du pauvre ; ce n’en est qu’un cas particulier. L’idée générale est celle que je viens d’énoncer : goto permet d’écrire des programmes C avec des structures non présentes directement dans le langage.

Lol le C, c’est pour les ptits joueurs, ya pas d’exceptions

Quelques exemples maintenant, avec tout d’abord le grand classique : la sortie de boucles imbriquées et la gestion des erreurs. Pourquoi ai-je donc regroupé ces deux cas qui apparaissent distincts ? Car ils modèlent tous deux un type de flot permis par les exceptions dans les langages qui en possèdent : on dispose de contextes imbriqués desquels on veut se défaire en un saut.α

α : Notez que je n’ai pas dit que les exceptions pouvaient être émulées par des goto, juste qu’il y a des ressemblances dans le flot obtenu.

Jetons un œil à deux bouts de code. La sortie de bloc, tout d’abord :

{
    while (a) {
        while (b) {
            if (c)
                goto end;
            ...
        }
    }
end:
    ...
}

Et la gestion des erreurs, dans sa forme modelant une pile :

{
    if ((a = f(...)) == NULL)
        goto bada;
    if ((b = g(...)) == NULL)
        goto badb;
    ...
    return ...;
badb:
    antif(a);
bada:
    return ...;
}

Le premier exemple ne devrait demander aucun commentaire. Le second n’est pas aussi évident : il n’exhibe pas clairement l’idée de contextes imbriqués. Celle-ci est en vérité masquée par le court-circuitage introduit par les sauts. Nous aurions pu écrire :

{
    if ((a = f(...)) != NULL) {
        if ((b = g(...)) != NULL) {
            ...
        } else {
            antif(a);
            goto bada;
        }
    } else {
    bada:
        return ...;
    }
    return ...;
}

OK, le tout s’est sensiblement enlaidi, et je vous rassure, personne ne l’écrit ainsi (car en plus d’être laid, on effectue beaucoup plus de sauts en cas d’erreur). L’avantage de cette forme, cependant, est qu’elle modélise beaucoup plus précisément le comportement d’une exception dans un langage de plus haut niveau : chaque bloc if correspondrait à un bloc try (ou équivalent) avec une clause de finalisation (contenue dans la partie else). Lorsqu’une condition exceptionnelle se produit, les clauses de finalisation sont déroulées les unes après les autres, dans l’ordre d’empilement des contextes (représentés ici par les blocs).

À part remarquer que la gestion des erreurs, cas normal d’utilisation des exceptions, n’est pas celui qui possède la correspondance la plus naturelle avec l’utilisation de goto, nous n’avons plus grand chose à dire ici. Passons donc à…

Plus tiré par les cheveux (encore) : appels récursifs terminaux

Si vous jetez un œil aux codes sources que j’écris, il arrive que j’utilise des goto à la place de boucles classiques. Et oui, sacrilège § L’explication ? ’Tention, c’est tordu : cela modélise des appels récursifs terminaux simples.β

L’exemple typique est celui d’une fonction :

{
    T x;

re:
    if (...)
        ...
    if (...)
        ...
    if (...) {
        x = f(x, ...);
        goto re;
    }
}

Alors oui, on pourrait bien sûr écrire tout ça sous forme d’une boucle ou carrément utiliser l’appel récursif et prier pour que le compilateur C optimise.γ

C’est en vérité une question d’emphase, et ces questions là sont profondément subjectives. Une boucle mettrait moins l’accent sur l’action et davantage sur sa répétition. Dans le cas où le cas récursif est marginal, la construction s’en retrouve relativement maladroite : on a alors le choix entre une condition de boucle obscure et redondante, des marqueurs booléens dans tous les sens, ou des instructions break et continue judicieusement placées. En choisissant cette dernière solution, qui semble la moins contraignante, on se retrouve avec un code dans ce genre-là :

{
    T x;

    for (;;) {
        if (...)
            ...
        if (...)
            ...
        if (...) {
            x = f(x, ...);
            continue;
        }
        break;
    }
}

Mieux ? Je ne crois pas, mais je n’irai pas non plus cracher sur celui qui écrit cela. :) Tout cela au fond pour une question de…

β : Là aussi, soyons fous mais pas trop, on ne peut pas émuler la récursivité terminale dans le cas général avec goto.

γ : GCC le fait, m’enfin, d’autres compilateurs ne le font pas forcément, ce n’est pas une optimisation très orthodoxe en C comme elle peut l’être dans les langages fonctionnels, l’idiome étant en Cδ de tout convertir en style itératif.

δ : Mais rms a trop fait de Lisp, comme tout le monde sait. Lalala.

Lisibilité !

C’est là que les gens se fâchent, que les regards se clashent, que la violence se lâche, wash wash. Hum, les uns disent de ne pas utiliser goto, pour la clarté du code, et pour les mêmes raisons, d’autres l’utilisent ! C’est la vie. Après tout, il y bien des gens qui codent en style GNU en pensant se rendre lisibles. :)

La question de la lisibilité de goto tient principalement à sa nature la plus primaire : la possibilité de passer librement d’une portion de code à une autre. Les détracteurs de goto pointent du doigt les utilisations de l’instruction qui engendrent des transitions difficiles à suivre dans le flot. Ceux qui en défendent le gain visuel évoquent la possibilité offerte par goto de déporter des blocs de code d’un endroit à un autre. Cette astuce est principalement utilisée pour la gestion des erreurs et permet ainsi de dégager du traitement utile la gestion des cas de défaillance, rares en pratique et souvent triviaux (l’action à entreprendre étant, la plupart du temps, de faire remonter l’information telle qu’on la reçoit).

Pour conclure sur un dernier exemple, j’abuse souvent de cette propriété pour écrire des choses comme celles-ci :

{
    T *p;
    R *q;

    p = malloc(sizeof *p);
    q = malloc(sizeof *q);
    if (p == NULL || q == NULL)
        goto bad;

    ...
    return ...;

bad:
    free(q);
    free(p);
    return ...;
}

Mon style vous répugne ? N’hésitez pas à commenter. :)

Annexe : setjmp et ses copains

Si, de mon expérience, goto est généralement relativement bien reçu dans la communauté des programmeurs C dans son ensemble (excepté dans certains cercles, et dans l’enseignement, sans doute de peur de pervertir les étudiantsε), il en est autrement de setjmp, le « goto non local ».ζ

Il y a probablement plusieurs raisons à cela : la difficulté d’utilisation (cf. restrictions énoncée par la norme, dont la nécessité de déclarer toutes les variables locales manipulables après le saut volatile), mais aussi les divers problèmes lié à la conservation et la restauration des états : il faut pouvoir replacer de manière sûre le contexte au point où il était au moment de l’appel à setjmp. Cela implique la libération de mémoire et l’appel de destructeurs pour les données allouées dans l’intervalle, mais également des notions de plus bas niveau telles que la gestion des signaux (cf. sigsetjmp(3)). En plus de ça, setjmp avec toute cette complication engendre des performances moisies, en général.

setjmp est toutefois utilisé, parfois, pour implémenter des mécanismes d’exceptions, notamment dans la kazlibη (pour le C) et diverses implémentations portables de langages dynamiques (l’interpréteur interactif d’OCaml, si je me rappelle bien, et d’autres dont je ne me rappelle plus…).

ε : Il paraît que ce sont les éléments perturbateurs tels que moi qui tentent leurs petits camarades avec le côté obscur. :)

ζ : Notez que GCC possède également des goto non locaux et indirects grâce aux pointeurs d’étiquettes. Je ne m’étendrai pas à ce sujet (car je connais mal le GNU C). Plus d'informations dans le manuel de GCC.

η : Je ne vois plus dans Google la page Web correspondant. Le projet est peut-être bien décédé…

Annexe : omg l’optimisation qu’elle est bonne

On entend dire parfois que goto c’est pas bon pour les performances. Bon alors, dans l’absolu, hum, en fait il n’y a pas d’absolu. On pourrait dire que remplacer des structures de contrôle classiques par goto enlève de l’information sémantique, et bon, dans l’absolu, c’est vrai. La question est de savoir si cette information sémantique est pertinente d’une part, et si elle est utilisée, d’autre part. Question pertinence, c’est, là encore, un peu subjectif : est-ce que tel ou tel goto remplace simplement une combinaison intelligente de structures de contrôle ou ne reflète-t-il un concept absent du C ? C’est davantage une question philosophique, donc humaine, que d’importance à la machine… car en vérité, la plupart des compilateurs modernes ne tiennent absolument pas compte de ce genre de choses, et le langage intermédiaire employé n’emploie que des sauts. Ce sont ensuite les algorithmes d’analyse de flot (brrr, théorie des graphes) qui ont à leur charge de différencier les blocs et autres constructions utiles au compilateur.

[ tag:blog.huoc.org,2009:posts/29 ]
Voir les commentaires · Commenter

/\ \/

Premiers pas vers un environnement de test NetBSD/Xen

#. Par rz0. Publié le 20.12 2009 à 15:50. 4 commentaires.
débutants ensimag netbsd xen

Loin des préoccupations über-théoriques de bluestorm, ces temps-ci, moi, j’ai essayé de bosser un peu, pour la Junior Entreprise locale. Autant dire que ça ne se passe pas tellement bien : on manque de main d’œuvre compétente et sérieuse, et honnêtement, ça m’irrite au plus haut point que le client soit perpétuellement déçu de notre travail ; c’est comme si je faisais du mauvais travail, mais franchement, si je savais que je me taperais autant de glands à gérer et autant de boulot pour lequel je n’ai pas signé, je n’aurais jamais accepté ce job. Bref, paraît qu’il y a des années avec et des années sans, bah là c’est clairement une année sans.

Tout ceci me mène à une petite anecdote : au cours de mon travail, je suis amené à tester le produit que je développe (la nature duquel est un secret…) dans une perspective de déploiement sur serveur. Récemment, je me suis heurté à des bugs que je n’arrive pas à reproduire sur ma machine de développement, pour une raison ou pour une autre (je n’ai pas encore réglé le souci), qui semble être liée au déploiement lui-même, du moins la configuration employée. Du coup, pour bien faire, je me suis dit que j’allais monter un petit serveur de test… un petit serveur virtuel !

Et ça tombe bien, j’ai toujours trouvé que Xen, ça avait l’air sympa ! Et en plus, chez NetBSD, on semble accorder une certaine importance au support Xen, ce qui est plutôt cool. Je me suis donc mis en tête de configurer un joli petit environnement NetBSD+Xen, le but étant d’avoir un serveur virtuel tournant sur une NetBSD vanilla, avec le minimum de paquetages nécessaires au déploiement de mon application. Bref, un truc propre.α

α : Le véritable serveur tourne sous Linux, mais pour du test rapide, il est beaucoup plus simple pour moi de virtualiser du NetBSD ; j’écrirai peut-être un autre billet, si je me fais un test Linux+Xen.

Xen pour les innocents

Pour me la jouer à la blueblue, je vais dire deux mots pour ceux qui ne connaîtraient pas le principe. Ahem, donc pour ceux qui ne connaîtraient pas, Xen est une solution de virtualisation,β comme VirtualBox… mais pas tellement en fait.

β : … principalement destinée aux serveurs, mais hey !

Avec VirtualBox, vous avez votre système normal et par-dessus, un petit logiciel qui fait tourner en son sein un autre système d’exploitation.

Xen est un peu différent. Vous ne pouvez pas simplement lancer Xen depuis votre système préféré, comme vous lanceriez un programme quelconque… c’est en fait plutôt l’inverse : Xen est une espèce de méta-système (un hyperviseur) qui va se charger de lancer vos systèmes d’exploitation un à un ! Si donc avant vous aviez, disons une Ubuntu avec une NetBSD dans votre VirtualBox, vous auriez maintenant l’hyperviseur Xen, et deux domaines (c’est comme ça que ça s’appelle dans Xen) : votre Ubuntu, et votre NetBSD.γ

γ : Pour tout vous dire, ce n’est probablement pas le meilleur choix… Ubuntu ne semble pas intégrer, dans sa ligne de développement centrale le support de Xen. Voyez la documentation communautaire d'Ubuntu sur Xen pour plus de détails.

Tous les domaines ne sont pas égaux pour autant : il y a toujours un système principal, appelé dom0 (domaine 0) ; les autres sont les domaines U (domU). Le dom0, c’est un peu le root de tous vos domaines : il a tous les pouvoirs, tandis que les autres ont des droits restreints, notamment concernant l’accès au réseau et au matériel. Au démarrage, Xen lance le dom0, qui a à sa charge de créer les domU qu’il désire (ou plutôt d’ordonner à Xen de les créer). Dans notre exemple, Ubuntu est le dom0, et NetBSD, le seul domU.

La dernière chose à savoir est que pour pouvoir être géré par Xen, il faut que le noyau soit conçu pour dialoguer avec l’hyperviseur, qui fait office d’abstraction entre la machine et le système. Il y a des noyaux dom0 et des noyaux domU. NetBSD propose dans sa distribution officielle des noyaux dom0 et domU ; sous Gentoo, compiler un noyau Xen est juste une histoire de changer de paquetage sources ; je ne sais pas trop ce qu’il en est des autres distributions Linux…

Bref, si vous ne connaissiez pas Xen, voilà qui est réglé. Il est temps de passer aux choses sérieuses…

Notes d’installation

Peut-être parce que c’est destiné à des mecs un peu underground, des sysadmins et tout, il n’y a pas beaucoup de tutos sur Xen… du moins, il y en a, mais beaucoup ne sont plus à jour et offrent des informations périmées. Notamment, le howto officiel de NetBSD/xen est, en l’état, incorrect.δ C’est fort dommage car mettre en place un environnement Xen avec NetBSD est en réalité très simple… pour peu que l’on sache comment s’y prendre.

J’offre ici mes humbles notes d’installation. Sait-on jamais, peut-être mon expérience servira-t-elle à quelqu’un.

δ : Si je mets la main sur ces permissions de commit un jour, je devrais ptet y faire quelque chose… Normalement, je fais signer ma clef GPG ce lundi !

  1. Si NetBSD est livré avec tout ce qu’il faut pour le faire tourner comme domaine Xen, Xen lui-même (l’hyperviseur et les outils pour le contrôler depuis le dom0) est à installer via pkgsrc. Il y a un jeu de paquetages par version majeure de Xen. Dans mon cas, j’ai pris la 3 :

    # cd /usr/pkgsrc/xenkernel3 && make install
    # cd /usr/pkgsrc/xentools3 && make install
    

    Le noyau Xen s’installe très logiquement dans la hiérarchie /usr/pkg. À l’heure où j’écris ces lignes, le fichier en question est /usr/pkg/xen3-kernel/xen.gz.

    On aura aussi besoin de scripts de démarrage pour les démons Xen xend et xenbackendd. J’ai simplement recopié ceux fournis à l’endroit habituel :

    # ln -s /usr/pkg/share/examples/rc.d/xend /etc/rc.d/
    # ln -s /usr/pkg/share/examples/rc.d/xenbackendd /etc/rc.d/
    

    Pour l’instant, ces démons ne font rien ; ils ne peuvent même être lancés (mais on peut déjà les ajouter dans le rc.conf, histoire de ne pas oublier). Il nous manque un dom0…

  2. Concernant le dom0, la bonne nouvelle, c’est que seul le noyau a besoin d’être modifié : juste en changeant de noyau au démarrage, on peut décider d’utiliser Xen ou pas, tout ça avec le même jeu de programmes, les mêmes disques et systèmes de fichiers, etc.

    Pour se faire un noyau dom0, rien de plus simple : un noyau dom0 est fourni avec NetBSD (XEN3_DOM0) ; en partant de sa configuration, j’ai simplement modifié le fichier à ma convenance (comme j’ai fait pour le noyau GENERIC, pour obtenir ma configuration non Xen), puis config et make ont fait le reste, comme d’habitude :

    # cd /usr/src/sys/arch/conf/
    # cp XEN3_DOM0 ELHAYM_DOM0
    # emacs ELHAYM_DOM0
    # config ELHAYM_DOM0
    # cd ../compile/ELHAYM_DOM0
    # make depend && make
    # cp netbsd /netbsd-dom0
    

    Pour avoir testé, il est sensiblement plus difficile de partir de la configuraiton personnalisée (dérivée de GENERIC) pour la rendre compatible avec Xen, que de partir de la configuration Xen pour l’adapter à la machine…

    Quelques défauts remarquables du noyau compatible Xen par rapport à un noyau NetBSD normal :
    • pas de SMP ;
    • pas de changement de fréquence SpeedStep du processeur (corrigé dans NetBSD-current, il me semble) ;
    • l’ACPI a quelques soucis chez moi (le poweroff ne fonctionne pas et la machine s’arrête simplement, sans s’éteindre)…
  3. Une fois le nouveau noyau fraîchement compilé, il suffit d’ajouter la ligne correspondante dans le boot.cfg. Autrefois, il fallait passer par Grub, à la place du bootloader NetBSD ; ce n’est plus le cas aujourd’hui, et tant mieux ! Les manips sont d’autant plus simple.

    À noter que la ligne à ajouter ne lance pas le nouveau noyau directement ; elle doit lancer l’hyperviseur, en lui indiquant le chemin du dom0. Quelque chose comme ceci (dans boot.cfg, il faut placer les instructions sur une seule ligne en les séparant par des points-virgules, bien sûr) :

    load /netbsd-dom0 console=pc
    multiboot /usr/pkg/xen3-kernel/xen.gz dom0_mem=max:1536M
    

    Limiter la mémoire pouvant être allouée au dom0 permet d’être sûr de pouvoir créer les domU quand on en a besoin. Je n’ai pas trouvé d’autre moyen de réduire la mémoire consommée par un domaine.

  4. Vient ensuite le domU, la configuration duquel est contrôlée par un fichier, que j’ai, pour ma part, simplement emprunté aux exemples fournis avec le paquetage :

    # cp /usr/pkg/share/examples/xen/netbsd1 /usr/pkg/etc/xen/test
    

    Le fichier est commenté, et c’est plutôt explicite. Cependant, quand on débute dans ce vaste monde, comme moi, ce n’est pas forcément évident pour autant…

    1. Tout d’abord, ça paraît stupide, mais domU, au départ, correspond à une machine vierge, sans système d’exploitation. Cela ne sert donc pas à grand chose de charger un noyau domU (par exemple le noyau XEN3_DOMU standard de NetBSD) vu qu’il ne trouvera rien sur le disque… Il faut en fait charger un noyau d’installation (INSTALL_XEN3_DOMU), qui contient un ramdisk avec sysinst.

      #kernel = '/netbsd-domu'
      kernel = '/netbsd-install-domu'
      
    2. La seconde variable d’intérêt s’appelle disk. Elle décrit les disques virtuels disponibles à l’intérieur du domU. Créer un disque virtuel vivant dans un fichier est le plus simple :

      vif = ['file:/var/xen/images/test.img,0x01,w']
      

      Pour l’installation du domU (avec le noyau d’installation), il ne faut pas non plus oublier de configurer le disque virtuel correspondant au CD-ROM contenant les sets d’installation NetBSD (les gros fichiers .tgz contenant l’userland précompilé) :

      vif = ['file:/var/xen/images/test.img,0x01,w',
             'file:/var/xen/images/install.iso,0x02,r']
      

      L’ISO se télécharge simplement sur un miroir NetBSD, dans le dossier iso/ (séparé des autres dossiers de fichiers d’installation).

      Ces disques apparaîtront dans le domU NetBSD sous la forme de périphériques xbd (le premier étant xbd0, le second xbd1, etc.). D’autres systèmes interprètent cela différemment.ε

      ε : Sous Linux, le second paramètre de la description du disque virtuel spécifie le périphérique à créer dans le domU.

      Le seul point délicat sur lequel je suis tombé est que pour utiliser un fichier en guise de disque virtuel (file:), il faut absolument que ce fichier ne soit pas creux (sparse), sans quoi vnd(4), le périphérique virtuel permettant de monter un fichier comme un disque, est incapable de fonctionner correctement.

      En pratique, cela signifie utiliser dd count=N plutôt que dd seek=N pour créer le fichier.

    3. Enfin, la configuration du réseau mérite un point à elle toute seule. Le principe est que Xen expose deux interfaces réseau (type Ethernet) connectées : une du côté domU (qui s’appelle xennet sous NetBSD) et une du côté dom0 (xvif sous NetBSD).

      Ce qui est fait de ces interfaces est laissé libre à l’administrateur, et est typiquement contrôlé par des scripts. Il y a deux scripts fournis avec le paquetage, vif-bridge et vif-ip, permettant respectivement d’obtenir un pont (bridge(4)) ou une simple IP statique privée associée au domU.ζ

      Pour ma part, j’ai choisi la seconde solution, car elle me convient mieux. Je n’ai pas besoin d’accéder à Internet depuis mon domU, et si cela devait se présenter, je pense que j’essaierai de mettre en place de la redirection, et du NAT. Pour utiliser par défaut vif-ip, il suffit de modifier le fichier de configuration de xend (/usr/pkg/etc/xen/xend-config.sxp, syntaxe s-exp, d’où le nom) :

      (vif-script vif-ip)
      

      Avec une telle configuration, la ligne de configuration du vif ressemble à :

      vif = ['ip=192.168.127.2 netmask 255.255.255.0']
      

      La valeur du sous-paramètre ip est simplement passée à ifconfig(8) pour configurer l’interface xvif (côté dom0). Le script ajoute automatiquement le sous-réseau mentionné (ici 192.168.127/24) dans la table de routage afin de diriger le trafic en direction de celui-ci à travers la bonne interface xvif. Cela signifie, en bref, qu’il suffit de donner une adresse IP appartenant au même sous-réseau à l’interface xennet, du côté du domU, et tout marche comme si les deux domaines étaient sur un même réseau !

      ζ : Le script proposé dans le howto NetBSD/xen est périmé. Celui fourni avec le paquetage semble très bien marcher.

    Une fois le fichier de configuration écrit, il suffit de faire appel à xm create, et tak.

  5. L’installation se passe comme une installation classique, par CD-ROM (à part que le périphérique CD est ici xbd1). Une fois terminée, il suffit de changer de noyau (dans le fichier de configuration du domU), et hop ! Une fois les briques de base en place, on peut passer à l’installation des paquetages que l’on veut.

    Pour ça, j’ai utilisé la solution cheap : un chroot(8) dans le vnd(4), en réutilisant les paquetages précompilés par pkgsrc pour ma NetBSD principale (avec un petit mount_union(8)).

    # vnconfig vnd0 /var/xen/images/test.img
    # mount /dev/vnd0a /mnt/
    # mkdir /mnt/usr/pkgsrc/
    # mount -t union -o -b /usr/pkgsrc/ /mnt/usr/pkgsrc/
    # chroot /mnt/ /bin/sh
    # export PKG_PATH=/usr/pkgsrc/packages/All/
    # pkg_add ...
    

    Et voilà ! Un joli environnement de test. :)

Impressions

Avec tout ça, j’ai un truc qui tourne. Je peux même tout simplement copier le disque en l’état, pour produire des environnements de test en masse.

Ceci dit, même si je considère Xen comme une technologie « cool », tout n’est pas rose. Au-delà des défauts que possède la version dom0 de NetBSD (principalement l’absence de SMP), j’ai eu du mal à trouver de la doc pertinente, et c’est un gros problème pour l’adoption de Xen par un public moins gurutique, à mon avis.

Cela mis à part, comme dit plus haut, je trouve, à titre personnel, que Xen avec NetBSD, c’est plutôt facile à mettre en place une fois que l’on a compris le truc ; et même si la doc comporte quelques erreurs, à l’heure actuelle, cela donne une bonne base sur laquelle partir.

Je ne sais pas ce qu’il en est des distributions Linux. J’entends souvent les gens opposer KVM à Xen. Je ne connais pas KVM donc je ne saurais pas trop dire ce qu’il en est. Mais du coup, il semblerait que l’adoption de Xen dans le monde Linux soit moindre… dommage ? Je ne saurais pas dire.

[ tag:blog.huoc.org,2009:posts/35 ]
Voir les commentaires · Commenter

/\

Pourquoi attacher tant d'importance au typage ?

#. Par bluestorm. Publié le 30.12 2009 à 13:34. 15 commentaires.
débutants langages méditation typage

La réponse, enfin dévoilée ! Bluestorm Origins: The Typing Mystery, maintenant dans les blogs (well, le nôtre du moins :-°). —rz0

Il est arrivé une bonne chose à mon dernier billet : des gens m’y ont répondu en commentaire. Je les remercie tous chaudement. Ce n’est pas complètement rationnel, mais je trouve ça vraiment appréciable, et ça m’a motivé pour retenter l’expérience de ces billets « en bref ». Mais pas pour cette fois : c’est intéressant quand on a différentes choses à dire, et il vaut mieux que ce ne soit pas trop souvent, sinon ils deviennent de plus en plus vides et répétitifs.

Dans les commentaires, que j’ai lus avec intérêt (j’ai même répondu à certains, c’est fou les blogs), se trouve une question qui m’a donné envie d’en faire un billet à part entière. C’est le commentaire de robocop :

Il y a quelque chose qui ressort quand même pas mal dans nombre de tes articles, c’est l’importance que tu attaches aux types. J’ai la facheuse impression d’être passé à coté de quelque chose de fondamental, pour moi, les types ce n’est qu’une vérification supplémentaire qui est fait à la compilation et ou à l’interpretation, mais à la façon dont tu en parles, le système de type semble le cœur même d’un langage de programmation.

Si comme je le pressens, je loupe vraiment quelque chose, pourrais-tu disserter rapidement là-dessus ?

En une phrase : pourquoi, parmi toutes les caractéristiques d’un langage de programmation, attacher autant d’importance à son système de typage ?

J’ai été surpris de ne pas pouvoir répondre à cette question sur le moment. J’ai pris du temps pour y réfléchir, et je pense avoir maintenant une réponse satisfaisante (mais pas rapide).

J’ai apprécié les commentaires de la carotte qui, avant l’ours, a relu ce texte et m’a permis de l’améliorer.

Rappels

Je ne compte pas expliquer dans ce billet les divers avantages et inconvénients, existants ou fantasmés, des différents systèmes de typage ; ce n’est pas le sujet. Je me contenterai ici d’un bref rappel de deux notions qui reviennent souvent quand on discute les systèmes de typage, et dont je reparlerai par la suite :

typage statique

désigne les systèmes de typage qui vérifient les types dans un programme avant de l’exécuter, typiquement au moment de la compilation. Les erreurs de typage, s’il y en a, sont signalées pendant cette phase et le programme n’est pas exécuté tant qu’il n’est pas correctement type. En général, à l’exécution, on n’a plus à se soucier des types et on peut raisonner comme s’ils n’existaient plus.

typage dynamique

désigne les systèmes de typage qui vérifient les types pendant l’exécution du programme. Avant chaque opération que le programme doit exécuter, il vérifie que les types des données sont correctes ; cela implique que le programme a accès à ces types quelque part : ils sont stockés dans la mémoire du programme pendant son exécution.

Ce sont deux méthodes très différentes qui ont un but commun : empêcher l’ordinateur d’effectuer des opérations mal typées (« mélanger des patates et des carottes »). Chaque façon de faire a ses adeptes, et les deux camps se livrent des guerres terribles, depuis plusieurs générations de langages de programmation — et, bientôt, d’informaticiens. Le point important ici est que les langages mettant en place l’une ou l’autre de ces méthodes ont un système de typage. Il sera plus ou moins bien défini, selon la précision de la définition du langage lui-même, mais dans tous les cas on peut s’y intéresser.

J’ai déjà parlé du typage dans un article publié sur le SdZ : Le typage, présentation thématique et historique. Je n’en suis pas complètement satisfait : je le trouve trop jeune et un peu naïf, et j’essaierai peut-être de le reprendre quand je saurai mieux ce que je veux dire.
Pour les débats autour du choix d’un système, j’aime bien l’article What To Know Before Debating Type Systems. Il est peut-être un peu biaisé en faveur des systèmes statiques, et un peu trop technique, mais il apporte une vue d’ensemble assez intéressante. C’est une bonne base si vous voulez vous lancer dans le débat : statique ou dynamique ?

Cependant, ce que je voudrais discuter ici, ce n’est pas le choix de telle ou telle méthode de typage, mais plutôt la place du typage au sein d’un langage de programmation, relativement aux autres aspects du langage.

Types comme information

Imaginez que vous voulez écrire un petit morceau de programme dans le langage de votre choix, qui soit réutilisable par d’autres programmeurs. Vous devez leur décrire ce que vous avez fait. Vous commencez par leur dire ce que fait ce bout de programme (« c’est une compression selon le codage de Huffman »), et vous allez décrire la manière dont ils doivent interagir avec ce bout de programme pour profiter de ses bienfaits : « on lui donne un texte, et il le renvoie compressé, avec le dictionnaire de compression ». Avec cette simple phrase, vous avez déjà transmis des informations de typage : on lui fournit un texte, donc en général une chaîne de caractère. S’il a besoin de plus d’informations (par exemple sur la nature du dictionnaire de compression), vous donnerez une description plus précise qui contiendra autant d’informations de typage supplémentaires.

Cette façon de procéder est commune à l’ensemble des programmes et langages. Choisissez un langage que vous appréciez et qui a une documentation utilisable : elle contient (plus ou moins explicitement) des informations de typage. Par exemple, j’ai choisi une page au hasard de la documentation du langage PLT Scheme, réputé « non typé », dans laquelle on peut trouver le texte (code ?) suivant :

(directory-exists? path) → boolean?
  path : path-string?

C’est tout à fait du typage : on décrit une fonction qui vérifie qu’un répertoire existe dans un système de fichier. Cette fonction accepte un chemin de type path-string, et renvoie un boolean. Les points d’interrogation soulignent un détail intéressant : les noms choisis pour décrire les types sont aussi des fonctions (prédicats) que l’on peut utiliser pour vérifier qu’une valeur est bien du type donné. Par exemple la documentation de la fonction path-string? contient :

(path-string? v) → boolean?
  v : any/c

Ces prédicats sont utilisés au sein de ces fonctions pour vérifier que l’utilisateur ne donne pas n’importe quoi, et la vérification de validité est donc effectuée à l’exécution : c’est une forme de typage dynamique.

Ce ne sont pas les seules explications concernant ces fonctions, il y a aussi du texte en langue naturelle qui explique leur usage. Certaines documentations ne mettent aucune information de typage aussi claire (je pense à Python par exemple), il faut aller la pêcher dans le texte explicatif, ou encore elle peut être implicitement contenue dans les noms des paramètres, qui respectent des conventions facilitant cette tâche.

L’idée importante c’est qu’au fond, un type c’est essentiellement une information sur une expression du langage de programmation.

Le langage qui parle de lui-même

Cette idée des types comme informations est très large, car elle doit faire cohabiter les notions de typage très différentes selon les langages. Elle est peut-être même un peu trop étendue, puisqu’elle désigne tout et n’importe quoi, et que je ne vais pas avoir de mal à vous convaincre que vous avez besoin d’informations pour coder correctement. On peut affiner un peu plus.

Quand on parle du système de typage d’un langage, on n’a pas en tête les conventions de nommage des paramètres utilisées dans sa documentation. On parle en général d’une manière de décrire ces informations qui fait partie du langage lui-même : le système de typage, c’est la partie du langage qui permet de donner des informations sur les valeurs.α Je désigne cette partie par le terme langage de types : par exemple le langage des types du C est un sous-ensemble du langage C qui contient les types de base (int, float..), des modulateurs (unsigned), enum, struct, …, des pointeurs, tableaux, et même des types fonctionnels.

En d’autres termes, le système de typage, c’est un méta-langage, qui détient le pouvoir d’expression du langage sur lui-même. Plus le système de typage est expressif, plus on peut dire de choses sur un programme au sein de ce programme, plutôt que dans la documentation ou en commentaire.

Vous comprenez sans doute pourquoi, avec ce point de vue, j’attache tellement d’importance, quand je m’intéresse à un langage de programmation, à son système de typage : c’est une partie toute fondamentale puisqu’elle détermine la capacité des programmes dans ce langage, en plus de faire quelque chose d’utile, à transmettre des informations sur ce qu’il fait.

Il faut cependant bien remarquer que, bien que très importante, elle n’est pas non plus indispensable : on peut très bien imaginer des langages qui ne permettent de formuler aucune information sur les programmes, mais qui ont d’autres qualités qui en font des langages intéressants. Je pense par exemple à Forth, un des premiers langages à pile haut niveau.

α : On pourrait croire à une circularité ici : si les types parlent du langage tout en en faisant partie, alors les types parlent des types et le monde s’écroule. En réalité, la plupart des langages de types ne sont pas suffisamment expressifs, et ne parlent que d’une partie restreinte du langage (qu’on appelle souvent les valeurs). Mais il existe effectivement des langages (par exemple Coq) dans lesquels les types ont eux aussi un type, qui a à son tourβ un type… Il peut bien sûr exister un nombre infini de types différents, bien qu’un programme donné ne les utilise pas tous en même temps.

β : « Turtles all the way down ! » ;-)

Système de typage et vérification

La définition actuelle reste assez molle : est-ce que les prédicats de PLT Scheme constituent un système de typage ? Ils font partie du langage, mais leur utilisation est laissée au bon vouloir de l’utilisateur : il peut aussi implémenter sa fonction sans faire aucune vérification. On pense en général plutôt à des systèmes dans lesquels le typage est obligatoire, et vérifié automatiquementγ par le compilateur. Ce qui est sûr c’est que les informations implicites de la documentation Python ne rentrent pas dans ce cadre : le typage en Python, c’est ce que le compilateur sait vérifier de lui-même et afficher dans un message d’erreur : l’appartenance à une classe, l’existence d’une méthode donnée, etc.

Quand on décrit le système de typage d’un langage, il faut donc aussi préciser la manière dont ces informations sont vérifiées, ou plus généralement exploitées par le reste du langage. Il y a d’autres distinctions que la vérification statique ou dynamique que j’ai évoquée. Par exemple, certains langages permettent des conversions implicite d’un type vers un autre, ou le choix dans une fonction d’un comportement différent selon le type des paramètres ; on parle dans ce dernier cas de polymorphisme ad-hoc, car il permet de gérer chaque cas particulier séparément.

Autres points à considérer pour son système de typage

L’expressivité et les conditions de vérification ne sont pas les seuls points à débattre d’un système de typage.

On peut s’intéresser à l’emplacement, au sein des programmes de ce langage, des annotations de typage. Certains langages demandent au programmeur très peu d’indications de typage, car ils peuvent les reconstruire à partir des informations déjà présentes dans le langage : c’est l’inférence de type. Plus le langage des types est expressif, plus cette reconstruction est difficile. Les annotations peuvent encombrer le programme si elles sont trop nombreuses, mais placées judicieusement elles peuvent augmenter la lisibilité. Plus généralement, les informations de typage, qu’elles soient produites par le programmeur ou ses outils, renforcent la documentation et la modularité entre les différentes partie du programme : donner le type des valeurs d’une bibliothèque logicielle facilite beaucoup son utilisation par d’autres programmeurs, surtout quand il est assez expressif pour traduire l’ensemble des contraintes d’utilisation de cette interface.

Un dernier exemple, plus sophistiqué, la propriété de séparation : dans certains langages (dont OCaml), le typage est une phase totalement séparée de l’évaluation, dans le sens où une fois qu’on a un programme bien typé, on peut oublier complètement tout ce qui concerne le typage, et cela ne change absolument rien au déroulement du programme.δ Dans d’autres langages, en particulier ceux qui possèdent le polymorphisme ad-hoc, il n’y a pas cette séparation. L’avantage de cette propriété est qu’elle donne un langage plus simple à comprendre, avec des phases plus indépendantes. L’inconvénient est qu’elle est contradictoire avec certaines fonctionnalités que les concepteurs du langage peuvent avoir envie de fournir : imposer la séparation peut réduire l’expressivité. Il n’y a pas de bon et de mauvais choix dans l’absolu, cela dépend des besoins du langage, mais c’est un choix auquel les systèmes de typage sont confrontés. On peut enfin imaginer des distinctions plus fines, avec une partie du langage qui brise la séparation, mais qui est traduit dans une première phase vers un langage plus simple, bien séparé. C’est comme cela par exemple qu’on manipule les type classes de Haskell quand on veut en donner des définitions très précises (formelles).

γ : En fait, PLT Scheme permet aussi la vérification des types ; je suis impressionné par cette implémentation de Scheme.

δ : Je parle ici de la sémantique du langage, c’est à dire d’une définition un peu abstraite du comportement (ou sens) des programmes. Ce n’est pas une question d’implémentation : si on crée un compilateur pour un langage avec cette séparation, on peut très bien choisir de faire des optimisations en raisonnant sur le type des variables, par exemple en changeant leur représentation mémoire (boxing, etc.), du moment que cela crée aucune différence de comportement du programme observable par le programmeur.

Contraintes et limitations

Si le langage des types est peu expressif, et la vérification très rigoureuse (impossible de définir des termes non typés), le typage peut vite devenir une contrainte. Par exemple, le langage des types du C ne permet pas d’exprimer le polymorphisme paramétrique (une fonction qui marche sur tous les types ayant structure donnée) ; on fait avec, en utilisant une sorte de type-à-tout-faire, void *, qui permet d’abandonner à la fois l’information de typage et la restriction qui y est associée.

Cette limitation que l’on rencontre n’est pas seulement un artefact de systèmes pratiques, mais se trouve déjà dans la théorie des langages. Par exemple, le lambda-calcul simplement typé de base ne permet pas d’écrire des programmes qui ne terminent pas : tous les programmes bien typés terminent en un temps fini. Ça a ses avantages, mais aussi un inconvénient : ce langage n’est pas Turing-complet. En gros, il y a des fonctions qu’on ne peut pas écrire avec. On peut lever cette limitation,ε soit en enrichissant le langage des types (polymorphisme plus types récursifs par exemple), soit en ajoutant des constructions au reste du langage (opérateur de point fixe ou définitions de fonctions récursives).

Les langages au typage dynamique ont en général un langage de type assez faible, mais ont du coup l’avantage de ne poser que très peu de contraintes : comme ils ne vérifient pas grand chose au niveau du typage (par exemple, ils ne vérifient pas le fait que les deux branches d’une condition renvoient le même type), ils ne posent pas non plus de contraintes gênantes. C’est un autre choix qui peut se justifier ; par exemple, certains motifs de conception de la programmation objet sont notoirement difficilesζ à typer correctement (ils demandent un langage très expressif), les langages dynamiques n’ont pas ce problème.

ε : Limitation qui n’en est pas toujours une : on peut faire des choses très intéressantes avec des langages non Turing-complets (par exemple les expressions régulières/rationnelles, quand elles le sont vraiment, ou Coq).

ζ : Jacques Guarrige, Code reuse through polymorphic variants

The idea for this example originally comes from a post by Phil Wadler on the Java-Genericity mailing list W+98, in which he proposed a solution to the Expression Problem, that is the problem of extending a variant type with new constructors without recompiling code for old ones. A similar problem and solution can be found in KFF98, but untyped. It appeared later that Wadler’s solution, which already supposed an extension of Generic Java, itself an extension of Java, could not be typed. Didier Rémy, Jérôme Vouillon and myself finally came up with a typable solution in an object-oriented extension of the 3rd order typed lambda-calculus, which was later checked correct by Haruo Hosoya in F-omega- sub-rec. On the other hand, I could provide a much shorter solution in about 15 lines of Objective Label using polymorphic variants, which were merged later in Objective Caml. The solution used only standard ML-polymorphism, which is a weakened 2nd order typed lambda-calculus, and can be inferred automatically.

Pour bien typer, il faut bien comprendre

Il arrive donc assez souvent que des idées sur de nouvelles façon de programmer soient développées dans un cadre non typé, et qu’elles trouvent seulement ensuite leur place dans un système de typage, comme par exemple le transfert du langage de programmation Oz, langage dynamique qui a popularisé certains paradigmes de concurrence, à AliceML, langage typé qui reprend une partie de ces fonctionnalités.

Cela ne veut pas dire que le cadre non typé est plus fructueux : exprimer précisément une information statique sur ces nouvelles idées permet souvent de les éclairer d’un jour nouveau. Par exemple, la recherche de sémantiques statiques pour les langages à effets de bords a donné lieu à l’idée de monade, qui est utilisée en Haskell pour représenter, entre aures, les effets de bords, et qui a pour conséquence de modifier le type des programmes en y rendant ces effets explicites. On y a gagné des outils intéressant, un point de vue nouveau, et une meilleure compréhensionη de la programmation impérative.

Malheureusement, la programmation est un sujet compliqué dont nous n’avons fait qu’égratigner la surface : il y a beaucoup de choses qu’on ne sait pas encore expliquer de manière simple et précise à la fois, par exemple la concurrence, les manipulations mémoire, la communication avec les programmes extérieurs, etc. L’explication et le typage précis de manières avancées de programmer est un sujet de recherche actif, dont on espère qu’il nous apportera des réponses ; en attendant, il faut se contenter de typages correspondant à l’état des connaissances aujourd’hui, et qui ne sont pas forcément aussi expressifs qu’on le voudrait.

η : Rz0 me signale que cette « meilleure compréhension » n’a pas du tout affecté la pratique des programmeurs qui, sur le terrain, utilisent des effets de bords, et qu’il est donc un peu « osé » de présenter les monades comme une réussite de ce point de vue. C’est en effet un peu plus compliqué que ça : notre compréhension théorique des effets de bords s’est améliorée, mais on a aussi découvert que les monades sont des outils trop généraux, qu’elles s’appliquent à beaucoup de choses qui ne sont pas considérées habituellement comme des effets de bords, et qu’elles ne sont pas toujours assez précises.

Pour la gestion de la mémoire par exemple, une monade ne peut pas raisonner, au niveau du typage, sur l’étendue des zones mémoires affectées par un programme : la monade saura dire de deux programmes qu’ils modifient la mémoire, mais pas si les zones de mémoire qu’ils utilisent sont disjointes ou s’il y a des interactions entre les deux. On a besoin de ces propriétés plus fines, et on est en train de développer des outils plus spécialisés et plus complexes, comme les systèmes d’effets et les logiques de séparation.

Enfin, même si ces idées mettent du temps à sortir de leur cadre théorique pour atterrir dans la boîte à outils des programmeurs, elles restent présentes dans l’industrie, avec par exemple les outils d’analyse statique de programmes, qui suivent d’assez près les progrès de la recherche dans ce domaine. L’arrivée du concept dans un langage de programmation grand public est la dernière étape (après l’entrée dans les langages expérimentaux comme DDC ou ATS), qui se fera plus tard, ou peut-être pas du tout, si l’on constate que les bénéfices de ces méthodes ne compensent pas l’ajout en complexité.

η´ : Et puis d’abord, si l’exemple des monades ne vous plaît pas, en voici un autre : les continuations. Le comportement dynamique de call/cc est bien connu, mais l’étude de son lien avec le typage et la logique classique est au moins aussi intéressante.

Puissance du typage et complexité du langage

Cette difficulté que rencontrent les concepteurs de langage de programmation se retrouve aussi, à plus petite échelle, quand un programmeur essaie de donner un type statique à son programme. Quand on code dans un langage dynamique, on ne se soucie essentiellement que d’une chose : il faut qu’au moment de l’exécution, notre programme marche. Donner un type très expressif à un programme, cela peut demander de rendre explicites plus d’informations : qu’est-ce que notre programme fait, comment le fait-il, pourquoi cette fonction est-elle correcte ? L’ordinateur peut en deviner une partie (c’est le principe de l’inférence des types), le programmeur en connaît une partie (par exemple ce qui est un entier, et ce qui est un booléen dans son programme), mais il y a parfois des programmes compliqués pour lesquels ce n’est pas aussi simple : donner un type très précis impose une bonne compréhension du programme.

Bien entendu, même dans les langages typés, un programmeur a toujours la possibilité d’utiliser des représentations moins expressives, et qui demandent donc moins de réflexion. Par exemple, au lieu d’avoir un type de donnée spécialisé pour décrire les dates (jours, mois, heure, etc.), on peut par exemple utiliser des chaînes de caractères. C’est généralement une mauvaise idée : un effort au départ pour décrire et utiliser un type plus précis permet ensuite des manipulations plus sûres et plus directes.

Il y a cependant un point à ne pas oublier quand on cherche des langages de types expressifs : ils rendent nécessairement les langages plus complexes. Quand des débutants en Ada doivent comprendre les différences entre les paramètres in, out, et in out, ils peuvent penser qu’un langage faisant moins de fioritures aurait été plus simple. On peut se demander s’il vaut mieux concevoir des langages simplifiés pour les débutants et des langages moins simples mais ayant d’autres qualités pour les experts, ou si une taille unique peut convenir à tout le monde. Cette question intéressante est encore ouverte, mais il est clair que la simplicité (réelle ou perçue) d’un langage joue dans son adoption par les programmeurs, et qu’on ne peut pas se permettre de concevoir des systèmes de typage arbitrairement compliqués en espérant qu’ils vont dominer le monde.

Tous les langages sont typés, parfois sans le savoir

Ma définition est suffisamment large pour qu’on puisse trouver un système de typage à tous les langages, même ceux qui se proclament traditionnellement « non typés ». Les cas les plus extrêmes sont les langages dont les types sont tellement pauvres qu’ils ne peuvent essentiellement rien dire d’intéressant. Par exemple, en Brainfuck, le système de typage est vide : on ne peut rien dire sur les programmes, et d’ailleurs il n’y a rien à dire puisqu’ils ne manipulent jamais rien d’autre qu’une bande mémoire dont une case est pointée.

Plus utiles, les assembleurs sont aussi des langages avec des typages très pauvres : il y a en général des registres, des valeurs immédiates et des adresses mémoires (et par exemple des labels à plus haut niveau), et des considérations de taille, alignement, etc., qui sont assez riches mais ne nous apprennent pas beaucoup sur le comportement à plus haut niveau du code, tant qu’on ne le munit pas d’un système de typage plus expressif.θ On peut encore citer le langage de Matlab/Octave, qui ne connaît essentiellement que les matrices de scalaires, ce qui peut parfois donner de très mauvaises surprises.

θ : Voir par exemple les travaux de Greg Morrisset ou George Necula sur les assembleurs fortement typés.

La réaction de rz0 de sujet souligne un autre aspect du problème :

À mon avis, un point intéressant, et pas du tout développé dans la plupart des visions « haut niveau » de l’assembleur, c’est les contraintes sur les adresses ou la mémoire : représentation, décalage, facteur, taille, alignement, granularité, etc. Je considère que c’est du typage aussi, et c’est du typage « utile », dans le sens où si les données fournies aux opérandes sont mal typées, soit ça n’assemble pas (p.ex. parce qu’il n’y a pas assez de bits pour représenter l’adresse entièrement), soit ça balance une exception au runtime (si on passe un registre et que sa valeur ne vérifie pas les conditions de typage). Bref, je trouve que ce n’est pas « rien à dire ».

Mélanger typage statique et typage dynamique ?

Cette idée de décrire les langages peu typés comme possédant un petit nombre de « types poubelles » ne donnant que très peu d’information peut en fait être utilisée pour mélanger au sein d’un langage le typage statique et le typage dynamique : on peut rajouter ces types poubelles à l’éventail des types du langage.

Il s’agit de permettre de « perdre » localement l’information de typage, en transformant toutes les valeurs, quelle que soit leur type, vers un même type dynamic. On peut ensuite utiliser une fonction d’extraction pour récupérer des valeurs bien typées dans les parties du programme qui n’ont pas besoin de typage dynamique. Cette idée est vieille comme le monde, il s’agit de faire un cast (conversion d’un type vers un autre) au détriment de la sûreté du typage, et elle est présente dans un grand nombre de langages de programmation.

Pour que cette méthode ne nuise pas trop à la sûreté de l’application dans son ensemble, il faut réutiliser les méthodes de vérification de typage des langages dynamiques : quand on envoie la valeur vers le type poubelle, on lui associe une autre valeur runtimeι qui décrit son type. Ensuite, quand on convertit à nouveau la valeur dynamique vers un type plus expressif du langage, on peut insérer un test pendant l’exécution qui vérifie que le type d’arrivée est bien compatible avec le type dynamique stocké avec la valeur.

Au-delà du dynamic_cast de C++, les langages fortement typés ont fait des tentatives dans cette direction. La solution la plus simple est de demander à l’utilisateur de ces fonctions d’insertion et extraction dans le type dynamique de manipuler lui-même les types dynamiques, en utilisant une bibliothèque logicielle dédiée. On peut utiliser des fonctionnalités du langage pour simplifier cette manipulation (par exemple des macros syntaxiques, des types fantômes,κ ou des type classes), et mettre en place des bibliothèques logicielles apportant ces facilités aux programmeurs.

Ça n’est cependant pas suffisant : si l’on se contente d’une bibliothèque logicielle à part, le langage ne garantit pas la cohérence entre l’information statique de typage dont il dispose, et les valeurs dynamiques construites par l’utilisateur ou sa bibliothèque spécialisée : même quand elle est fortement typée (les manipulations à l’extérieur de la bibliothèque sont sûres) il faut faire confiance à l’auteur de cette bibliothèque de valeurs dynamique pour n’avoir pas fait d’erreurs de conversion à l’intérieur.

ι : Un anglicisme qui désigne le « moment de l’exécution », à nuancer de compile-time ou parse-time, et dont j’ai beaucoup de mal à me débarrasser.

κ : C’est ce que j’ai utilisé dans Macaque ; j’avais besoin d’une méthode sûre pour traiter la perte de typage des données passant par le serveur SQL (qui n’utilise que des chaînes de caractères), et je me retrouvé, sans m’en rendre compte au départ, avec du typage dynamique fait maison.

Si l’on veut plus de simplicité et de sûreté, il faut intégrer cette fonctionnalité directement au sein du langage de programmation. La communauté CAML a fait partie des pionniers de ce domaine avec Dynamics in ML, une extension du langage proposant des valeurs dynamique même pour les types polymorphes. Les langages fonctionnels Clean et AliceML proposent aussi cette fonctionnalité.

On peut remarquer que cette approche (construction automatique, par le langage directement, de la description runtime de type) brise la propriété de séparation, que j’ai évoquée précédemment, entre la phase de typage et la phase d’exécution.

Ces méthodes permet de concevoir des langages typés statiquement mais qui acceptent de « laisser la main » gracieusement quand on atteint les limites de leur expressivité. Cela permet de résoudre les problèmes des constructions trop difficiles à typer mentionnés auparavant, sans abandonner trop de typage. Malheureusement, en pratique la mise en place de tels systèmes au sein d’un langage peut compliquer sensiblement son implémentation efficace, et elles ne sont donc pas très répandues. C’est encore une fois un sujet de recherche actif. Il y a des langages qui essaient de rendre encore plus transparente la frontière entre les mondes dynamiquement et statiquement typés. Cela demande de nouvelles méthodologies de typages, et tout une zoologie se crée autour du sujet : soft typing, gradual typing, blame typing, hybrid typing, …

En attendant, les programmeurs se contentent en général de méthodes plus indirectes, en utilisant des valeurs dans des types moins expressifs mais plus souples, comme le font sans le savoir les utilisateurs de langages dynamiques.

Les types ne font pas tout

Ce billet a beaucoup parlé des types, et de pourquoi le système de typage est un ingrédient majeur d’un langage de programmation. Il ne faut pas non plus oublier que ce n’est pas le seul, et qu’une grande partie de l’intérêt des langages ne vient pas du tout de leur système de types.λ

L’un des livres sur la programmation qui m’a beaucoup plu est le CTM : Concepts, Techniques and Models of Computer Programming. C’est un livre vraiment formidable, qui a étendu mon horizon en matière de techniques de programmation, et qui pourtant ne parle pas du tout, ou quasiment pas, de typage.

λ : Même si vous seriez sans doute surpris par la quantité de chose qu’on peut présenter sous forme d’un typage, ou en relation avec le typage : sécurité, performances, invariants de structures de données, etc.

[ tag:blog.huoc.org,2009:bluestorm/8 ]
Voir les commentaires · Commenter

>> Page : 0 1