Langage de programmation Common Lisp

Common Lisp est un langage de programmation de la famille des Lisp, multi-paradigmes, compilé, typé dynamiquement.

Common Lisp est une spécification standardisée par l'ANSI en 1984, et compte plusieurs implémentations. SBCL (Steel Bank Common Lisp) est l'implémentation open-source la plus populaire. SBCL dérive des travaux de la Carnegie Melon University et de tous les financements institutionnels et privés ayant abondé dans les années 80. SBCL produit du code machine efficient, qui la place, dans les tests comparatifs, souvent dans le même groupe que Rust ou Java.

Aujourd'hui, Common Lisp est toujours utilisé dans l'industrie dans beaucoup de domaines: informatique quantique, analyses financières, systèmes de planification, logiciels de modélisation, musique assistée par ordinateur, assistants de preuves, applications web…

Les premières origines des langages Lisp remontent aux années 60 suivant les travaux de John McCarthy.

On distinguera plusieurs dialectes de la famille des Lisp: Common Lisp, la famille des Scheme, Clojure, les langages spécialisés (Emacs Lisp, AutoCAD) et les langages avec une syntaxe Lisp qui transpilent dans un autre langage hôte (tels que LFE pour "Lisp-Flavored Erlang" ou Fennel sur la plateforme Lua).

Installation

Il vous faut installer une implémentation. Sous Ubuntu, plusieurs sont disponibles, mais nous utiliserons sbcl:

$ apt install sbcl rlwrap

Première utilisation

SBCL contient un interpréteur de commandes et un compilateur. Pour évoquer l'interpréteur et obtenir un "REPL" (Read Eval Print Loop) basique en ligne de commande, appelez sbcl.

Mais pour obtenir une interface plus ergonomique, utilisez l'interface à readline avec rlwrap (readline wrapper):

$ rlwrap sbcl
This is SBCL 2.1.5, an implementation of ANSI Common Lisp.
More information about SBCL is available at <http://www.sbcl.org/>.
 
SBCL is free software, provided as is, with absolutely no warranty.
It is mostly in the public domain; some portions are provided under
BSD-style licenses.  See the CREDITS and COPYING files in the
distribution for more information.
* 

Vous pouvez entrer du code directement dans l'interpréteur de commandes. Par exemple:

* (+ 40 2)
;; => 42
* (defun hello (name) (format t "Salut ~a !" name))
;; => HELLO
* (hello "moi")
;; => "Salut moi !"
* (mapcar #'hello (list "moi" "toi"))
;; => Salut moi !Salut toi !
;; (NIL NIL)

Bravo ! Vous venez de voir que:

  • Lisp utilise une notation préfixée. Au lieu d'appeler "2 + 2", soit une opérande, une fonction, une opérande, on appelle la fonction d'abord et les opérandes à la suite, soit "(+ 2 2)".
  • les appels de fonction se font en ouvrant une parenthèse et en la refermant après les arguments: (+ 40 1 1).
  • on crée une fonction avec defun: (defun hello () …), on appelle notre fonction avec (hello …).
  • format t … permet d'écrire sur la sortie standard (t). print existe mais format est beaucoup plus modulaire.
  • on peut référencer une fonction avec la notation #\': #'hello. Les fonctions sont des objets de premier ordre. On peut avoir des variables et des fonctions avec le même nom, elles ne se marcheront pas dessus.
  • on peut appliquer une fonction à plusieurs arguments à la suite: mapcar (souvent "map" dans les langages plus récents).

Et, enfin: la structure des expressions en Lisp est la même que la structure utilisée pour représenter des listes: les parenthèses. (list 40 2) est une liste, '(40 2) est la même liste écrite avec un raccourci, et (+ 40 2) a la même structure qu'une liste. Mais quand on l'évalue, on appelle la fonction + avec deux opérandes. Ainsi, un programme Lisp est, dans la syntaxe Lisp, simplement une liste de listes imbriquées.

L'option --remember de rlwrap permet d'auto-compléter avec la touche TAB les mots précédemment écrits. L'option -i permet de ne pas faire attention à la casse (majuscule ou minuscule, peu importe), l'option -c permet d'auto-compléter les fichiers.
L'implémentation CLISP, également dans Debian, propose une ligne de commande pour le terminal qui est ergonomique par défaut: la complétion des fonctions existantes est disponible, etc. Pas besoin de rlwrap. Mais attention cette implémentation est vieillissante, n'est pas totalement aux normes du langage, elle produit moins de "warnings" que SBCL, le code qu'elle génère est moins efficient que SBCL. On l'évitera quand une application prend de l'ampleur.

Avec un fichier texte

La grande force de Common Lisp est son environnement de développement dynamique, mais on peut commencer de la manière la plus simple possible, en écrivant du code dans un fichier texte (extension .lisp) avec n'importe quel éditeur. S'il indente automatiquement la ligne et s'il permet de visualiser les parenthèses ouvrantes et fermantes, tant mieux.

Créez un fichier "hello.lisp" avec votre éditeur de texte préféré et lancez SBCL.

$ touch hello.lisp
$ rlwrap sbcl
;;
*

Dans votre éditeur, ajoutez la fonction hello vue plus haut.

Depuis SBCL, nous pouvons charger et exécuter le code de ce fichier avec la fonction load:

* (load "hello.lisp")
T

Le T veut dire "true", que tout s'est bien passé. Vous pouvez maintenant utiliser la fonction qui a été définie dans le fichier externe.

Utilisation en script

Presque toutes les implémentations, dont SBCL, permettent de créer un fichier binaire exécutable. Mais nous pouvons également lancer notre programme sans cette étape, simplement à partir des sources.

Dans votre fichier "hello.lisp", ajoutez quelques commandes qui produisent un résultat. Par exemple:

(defun hello (name)
  "Dit salut à NAME."
  (format t "Salut ~a !" name))
 
(print "Je vais dire salut…")
(hello "you")

Vous pouvez lancer ce script depuis le terminal avec sbcl –script:

$ sbcl --script hello.lisp
"Je vais dire salut…" Salut you !
$

On observe que print a affiché les guillemets mais pas format … ~a.

Vous pouvez également utiliser sbcl –load hello.lisp, et dans ce cas vous obtiendrez un REPL après exécution du fichier, ce qui vous permet de continuer à travailler.

Pendant longtemps, les seuls *bons* éditeurs disponibles étaient Emacs et son plugin Slime ainsi que l'IDE LispWorks, qui vient avec l'implémentation du même nom, mais qui est propriétaire. Aujourd'hui, il existe de bons modules pour des éditeurs populaires. Référez-vous à: https://lispcookbook.github.io/cl-cookbook/editor-support.html

Beaucoup de langages "modernes" proposent un "REPL", une invite de commande interactive, qui permet d'écrire du code et d'obtenir un résultat sans passer par une étape supplémentaire de compilation. Par exemple, Python. Mais aucun, à ma connaissance, à part Smalltalk, n'a été pensé pour une utilisation interactive autant que Common Lisp. Ce n'est pas évident à expliquer en quelques phrases, mais voici quand même quelques pistes. Nous vous renvoyons à quelques vidéos (cf la section des liens).

Avec un bon IDE pour CL (typiquement Emacs et Slime, mais aussi SLIMA pour Atom et d'autres), on peut développer son programme interactivement de A à Z, sans avoir besoin de redémarrer le processus Lisp sous-jacent. Toutes les modifications sont ajoutées au fil de l'eau, les tests peuvent être lancés depuis la même image au fil de l'eau. Un cas d'usage classique est:

  • on écrit du code dans un fichier, on démarre le plugin Lisp et on connecte un REPL à son éditeur. Lorsqu'on a écrit une fonction, on compile cette fonction avec un raccourci clavier (C-c C-c). On a compilé dans ce cas UNE fonction. Le compilateur Lisp nous donne s'il le faut des messages de warning: incompatibilités de types, une variable n'est pas utilisée… Si la compilation a réussi (j'insiste: la fonction a été compilée, dans le cas de SBCL c'est en code machine efficient. Dans le cas de l'implémentation CLISP ce serait du "bytecode" intermédiaire) on peut utiliser notre fonction depuis le REPL. Cela permet de la tester, ou de continuer à travailler. Souvent, un développeur Lisp garde son éditeur et son REPL ouvert pendant plusieurs jours, puisqu'il y a rarement besoin de le redémarrer.
  • si notre appel de la fonction dans le REPL échoue, nous obtenons une fenêtre de débogueur interactif. Dans le cas de LispWorks, c'est une fenêtre graphique facile avec des boutons. Dans le cas d'Emacs et Slime, c'est une fenêtre en mode texte, avec un menu pour effectuer des actions. Dans ce débogueur, nous voyons le message d'erreur, la pile des appels de fonction avec leurs arguments. Nous pouvons évaluer du code dans le contexte de cette erreur. Nous pouvons aller à la ligne d'où provient l'erreur, la corriger, compiler le nouveau code (avec le raccourci clavier), retourner dans le débogueur, redémarrer le programme du début ou bien le redémarrer depuis un appel de fonction que l'on choisit. Le programme reprend son exécution de là où on le demande, et on peut observer son exécution finir avec succès.
Common Lisp permet (mais n'impose pas) une différence de travail fondamentale, où l'on agit de manière interactive et incrémentielle avec l'image Lisp: au lieu d'un cycle typique édition / compilation / attente / test, on obtient un cycle édition / test beaucoup plus court.
  • on peut installer de nouvelles librairies en restant dans le REPL, il n'y a pas besoin de redémarrer l'interpréteur.
  • si l'on définit une classe (defclass) et quelques objets, instances de cette classe, puis que l'on modifie la définition de cette classe, les objets existants restent en mémoire. Mieux, ils sont mis à jour. Par exemple, si l'on crée un nouvel attribut pour cette classe, les objets existants (créés lorsque cet attribut n'était pas encore défini) vont être mis à jour pour obtenir ce nouvel attribut dans sa valeur par défaut.
  • Mieux, le langage possède des mécanismes qui permettent de contrôler si et comment la mise à jour de ces objets existants s'effectue. Cela paraît fou, voire inutile! Mais ce sont des mécanismes nécessaires pour des programmes au long cours.
  • on peut mettre à jour un programme "à la volée" sans rien faire de spécial: on utilise la fonction load vue plus haut, et le nouveau code est intégré dans le programme en cours. Si une fonction est re-définie, et bien les nouveaux appels de cette fonction utiliseront la nouvelle définition.
  • on peut se connecter à un programme en cours d'exécution et évaluer du code depuis l'intérieur de ce programme. Il est déconseillé de modifier le code de cette façon (toute trace du code devrait être gardée dans des fichiers et gérée avec un gestionnaire de versions, comme d'habitude), mais on peut explorer l'état du programme de cette façon.

On ne sait pas toujours quand une entreprise, un service web ou un logiciel est basé sur Common Lisp. Voici quelques exemples.

  • pgloader est un outil officiel de la fondation PostgreSQL. À noter qu'il a été ré-écrit de Python à Common Lisp, notamment pour des gains de performances importants.
  • OpusModus est un logiciel reconnu de composition musicale assistée par ordinateur.
  • dans la même veine, OpenMusic est un langage visuel pour la composition musicale.
  • ScoreCloud est une application de notation musicale: jouez de votre instrument ou chantonnez, et l'application écrit la partition.
  • Maxima (et son interface graphique Wxmaxima) est un "Computer Algebra System".
  • Kandria est un jeu de plateforme publié sur Steam.
  • Lem est un éditeur de texte écrit in Common Lisp, qui fonctionne avec un grand choix de langages grâce à son client LSP (Language Server Protocol). Il est prêt à l'emploi pour Common Lisp.

Pour plus, voir les liens ci-dessous.

Ressources d'apprentissage:

Vidéos:

Communauté:

  • common_lisp.txt
  • Dernière modification: Le 11/01/2024, 11:56
  • par 90.65.241.102