À la découverte d'Emacs (Lisp) 24
Depuis quelques jours, j’ai décidé de migrer vers Emacs 24, la version en développement d’Emacs. Le changement s’est effectué sans trop de difficultés, une ou deux extensions mineures devenues incompatibles, quelques options de configuration qui ne font plus tout à fait ce que je veux, mais globalement, une mise à jour tout en douceur, comme d’habitude… après tout, un ancêtre comme Emacs ne doit plus tellement évoluer… si ?
Bah, en fait… non, pas vraiment ; côté fonctionnalités, Emacs 24 n’apporte rien de révolutionnaire, à vrai dire… des améliorations des modes existants : une meilleure prise en charge des gestionnaires de versions distribués, la possibilité de manipuler des fichiers 7-zip, ou encore une meilleure indentation pour certains modes de programmation, par exemple. Il faut aller chercher du côté du programmeur pour trouver les nouveautés de cette version, car il y en a ! Du moins, quelques unes qui ont retenu mon attention, et qui constituent, selon moi, tout autant de petits signes qu’Elisp mûrit encore, lentement mais sûrement, en tant que langage.
Cela ne surprendra probablement personne si je dis qu’Elisp — Emacs Lisp, le dialecte de Lisp utilisé par Emacs, n’est pas tout à fait dans l’air du temps… c’est un Lisp historique, auquel il manque toutes sortes de petits plus qui contribuent à notre confort. Bien qu’il possède des fermetures et des fonctions d’ordre supérieur, on ne peut pas dire qu’Elisp encourage vraiment un style de programmation fonctionnel, tel que ses partisans le défendent aujourd’hui… pas de portée lexicale pour les symboles, pas de types algébriques, ni de récursion terminale optimisée, bref, pas exactement idéal pour faire du fonctionnel…
Du moins, cela a toujours été le cas… jusqu’à Emacs 24. Non, non, je vous rassure, il n’y a toujours pas de typage algébrique en Elisp, mais cette version apporte son lot de changements intéressants qui font d’Elisp un langage un peu plus respectable aux yeux des vrais, des durs. Tour d’horizon des choses nouvelles et moins nouvelles.
Je profite de l’occasion pour parler un peu plus généralement d’Elisp comme langage de programmation ; j’évoque, bien évidemment, les nouveautés d’Emacs 24, mais également des choses plus générales qui pourraient intéresser les curieux, ou les non-initiés, bref, les innocents qui voudraient se lancer dans scriptage de leur Emacs.
Visite guidée : à votre droite, les fondations…
Flots de contrôle
Parmi les constructions de base du langage, rien de nouveau dans Emacs
24 (ou 23, ou 22, etc.) ; on retrouve les conditionnelles du Lisp,
if (et ses variantes when et unless) et cond, ainsi qu’une
simple boucle while, implémentée comme une primitive dans
Elisp. Comme dit plus haut, la récursion terminale n’est pas prise en
charge en temps constant, et il vaut mieux utiliser une boucle quand
on peut.
Le module cl-macs apporte son lot de macros complémentaires
empruntées à Common Lisp (mais utilisées dans quasiment tous les
paquetages Emacs), notamment do et ses variantes (dolist et
dotimes) ainsi que le tout-puissant loop (dans une forme quelque
peu amoindrie).
Le problème des macros Common Lisp dans Emacs est qu’elles sont généralement très succinctement documentées, et le manuel Elisp n’en parle pas. Il faudra donc regarder ailleurs pour apprendre à s’en servir…
Variables, fonctions et fermetures
Côté variables, depuis Emacs 24, Elisp a enfin renoué avec le
vingt-et-unième siècle et propose désormais des variables à portée
lexicale ! À activer via un petit commentaire en haut du fichier
source (il s’agit de mettre la variable lexical-binding, locale au
fichier à t), celles-ci se comportent un peu comme en Common Lisp :
les variables définies globalement (avec defvar) sont à portée
dynamique, tandis que les constructions locales telles que let et
letrec introduisent des variables lexicales.
-*- lexical-binding: t -*-
;; À mettre en première ligne de vos fichiers Elisp, avec les autres
;; variables locales au fichier.
Assez traditionnellement, les variables en Elisp sont mutables, comme
en Common Lisp ou en Scheme, mais pas comme en ML. L’affectation se
fait avec setq ; on trouve setf dans cl-macs, mais il n’est pas
d’usage de l’employer à la place de setq.
En ce qui concerne les fonctions, il faut tout d’abord mentionner
qu’Elisp est un Lisp-2, ce qui signifie que les noms de fonctions
existent dans un espace différent de ceux des variables (on peut avoir
une variable list et une fonction list). La déclaration au niveau
global se fait par defun ; localement, on dispose des
lambda-expressions avec le mot-clé lambda, qui introduit une
fonction anonyme.
Les macros Common Lisp labels et flet n’ont malheureusement pas
l’air d’avoir été mises à jour pour profiter de la nouvelle prise en
charge de la portée lexicale, et utilisent encore l’ancienne émulation
via lexical-let — en fait, il n’est pas clair si ce nouveau
mécanisme de portée lexicale s’applique également aux fonctions (a
priori, ce n’est pas le cas, puisque seul let expose cette nouvelle
fonctionnalité, et celui-ci n’affecte pas l’espace des
fonctions). Étant donné que labels et flet ne profitent pas du
nouveau mécanisme de portée lexicale, la déclaration de fonctions
(lexicalement) locales doit se faire préférablement par le biais de
let ou letrec, comme suit :
(letrec ((fun
(lambda (args...)
body...)))
body...)
Pas exactement ce que j’appellerais élégant, surtout que cela oblige
à utiliser funcall pour l’appel, mais on va dire que l’on fait
avec… Je m’attends à ce qu’à mesure que les programmeurs adoptent la
portée lexicale, l’usage en devienne plus souple, poussant ainsi les
développeurs d’Emacs à ajouter ce genre de support dans une révision
future.
Structures de données
Comme tout langage de haut niveau qui se respecte, Elisp prend en charge nativement un certain nombre de structures de données, telles que :
les listes, bien sûr, naturellement notées
(elts...)ou(elts . tail); avec leurs usages idiomatiques (piles, listes associatives, listes de propriétés, etc.) ;mais également les vecteurs et chaînes de caractères, mutables, mais de taille constante (bien que les chaînes de caractères soient mutables, il n’est pas d’usage de les modifier directement) ; les chaînes s’écrivent entre guillemets doubles
"et les vecteurs entre crochets[];ou encore les tableaux associatifs (depuis Emacs 21), sous forme de tables de hachage, avec la syntaxe (depuis Emacs 23)
#s(hash-table data (key val ...))ou juste#s(hash-table)pour une table vide ; pas des plus jolies, m’enfin, elle est là quand on en a besoin ;plus inhabituel, mais logique quand on y réfléchit, Emacs offre à ses programmeurs des buffers qui peuvent contenir plus ou moins des données quelconques, éventuellement beaucoup de données, et sont adaptés aux opérations d’édition (insertion et suppression de segments) ; il n’est pas choquant de voir les buffers utilisés un peu à tort et à travers, mais une caractéristique importante de ceux-ci est qu’ils existent en dehors du ramasse-miettes ; il faut donc penser à les éliminer explicitement quand on n’en a plus besoin (ou utiliser une macro telle que
with-temp-buffer, qui fait le job pour vous) ;du reste, on trouve d’autres structures spécialisées pour l’édition (tables de syntaxe, keymaps, etc.) et dont l’intérêt général est tout à fait discutable au-delà de leur fonction première ;
pour finir, des bibliothèques incluses par défaut dans Emacs implémentent des conteneurs supplémentaires telles que des tampons circulaires (
ring) ou des arbres AVL (avl-tree).
Globalement, les ressources sont multiples et bien présentes, quoique les interfaces soient parfois un peu archaïques et pas forcément très agréables à utiliser…
Dans « Emacs Lisp », il y a « Lisp »…
Et qui dit Lisp, dit macros. Elisp a un mécanisme de macros simple
mais tout à fait fonctionnel, qui ressemble un peu à celui de Common
Lisp, en moins évolué. La définition se fait avec defmacro, on peut
générer des symboles avec make-symbol, et les opérateurs `,
,, et ,@ sont disponibles avec leur sémantique habituelle
(quasi-littéral, substitution d’éléments évalués dans un
quasi-littéral, et substitution et aplatissement).
Par rapport à des systèmes plus sophistiqués, on peut remarquer, par exemple, que les macros ne peuvent être déclarées qu’avec un seul niveau de paramètres (ceux-ci peuvent bien entendu prendre n’importe quelle valeur) ; la structure interne des arguments doit être décortiquée à la main dans le code de la macro.
En pratique, les macros en Elisp sont largement utilisées ; on en voit
un peu partout, tous les jours. Cependant, un détail à garder en tête
lorsque l’on emploie des macros complexes : Elisp, contrairement à la
plupart des implémentations de Common Lisp, ne compile pas ses
fonctions au fur et à mesure, et par là j’entends que le code n’est
même pas traduit en bytecode. Cela signifie que les macros seront
réévaluées à chaque exécution de la fonction… une des raisons pour
lesquelles la plupart des gros paquetages Emacs vous demandent de
compiler avant usage. À titre d’exemple, l’utilisation des macros
fournies par cl-macs ou pcase (dont nous reparlerons un peu plus
loin) sont de bons indicateurs pour identifier les candidats à la
compilation obligatoire…
Et le fonctionnel dans tout ça ? Et la POO ?
Après ce petit tour des fondamentaux du langage, l’heure est venue de poser la question qui fâche : c’est bien joli, tout ça, mais quels possibilités Emacs offre-t-il en termes de programmation pour les hommes (et les femmes), les vrais, qui mangent du fonctionnel au petit déjeuner (et des objets au goûter) ?
Fonctions anonymes et fonctions d’ordre supérieur
Commençons par les choses triviales : Elisp dispose de fonctions
anonymes (avec lambda) et de valeurs-fonctions de première classe,
ce qui rend possible les fonctions d’ordre supérieur — telles que
mapcar — dont l’usage est cependant peut-être un peu moins
populaire que dans d’autres dialectes de Lisp, du fait du manque de
variété de tels opérateurs présents par défaut.
Avec Emacs 24 et la portée lexicale, les fermetures sont maintenant
également lexicales lorsque lexical-binding est actif ; les
variables globales (dites spéciales) demeurent dynamiques quoi qu’il
en soit.
Comme Elisp est un Lisp-2, on ne peut pas tout simplement appeler une
fermeture contenue dans une variable par son nom ; il faut utiliser
funcall ou apply. Elisp offre plusieurs possibilités de traitement
des arguments (par le biais des mots-clés &optional et &rest,
ainsi que &key avec les extensions Common Lisp), mais la
curryfication n’en fait pas partie ; on notera toutefois la présence
de la fonction apply-partially qui, exactement comme son nom
l’indique, retourne le résultat de l’application partielle d’une
fonction à une liste (éventuellement incomplète) d’arguments.
Oh, les jolis motifs…
Les fonctions anonymes, ça ne fait pas tout ; s’il y a un truc que les
fonctionnelleux comme gasche aiment bien, c’est le pattern matching,
ou le filtrage par motifs, en français. C’est traditionnellement le
domaine des langages de la famille ML, qui ont des types algébriques,
et pas franchement le fort d’Elisp, qui a un typage dynamique assez
primitif. Malgré cela, Emacs 24 introduit une petite nouveauté, sous
la forme de la macro pcase. Celle-ci permet un filtrage par motifs
sur les listes et les constantes, prend en charge les gardes
booléennes et les affectations de variables ; que demande le peuple ?
Illustration :
(pcase '(foo (0 1 2) 3)
(`(foo ,(and `(0 1 2) xs) ,x)
(cons x xs)))
=> (3 0 1 2)
Deux détails qui peuvent porter à confusion au départ :
l’utilisation des quasi-apostrophes
`qui introduisent les quasi-littéraux ; la virgule sert, comme d’habitude, à échapper à la quasi-apostrophe — dans ce contexte, « échapper » signifie revenir au format de motif général, qui permet notamment d’attacher des variables ;la présence de la conjonction
anddans les motifs (en plus du plus classiqueor) ;andest utilisée notamment pour affecter des variables à un sous-motif, commeasen Caml.
Avec pcase, on peut imaginer coder simplement un semblant d’union
typée annotée avec des listes et des symboles :
;; Un arbre binaire...
(defun leaf (x) `(leaf ,x))
(defun node (left right) `(node ,left ,right))
(defun height (tree)
(pcase tree
(`(leaf ,_) 0)
(`(node ,left ,right)
(1+ (max (height left) (height right))))))
(height (node (leaf 1)
(node (node (leaf 2)
(leaf 3))
(leaf 4))))
=> 3
Bien sûr, on pourrait se coder une petite macro pour rendre la définition de telles unions un peu moins vilaine (mais là n’est pas vraiment l’objectif de ce billet) :
(defmacro defunion (&rest alternatives)
`(progn
,@(mapcar (lambda (alt)
`(defun ,(car alt) ,(cdr alt)
(list ',(car alt) ,@(cdr alt))))
alternatives)))
(defunion
(leaf x)
(node left right))
Des objets, en veux-tu ? EIEIO
Pour finir sur une note rigolote, pour ceux qui souhaitent se faire du
mal, Elisp dispose depuis la version 23.2 d’EIEIO, sa propre couche de
compatibilité avec CLOS, le système d’objets de Common Lisp. Je n’ai
jamais touché moi-même, mais il paraît que c’est assez complet. On
y retrouve defclass, defgeneric et compagnie. De quoi satisfaire
les envies les plus folles…
Trop chouette, je veux m’y mettre !
Si après cette petite visite guidée, vous êtes encore devant votre
écran, n’avez pas régurgité votre quatre-heures, ou mieux, êtes pris
d’une envie irrésistible de vous essayer à ce langage, ce ne sont pas
vraiment les ressources qui manquent ; il y a un manuel Elisp
(consultable avec info, C-h i). EmacsWiki a également des
ressources dont l’Elisp Cookbook,
pour quelques recettes de base. Alternativement, vous pouvez tout
simplement vous lancer dans l’aventure en lisant et modifiant votre
extension préférée ; quelque part, c’est joindre l’utile
à l’apprentissage (agréable ou pas, selon les personnes). Restez
raisonnable — cc-mode n’est probablement pas le meilleur choix
pour commencer — et tout ira bien ! Et bienvenue dans le monde
merveilleux d’Emacs !
![[Atom]](feed.png)