Le langage LISP - LibreTlemcen.org

publicité
Université Abou Bakr Belkaïd – Tlemcen
Ecole Doctorale STIC option SIC
Module : Intelligence Artificielle
Exposé
Le langage LISP
Présenté par
Amine Brikci-Nigassa
(nh2 (à) libretlemcen.org)
Année universitaire 2006-2007
Avant-propos
Ce document est un exposé réalisé pour le module d'intelligence artificielle de
la 1ère année de magistère dans le cadre de l'école doctorale STIC (option
Systèmes d'Information et de Connaissances) à l'université Abou-Bakr Belkaïd
de Tlemcen (Algérie).
Le texte est sous licence GNU FDL (GNU Free Documentation License) dans
l'objectif d'en faire une base pouvant être améliorée selon la philosophie du
Libre.
Ce document a été réalisé sous Debian GNU/Linux grâce au logiciel Libre
OpenOffice.org librement téléchargeable sur le site http://fr.OpenOffice.org
Il est disponible aux formats OpenDocument (norme ISO/IEC 26300) et PDF à
l'adresse http://LibreTlemcen.org/nh2/expose_LISP
Pour toutes suggestions, remarques, réactions, il est possible de contacter
l'auteur par courrier électronique :
Amine Brikci-Nigassa : nh2 (à) libretlemcen.org
Note de Licence :
Copyright © 2007 Amine Brikci-Nigassa
Vous pouvez copier, redistribuer et/ou modifier ce document selon les termes
de la Licence de Documentation Libre GNU, Version 1.2 publiée par la Free
Software Foundation à l'adresse http://www.gnu.org/licenses/fdl.html.
2
Table des matières
Avant-propos.......................................................................................................2
Introduction........................................................................................................4
Historique...........................................................................................................5
Particularités de Lisp..........................................................................................7
Programmation fonctionnelle..........................................................................7
Récursivité......................................................................................................7
Les listes..........................................................................................................8
Nature d'un programme Lisp........................................................................10
La boucle read-eval-print..............................................................................11
Le Garbage Collecting...................................................................................11
Utilisation en Intelligence Artificielle...........................................................12
Eléments de syntaxe en Lisp.............................................................................12
Atomes.......................................................................................................12
Listes.........................................................................................................13
Exemple de programme en Scheme..............................................................13
Conclusion.........................................................................................................18
3
Introduction
Souvent associé au domaine de l'intelligence artificielle, le langage LISP
trouve un regain d'intérêt auprès de diverses communautés de programmeurs
depuis quelques années.
Assez différent des langages impératifs actuellement communs, presque tous
descendants d'ALGOL (Pascal, C ou encore Java), la beauté et la puissance de
ses concepts élémentaires en font une expérience que devrait connaître tout
bon programmeur.
C'est, après FORTRAN, le plus ancien des langages de haut niveau encore
utilisés aujourd'hui. Ses applications sont depuis longtemps sorties du champ
des laboratoires de recherche puisque ses capacités en font un langage aux
applications multiples.
Sa simplicité est également remarquable. La facilité de sa syntaxe, le faible
nombre de ses mots-clés, son interactivité sont des critères qui amènent
certains à l'utiliser comme langage d'initiation à la programmation.
4
Historique
Pr John Mac Carthy, le père du « LISt Processing language »1 est aussi
l'inventeur du terme « Intelligence Artificielle ». En concevant ce langage, en
1958, le but de ce chercheur au MIT2 était d'appliquer les théories du lambdacalcul du mathématicien Alonzo Church, ouvrant la voie au paradigme de la
programmation fonctionnelle.
Lisp a introduit des concepts nouveaux comme la structure si-alors-sinon
reprise dans la plupart des langages de haut niveau (tels que ALGOL et ses
successeurs). Il influencera les concepteurs de Smalltalk (l'un des premiers
langages orientés objet). C'est d'ailleurs avec Lisp qu'est apparu le premier
« ramasse-miettes »3 connu des programmeurs Java.
La nature fonctionnelle du langage Lisp a longtemps posé l'inconvénient de la
lenteur et de la forte consommation de mémoire (qui ont autrefois induit la
nécessité de machines dédiées), c'est une des raisons pour lesquelles sa
popularité n'a ressurgi qu'après plusieurs décennies.
Depuis sa conception, plusieurs dialectes de Lisp ont été développés
(MACLISP, InterLisp...) mais les deux les plus employés actuellement sont
Common Lisp et Scheme4, le premier étant riche et complexe et le deuxième
se voulant épuré (mais tout aussi puissant). Chacun a fait l'objet d'une
normalisation (respectivement par l'ANSI et l'IEEE) et leurs implémentations
sont nombreuses, sous la forme d'interpréteurs et/ou compilateurs (la plupart
Libres/Open Source et disponibles sur plusieurs plateformes).
Une autre variante également connue est Emacs Lisp qui est utilisée comme
langage intégré à l'éditeur de texte GNU Emacs5 et qui permet entre autre
d'étendre ses fonctionnalités.
Notons aussi que Lisp a inspiré le langage LOGO, développé à la fin des
1 Les mauvaises langues prétendent que c'est plutôt l'acronyme de « Lots of
Irritating Superfluous Parentheses » ou encore « Lots of Idiot and Silly Parentheses »...
2 Massachussets Institute of Technology
3 Traduction française de « garbage collector », aussi appelé « glaneur de cellules »
4 Prononcer 'skim'
5 Emacs est le premier logiciel du projet de système d'exploitation libre GNU réalisé par la
Free Software Foundation. Il est entre autre disponible avec les distributions GNU/Linux
5
années 60, très utilisé pour l'apprentissage de la programmation aux enfants
(en manipulant une tortue qui dessine des formes géométriques à l'écran).
6
Particularités de Lisp
Programmation fonctionnelle
En inventant le lambda calcul, Church inspira à Mc Carthy un nouveau
paradigme de programmation, pour lequel toutes les entités sont considérées
comme des fonctions.
Contrairement à la programmation impérative (FORTRAN, Pascal ou C par
exemple), les valeurs manipulées ne sont pas stockées dans des variables dont
l'état change au cours du programme. En programmation fonctionnelle, les
valeurs sont maniées d'une fonction à l'autre en tant que paramètres pour les
unes et que valeurs de retour pour les autres.
La modularité est de mise puisqu'un programme n'est en fait qu'une collection
de fonctions conçues séparément (qui complètent les fonctions déjà fournies
par le langage).
Lisp est un pionnier de la catégorie, mais ce n'est pas un langage fonctionnel
pur. Il est de nature multi-paradigme, ce qui lui permet d'y inclure les
concepts de la programmation impérative. Le dialecte Scheme constitue (dans
une certaine mesure) une tentative de simplification et d'amélioration du
langage.
Récursivité
Toute la puissance de Lisp ne pourrait être obtenue sans l'emploi de fonctions
récursives.
La récursivité n'est pas l'apanage de ce langage mais sa nature fonctionnelle
fait qu'elle est beaucoup plus employée, ne serait-ce que pour implémenter un
simple boucle. En effet, bien que tous les dialectes prévoient des structures de
boucles, il est plus élégant d'employer une fonction récursive (même si cela
aboutit au même résultat).
Il faut cependant faire attention à n'utiliser que des récursions terminales, i.e.
des fonctions où l'appel récursif est à la fin de leur définition. C'est le seul
7
moyen d'éviter les débordements de pile qui se produisent quand la fonction
doit sauvegarder ses résultats intermédiaires avant l'appel. La récursion
terminale fait partie de la norme de Scheme.
Les listes
Comme son nom l'indique, la principale structure de données que Lisp
manipule est la liste.
Dans sa première implémentation historique, Lisp utilisait deux registres de
l'IBM 7046 qui étaient accessible par les macros assembleur car (contents of
address register) et cdr (contents of decrement register) pour manipuler ses
données.
Une liste est donc une chaîne constituée par une série de doublets de
pointeurs, chaque paire de pointeurs contient l'adresse d'un élément de la
liste (le car) et celle de la paire suivante (le cdr), qui à son tour pointe sur
l'élément suivant et sur la paire qui le suit... Le car désigne donc la tête de la
liste et le cdr la liste constituée par les éléments suivants. Le dernier cdr de la
liste contient une adresse particulière qui par convention désigne une liste
vide (notée NIL ou () selon les dialectes).
La représentation en diagramme « boîtes-pointeurs » permet de visualiser les
paires sous formes de boîtes avec, à gauche, les car qui pointent sur les
éléments de la liste et, à droite, les cdr qui pointent sur la boîte suivante. La
barre oblique dans le dernier cdr représente l'adresse « NIL ».
1
2
3
Une liste est notée en Lisp en entourant les éléments par des parenthèses et
en les séparant par des espaces. La liste ci-dessus se note donc (1 2 3). Elle
est constituée de trois atomes. Les atomes peuvent être des nombres ou des
symboles ; ils constituent avec les listes les objets de base du langage.
6 L'IBM 704 était un ordinateur à lampes, qui ne connaissait pas encore la technologie des
transistors...
8
Néanmoins, la liste n'est pas la seule structure de donnée existant en Lisp. En
effet, les paires (aussi appelées conses ou cellules cons) qui constituent les
listes peuvent être agencées de plusieurs autres manières, pour construire
des arbres ou toute autre structure complexe. Voici un exemple d'arbre simple
noté (1 (2/7 3.14) A "foo") :
A
1
2/7
"foo"
3.14
Cet exemple est en fait un cas particulier de liste à 4 éléments dont le 1er le
3ème et le 4ème sont des atomes mais où le deuxième élément est lui-même une
liste de 2 éléments atomiques.
Il est possible de créer des structures qui ne sont pas des listes, la plus simple
étant la paire :
Cette paire d'éléments est notée (1 . 2), elle n'est pas considérée
par Lisp comme une liste, puisque le cdr ne référence pas une liste.
1
2
Les expressions arithmétiques et les appels de fonctions sont construites sous
forme de listes grâce à la notation préfixée parenthésée. En effet, l'expression
x
e 
1
s'écrira (+ (exp x) (/ 1 (sqrt x))) et correspond à la liste
x
arborescente ci-dessous.
9
+
exp
x
/
1
sqrt
x
Notons au passage qu'il est inutile de donner des priorités aux opérateurs
(source d'ennuis dans les langages classiques).
Nature d'un programme Lisp
Nous remarquons ci-dessus que l'appel d'une fonction se fait dans une liste
dont le premier élément est la fonction elle-même et les suivants sont les
paramètres :
(fonct param1 param2 ...)
et que les opérateurs arithmétiques sont considérés comme des fonctions :
(+ 2 3) n'est autre qu'un appel à la fonction d'addition, notée +, avec les
paramètres 2 et 3. Le résultat (la valeur renvoyée par la fonction) se
substituera à l'expression entière dans l'expression englobante.
Les programmes Lisp sont des fonctions, donc des listes, et leur exécution
revient donc à l'évaluation de ces fonctions. Le programme source étant lui
même une structure de donnée manipulable, il apparaît facile de faire de la
métaprogrammation en Lisp (pour étendre la syntaxe du langage par
exemple).
10
La boucle read-eval-print
L'exécution d'un programme Lisp passe par trois étapes dites « boucle readeval-print »7.
read : Le programme source, qui est une liste parenthésée (une
S-expression), est transformé en une structure de liste. Si un compilateur est
utilisé, cette transformation peut se faire en langage machine ou en bytecode.
eval : C'est l'exécution proprement dite. La structure de liste est évaluée,
retournant une autre structure comme résultat. Pour évaluer une fonction (qui
est une liste, rappelons le), eval commence par évaluer chacun des arguments
du cdr puis leur applique la fonction désignée par le car. L'évaluation d'un
argument peut évidemment nécessiter d'appliquer les fonctions qui s'y
trouvent.
print : L'affichage du résultat peut être simple, s'il s'agit par exemple d'un
simple atome. Si le résultat est une liste il nécessitera de parcourir la liste
pour pouvoir l'afficher.
Le Garbage Collecting
Les structures de données avancées sont bien utiles mais elles ont
l'inconvénient d'occuper de la mémoire. La conception de Lisp nécessitait un
mécanisme permettant de libérer la mémoire des éléments inutilisés. C'est
ainsi que Mc Carthy pensa au Garbage Collector (« Glaneur de Cellules » ou
« ramasse-miettes » en français). Il s'agit d'un processus qui se déclenche
automatiquement pour supprimer de la mémoire toutes les informations qui
ne sont plus référencées dans le programme à un instant donné.
Il permet de libérer le programmeur de la tâche fastidieuse de
« désallocation » qui est source d'erreurs et de problèmes dans de nombreux
langages modernes (comme le C++). Cette idée novatrice fut reprise maintes
fois, Sun l'a utilisée pour le langage Java par exemple.
7 Read-Eval-Print Loop (REPL)
11
Utilisation en Intelligence Artificielle
De par sa nature fonctionnelle et parce qu'il est adapté à la manipulation des
listes et des arbres, Lisp est un langage de prédilection pour la plupart des
domaines de l'Intelligence Artificielle, où la programmation exploratoire joue
un rôle prépondérant. Lisp est utile quand il s'agit d'explorer une
arborescence. Il est particulièrement aisé de programmer des analyseurs
syntaxiques (l'analyseur syntaxique du premier compilateur Lisp fut écrit en
Lisp), des systèmes experts ; les réseaux de neurones et autres algorithmes
génétiques sont souvent réalisés en Lisp, ainsi que les agents intelligents et
les systèmes de gestion de connaissances.
Cependant, Lisp s'est avéré adapté à bien d'autres domaines dont on pourrait
citer : l'animation et les graphismes, la bioinformatique, le e-commerce, le
data mining, les applications pour EDA/Semiconducteurs, les finances, la CAO
mécanique, la modélisation et la simulation, le langage naturel, l'optimisation,
l'analyse des risques, l'ordonnancement, les télécommunications, et le Web
Authoring...
Eléments de syntaxe en Lisp
Le dialecte de Lisp que nous utiliserons à titre d'illustration est Scheme. Les
différences avec les autres dialectes (Common Lisp notamment) sont minimes,
surtout quand il s'agit des notions de base.
Atomes
Les atomes sont des suites de caractères alphabétiques et/ou numériques,
seuls certains caractères spéciaux ne peuvent être utilisés.
Exemples : 3.14, x, tlm13, 5doigts, 72+
Les atomes ayant une valeur numérique sont des nombres, les autres sont des
symboles.
Les symboles peuvent servir d'identificateurs (mais ils peuvent être manipulés
sans avoir de signification particulière).
Lisp est « insensible à la casse », il ne distingue pas les minuscules des
12
majuscules. Tlemcen et TLeMcen désignent donc le même symbole.
Les constantes booléennes vrai et faux sont notées #t et #f en Scheme.
Les chaînes de caractères sont entourées de guillemets (ex: "Salut!") mais
les caractères sont notés d'une manière particulière – exemple : #\a (qui est
différent de la chaîne "a").
Listes
Une liste est une suite d'atomes et/ou de listes séparés par des espaces et
entourés par des parenthèses.
Exemples : (1 2 3 4), (1 (21 22) (31 (311 312)) 4 (51 52)),
(x y z t), (+ (exp x) (/ 1 (sqrt x)))
Un cas particulier est celui de la liste vide () notée aussi NIL en Common Lisp
(mais pas en Scheme).
Exemple de programme en Scheme
Voici un exemple de programme simple8 afin d'illustrer les principes de la
syntaxe de Scheme. Il s'agit du jeu « Morpion » (en anglais Tic-tac-toe). Le
programme doit arbitrer une partie entre le joueur O et le joueur X, en
désignant le vainqueur dès qu'il aligne trois symboles. Le jeu est nul si la
grille est remplie sans qu'il y ait de gagnant.
Pour éviter de compliquer le programme, celui-ci ne comporte ni vérification
pour tester la case jouée, ni « mode IA » (le programme ne sert que d'arbitre
entre 2 joueurs humains).
8 Adapté d'un sujet de travaux pratiques donné en 1998 à l'université de Caen
13
(define (morpion) (boucle '((- - -) (- - -) (- - -)) 'X 'O))
(define (boucle matrice joueur1 joueur2)
(print "position pour le " joueur1 " ?")
(let ((ligne (saisie "ligne: ")))
(if (eqv? ligne 'q) 'abandon
(let* ((colonne (saisie "colonne: "))
(new-matrice (nouveau matrice ligne colonne joueur1)))
(newline)
(affiche-matrice new-matrice) (newline)
(cond ((gagne? new-matrice) (display "le gagnant est : ") joueur1)
((partie-nulle? new-matrice) (display "partie nulle !!! ") 'Fin)
(else (boucle new-matrice joueur2 joueur1)))))))
(define (saisie message) (display message) (read))
(define (gagne? m)
(or (identiques?
(identiques?
(identiques?
(identiques?
(identiques?
(identiques?
(identiques?
(identiques?
(car (car m)) (cadr (car m)) (caddr (car m)))
(car (cadr m)) (cadr (cadr m)) (caddr (cadr m)))
(car (caddr m)) (cadr (caddr m)) (caddr (caddr m)))
(car (car m)) (car (cadr m)) (car (caddr m)))
(cadr (car m)) (cadr (cadr m)) (cadr (caddr m)))
(caddr (car m)) (caddr (cadr m)) (caddr (caddr m)))
(car (car m)) (cadr (cadr m)) (caddr (caddr m)))
(car (caddr m)) (cadr (cadr m)) (caddr (car m)))))
(define (identiques? a b c)
(and (equal? a b) (equal? b c) (not (equal? A '-))))
(define (partie-nulle?
(and (not (member '(not (member '(not (member '-
m)
(car m)))
(cadr m)))
(caddr m)))))
(define (nouveau m i j joueur)
(new-ligne m i (new-ligne (nieme m i) j joueur)))
(define (new-ligne l i val)
(cond ((null? l) '())
((= i 1) (cons val (cdr l)))
(else (cons (car l) (new-ligne (cdr l) (- i 1) val)))))
(define (nieme l i)
(if (= i 1) (car l) (nieme (cdr l) (- i 1))))
(define (affiche-matrice m)
(print " " (caar m) " | " (cadar m) " | " (caddar m))
(print "---+---+---")
(print " " (car (cadr m)) " | " (cadr (cadr m)) " | " (caddr (cadr m)))
(print "---+---+---")
(print " " (car (caddr m)) " | " (cadr (caddr m)) " | " (caddr (caddr m))))
14
Le programme est constitué de 10 fonctions définies grâce au mot-clé define.
La structure de donnée utilisée ici est une matrice constituée d'une liste de 3
listes à 3 éléments atomiques. Ces éléments sont initialisés au symbole « – ».
Ils seront remplacés au cours de la partie par des « O » et des « X ».
La fonction principale est morpion et n'a pas d'arguments. Elle ne fait
qu'appeler la fonction boucle avec pour arguments une « matrice vide » et les
symboles représentant les 2 joueurs. On remarquera le caractère « ' »
(apostrophe, ou quote en anglais): il indique que l'élément qui le suit ne doit
pas être évalué mais pris tel quel (l'évaluation de la liste matrice par exemple
n'aurait pas de sens et retournerait une erreur puisque son premier élément
n'est pas une fonction).
Nous avons utilisé l'interpréteur Scheme « Bigloo » pour Unix pour cet
exemple. Le fichier du programme écrit avec un éditeur de texte dans le
fichier « morpion.scm » est chargé par la fonction load de Bigloo.
Pour exécuter le programme, il suffit d'appeler la fonction (morpion) à l'invite
de l'interpréteur (1:=>).
15
nh2@debian:~$ bigloo
-----------------------------------------------------------------------------Bigloo (2.6e)
,--^,
`a practical Scheme compiler'
_ ___/ /|/
Tue Oct 19 12:13:41 CEST 2004
,;'( )__, ) '
Inria -- Sophia Antipolis
;; //
L__.
email: [email protected]
'
\
/ '
url: http://www.inria.fr/mimosa/fp/Bigloo
^
^
-----------------------------------------------------------------------------Welcome to the interpreter
1:=> (load "morpion.scm")
morpion
boucle
saisie
gagne?
identiques?
partie-nulle?
nouveau
new-ligne
nieme
affiche-matrice
morpion.scm
1:=> (morpion)
position pour le X ?
ligne: 2
colonne: 2
- | - | ---+---+--- | X | ---+---+--- | - | position pour le O ?
ligne: 1
colonne: 1
O | - | ---+---+--- | X | ---+---+--- | - | -
16
Les autres fonctions peuvent être appelées individuellement :
1:=> (gagne? ((O O O) (X - X) (- - -)))
*** ERROR:bigloo:eval:
Unbound variable -- O
#unspecified
L'oubli du quote entraîne l'évaluation de la matrice et donc une erreur de
Bigloo.
1:=> (gagne? '((O - X) (- O X) (X X O)) )
#t
La fonction gagne? retourne une valeur booléenne.
1:=> (affiche-matrice '((O - X) (- O X) (X X O)) )
O | - | X
---+---+--- | O | X
---+---+--X | X | O
O
La fonction affiche-matrice utilise la fonction print de Bigloo (qui n'existe
pas dans le standard de Scheme) pour afficher la matrice donnée en
paramètre. Elle retourne le dernier symbole affiché (ici « O ») mais cette
valeur n'est pas utilisée dans le programme.
1:=> (nouveau '((O - X) (- O X) (X X -)) 3 3 'O)
((O - X) (- O X) (X X O))
La fonction nouveau permet de générer une nouvelle matrice à partir d'une
autre, en remplaçant un symbole par un autre (ici « O » remplace « – » à la
3ème ligne, 3ème colonne).
1:=> (nieme '((O - X) (- O X) (X X -)) 2)
(- O X)
nieme est une fonction récursive simple qui, appelée avec pour arguments une
liste-matrice m et un indice n, retourne le nième élément de m.
17
Conclusion
Pour conclure cet exposé, on citera le programmeur Eric Raymond :
Lisp is worth learning for the profound enlightenment
experience you will have when you finally get it; that
experience will make you a better programmer for the rest of
your days, even if you never actually use Lisp itself a lot.
Eric S. Raymond, "How to Become a Hacker"
(Lisp vaut la peine d'être appris pour l'expérience profondément enrichissante que
l'on obtient quand on finit par l'assimiler. Cette expérience là fera de vous un
meilleur programmeur pour le restant de vos jours, même si vous n'utilisez jamais
vraiment Lisp lui-même énormément.)
18
Références bibliographiques
●
J.P. Barthès « Intelligence artificielle » Supports de cours au
Département de Génie Informatique, Université de Technologie de
Compiègne, 1998. http://www.utc.fr/_barthes/IA01
●
Laurent Bloch « Initiation à la programmation avec Scheme » Ed.
Technip, Paris, 2001.
●
Louis Gacôgne « 512 problèmes corrigés Pascal, C++, Lisp, Prolog » Ed.
Ellipses, 1996.
●
Denis Howe « Free On-line Dictionary of Computing » 1993.
http://www.foldoc.org
●
Jean-François Perrot « Introduction à l'intelligence artificielle » Notes de
cours au Laboratoire d'informatique de Paris-6, Université Pierre et
Marie Curie, 2004. http://www-poleia.lip6.fr/_jfp/insia
●
Eric S. Raymond « The on-line hacker Jargon File » 2005.
http://catb.org/~esr/jargon
19
Téléchargement