fred voisin’s website

computer music producer, since 1989

nanotutorial LISP

15 fév. 2006

Merci de me corriger le cas échéant !

Une machine LISP est une machine universelle dans l’esprit lambda-calcul. On communique avec l’interpréteur LISP de la machine à l’aide d’expressions, appellées S-expressions, écrites entre parenthèses. L’interpréteur interpréte, évalue ou exécute la list-expression.
Par exemple :
(or nil T)  

L’interpréteur commence toujours par lire le premier élement de la liste puis les suivants. Il possède un « signe d’invite » (?, >, *... selon les versions) après lequel on peut écrire la list-expression. Une S-expression précédée d’un guillemet simple [ ’ ] ne sera pas interprétée, mais seulement lue telle quelle comme une liste :

 ? '(or nil T) (or T nil)  

Pour effectuer une procédure, une opération, un programme ou un processus, nous disposons :

des deux valeurs booléennes : nil (néant, faux) et T (true, vrai) ;
des nombres rationnels et imaginaires ;
de symboles et variables libres et arbitraires, ex :
(a b c d), (toto, tata, titi)
des listes, par exemple :

(x y ((0 1 0) (1 0 1)) toto), '(<uneoie> <deuxoies> <troisoies>) ;

d’un grand nombre de fonctions ou instructions prédéfinies ou spéciales (liste), définissant le langage LISP et permettant aussi de définir des structures, des objets, des méthodes, et des processus : référence du langage Common Lisp, en anglais. L’interpréteur lisp tentera toujours d’exécuter le premier élement de chaque list-expression :

? (car '(0 1))
0
? (cadr '(0 1))
(1)  

puis les suivants, par arborescence... de listes ; par exemple :

? (car (cadr '(0 1)))
1  

ou on applique l’opération car - qui donne le premier élément de la liste - au résultat de l’expression (cadr ’(0 1)) - qui donne la liste (0 1) otée du premier élément, soit (1).

Une structure particulière de liste peut définir un lambda calcul ; par exemple :

(lambda (x) x)

exécute un calcul lambda qui, ici, donne x pour chaque x (ou, autrement dit, le premier élément de la liste, lambda, prend x pour argument et donne x. x représente ici le corps du lambda calcul. Le nombre d’arguments du calcul lambda peut être :

in(dé)fini :

(lambda (a b c d e f g h i j k l ...)

récurrent :

? (apply (lambda (x) (if x (lambda (x) x) x)) '(nil))
NIL
? (apply (lambda (x) (if x (lambda (x) x) x)) '(T))
#<Interpreted Function (LAMBDA (X) X) {580705F1}>

En fait dans ce dernier cas, le calcul n’est pas vraiment récurrent car les deux symboles lambda ne renvoient pas au même calcul, mais à deux imbriqués). Pour que le calcul soit récurrent, le calcul doit s’appeler lui-même au moyen d’un symbole unique - lambda est en Lisp un symbole spécial référent à une fonction (programme) « anonyme ». Pour un exemple de calcul récurrent, cf. infra.

Lorsqu’on défini un lambda calcul avec le symbole lambda en premier élément de l’expression, la définition dure le temps du (lambda) calcul : c’est une fonction anonyme, elle ne reste pas en mémoire. On peut exécuter le calcul ainsi :

(apply (lambda (x) x) '(listes))

Pour plus de commodité, un lambda calcul peut-être associé à un symbole en mémoire de l’environnement Lisp, et ainsi de nommer une fonction. Pour ce, on utilise la fonction prédéfinie (ou macro) defun (ou define, selon les dialectes notamment Scheme) :
(defun plus (x y) (incf x y))
ce qui est une list-expression toujours interprétée, avec comme premier élément define, symbole connu de l’interpréteur lisp, qui interprête ensuite le second element plus comme le nom de la fonction à définir avec une liste (x y) d’arguments avec lesquels sera interpréte la liste (incf x y) qui défini le corp (contenu) ainsi associé au symbole plus. Pour définir une fonction avec un nombre indéfini d’arguments, on utilise le mot-clef &rest, par exemple :

(defun tata (&rest arguments) (print arguments) )

permet d’éxécuter :

(tata 1 2 3 4 5 6 ...)

Un calcul récurrent peut se définir ainsi :

(defun toto (liste) (when liste (print (car liste)) (toto (cdr liste))))

Même principe pour les arguments optionnels avec &optional, et les mots-clefs avec &key.
our définir une variable, on utilise l’expression :

? (defvar foo '(cette liste n est pas evaluee a cause du quote) )

(cette liste n est pas evaluee a cause du quote)

ou bien :

? (defvar fooo (plus 1 2) )
3
? fooo
3  

On interroge le contenu de la variable fooo, résultat de la l’expression : (defvar ...)

Le contenu d’une variable est changée avec l’expression :

(setf foo T)

Une variable peut-etre definie comme étant une fonction (anonyme) :

? (def a (lambda (x) (b)))
a
? (def b (lambda () (+ x x)))
b

et ainsi demander à évaluer l’expression :

?(a 5)
10.0

qui exécute donc dynamiquement les deux fonctions lambda.

Au sein d’une fonction ou d’un programme que nous définissons, les variables temporaires nécessaires au calcul sont définies avec la fonction let :
La structure d’une fonction est :

(defun nom (liste des arguments) (let ((a 1) (b 8)) (calcul a b ) ) )

L’indentation combinée au groupement des parenthèses par lignes définissant les expressions facilitent la lecture et la lisibilité de la structure du code :

(defun nom (liste des arguments)
(let ((a 1) (b 8))
(calcul a b ) ) )

Le symbole point-virgule ; annonce un commentaire qui ne sera pas interprété, jusque la fin de la ligne.

Dans certains cas ambigus, les symboles #’ placés devant une expression permettent de préciser que le contenu (la liste) doit être évalué :

(eval #'(or nil T))

ou qu’il s’agit d’un fonction existente, et non une liste ou une variable :
(eval #'get-universal-time)

Ensuite, il ne s’agit que de vocabulaire.
L’énumération d’une liste se fait de plusieurs manières :

? (dolist (élément '(liste des éléments) (print élément)
liste
des
éléments
<code>
ou bien par exemple :
<code>
(mapcar #'(lambda (element) (print element)) '(liste des éléments))

ou encore :

(dotimes (n nombre) (print n))
1
2
3

...
ou la fonction recurrente toto définie plus haut...
ou encore :

(loop for elt in '(liste des elements) do (print elt))

qui n’est déjà presque plus du lisp, loop étant une implémentation tardive sous l’influence des autres langages. En Scheme, langage donc le LISP est un dialecte, seule la fonction do est utilisée).

Quelques liens pour continuer

f ;-)