Home About us Products Services Contact us Bookmark
:: wikimiki.org ::
Common Lisp

Common Lisp

ko:커먼 리스프 Common Lisp est un langage fonctionnel impur de la famille Lisp.

Citation

« Common Lisp, la force d'une boule de glaise, est le patriarche bien vivant de la famille Lisp. L'Ingénieur lui a donné forme, et non le Chercheur en quête de clarté conceptuelle. Mais en dépit de son applicabilité pratique, CL offre des plaisirs hélas inconnus de la programmation mainstream. » [http://www.schnada.de/hylin/tao.html Le Tao de la Récursion]

Introduction

Common Lisp est un dialecte de Lisp standardisé par l'ANSI X3.226-1994. Développé pour standardiser les variantes divergentes de Lisp qui l'ont précédé, ce n'est pas une implémentation mais une spécification à laquelle les implémentations Lisp essayent de se conformer. Il est fréquemment abrégé en CL. Common Lisp est un langage de programmation à usage général, a contrario de dialectes de Lisp comme Emacs Lisp et AutoLisp, qui sont des langages d'extension embarqués dans des produits particuliers. Contrairement à de nombreux Lisp plus anciens, mais comme Scheme, Common Lisp utilise la portée lexicale par défaut pour les variables. Common Lisp est un langage de programmation multi-paradigmes qui :
- Accepte des techniques de programmation impérative, fonctionnelle et orientée objet (CLOS).
- Est typé dynamiquement, mais avec des déclarations de type optionnelles qui peuvent améliorer l'efficacité et la sûreté,
- Dispose d'un système de gestion d'exceptions puissant, nommé Condition System (système de gestion de conditions),
- Est syntaxiquement extensible à travers des fonctionalités comme les macros et les macro de lecture.

Syntaxe

Common Lisp est un Lisp ; il utilise des s-expressions pour dénoter à la fois le code et les structures de données. Les appels de fonction et de macros sont écrits en tant que listes, avec le nom de la fonction en première place, comme dans ces exemples : (+ 2 2) ; ajoute 2 et 2, renvoie 4 (setf e 2.7182817) ; assigne 2.7182817 à la variable e (defun carre (x) (
- x x)) ; définit une fonction qui met un nombre au carré (carre 3) ; exécution de la fonction : retourne 9

Types de données

Common Lisp a une pléthore de types de données, plus qu'aucun autre langage.

Types scalaires

Nombres

Les types numériques incluent les entiers, les rationnels, les nombres à virgule flottante et les nombres complexes. Common Lisp utilise des Grands Nombres (en: bignums) pour représenter des valeurs numériques de taille et de précision arbitraires. Le type rationnel représente les fractions de façon exacte, une facilité absente de la plupart des langages. Common Lisp convertit automatiquement les valeurs numériques entre ces types de façon apropriée. Voici la tour numérique, c'est à dire la hiérarchie des types numériques de Common Lisp : complex ratio fixnum / / / number <--+-real--+-rational--+-integer--+-bignum \ \ \ (1) signed-byte---unsigned-byte---bit \ float--+-short-float \-single-float \-double-float \-long-float (1) integer et signed-byte sont des spécifications de types disjointes ; toutefois, les domaines sont identiques. A titre d'exemple, l'expression (+ (sqrt -2) (/ 6 4)) retourne #C(3/2 1.4142135) c'est à dire un nombre complexe dont la partie imaginaire est le flottant 1.4142135 et la partie réelle est le rationel 3/2.

Caractères

Le type Common Lisp caractère n'est pas limité aux caractères ASCII ; cela n'est pas surprenant car Lisp est plus ancien que l'ASCII. Certaines implémentations modernes supportent les caractères Unicode. [http://www.cliki.net/Unicode%20Support]

Symboles

Le type symbole est commun aux langages Lisp, mais largement inconnu en-dehors. Un symbole est un objet nommé, unique. Les symboles en Lisp sont similaires aux identifieurs de variables dans d'autres langages, en ce qu'ils peuvent être utilisés comme variables pour stocker des valeurs ; toutefois, ils sont plus généraux et peuvent être utilisés pour eux-mêmes également. Normalement, lorsqu'un symbole est évalué, sa valeur en tant que variable est retournée. Il existe des exceptions à ces règles, qui sont les mots-clefs (comme :foo ou :bar), et les valeurs booléennes qui sont représentées par les symboles réservés T et NIL. Exemples : foo ;; -> La variable FOO n'a pas de valeur. (function foo) ;; -> La fonction FOO n'est pas définie. L'opérateur QUOTE protège les symboles de l'évaluation (lorsqu'on veut utiliser un symbole pour lui-même) : (quote foo) ;; -> FOO 'foo ;; -> FOO On peut demander si un symbole est lié à une valeur ou une fonction : (boundp 'foo) ;; -> NIL (pas de valeur liée) (fboundp 'foo) ;; -> NIL (aucune fonction nommée FOO n'existe) Association symbole-valeur : (defparameter foo 77) ;; -> FOO foo ;; -> 77 Association symbole-fonction : (defun foo (bar) (1+ bar)) ;; -> FOO Appel de la fonction FOO avec la valeur FOO (illustre le fait qu'un symbole dispose de slots séparés pour les valeurs et les fonctions) : (foo foo) ;; -> 78 (boundp 'foo) ;; -> T (fboundp 'foo) ;; -> T (function foo) ;; -> #

Structures de données

Séquences

Les séquences sont un type abstrait représentant une collection ordonnée d'éléments. Les types concrets dérivés de séquence sont les listes et les vecteurs (y compris les vecteurs de bits et chaînes de caractères). De nombreuses fonctions sont disponibles pour les séquences.

Paires, Listes

Comme dans tout autre Lisp, les listes en Common Lisp sont composées de conses (pluriel), parfois appelés cellules cons ou paires. Un cons est une structure de données de deux éléments de type T, appelés son car et son cdr. Une liste est une chaîne de conses liés, où le cdr de chaque cons pointe sur l'élément suivant, et le dernier cdr pointe sur la valeur NIL. Les conses peuvent être facilement utilisés pour implémenter des arbres ou toute structures de données complexe ; bien que dans ce dernier cas il soit recommandé d'utiliser des structures ou des classes. L'arbre (1 (2/7 3.14) A "foo") est représenté par la chaîne de CONS suivante : 300px Il peut être construit de différentes façons, nous en citons deux : (list 1 (list 2/7 3.14) 'a "foo") (cons 1 (cons (cons 2/7 (cons 3.14 NIL)) (cons 'a (cons "foo" NIL))))

Tableaux

Common Lisp supporte les tableaux de dimensions arbitraires, et peut aussi redimensionner dynamiquement les tableaux. Des tableaux multidimensionnels peuvent être utilisés pour les mathématiques des matrices. Seuls les tableaux à une dimension (nommés vecteurs) sont un sous-type de séquence. Les tableaux peuvent être spécialisés par le type des éléments qu'ils contiennent. En particulier, les vecteurs de bits et les vecteurs de caractères (chaînes) sont fournis en standard par le langage. Exemple de création d'un tableau à trois dimensions (4 x 2 x 3) et initialisé : (make-array '(4 2 3) :initial-contents '(((a b c) (1 2 3)) ((d e f) (3 1 2)) ((g h i) (2 3 1)) ((j k l) (0 0 0)))) ... cela retourne : #3A(((A B C) (1 2 3)) ((D E F) (3 1 2)) ((G H I) (2 3 1)) ((J K L) (0 0 0)))

Tables de hachage

Les Tables de hachage stockent des associations entre objets. N'importe quel type d'objet peut être utilisé comme clef ou valeur. Les tables de hachage, comme les tableaux, sont automatiquement redimensionnées si nécessaire.

Paquetages

Les paquetages (packages) sont des collections de symboles, utilisés principalement pour partitionner un programme en espaces de noms. Un paquetage peut exporter certains symboles, les marquant comme une partie d'une interface publique. Les variables et méthodes dites privées des langages à objets classiques (principe de l'encapsulation) sont obtenues en Lisp en les déclarant dans un espace de nom, sans les exporter.

Structures

Les Structures, similaires au structs du C et aux records (enregistrements) du Pascal, représentent des structures de données de complexité arbitraire, avec un nombre quelconque et tout type de champs (appelés slots). Les structures supportent une forme limitée d'héritage. Pour les besoins de la programmation orientée objet, on se reportera à CLOS.

Classes et Objets

Common Lisp a été le premier langage à objets standardisé (en 1995, par l'ANSI). La partie du langage traitant des objets se nomme CLOS pour Common Lisp Object System. CLOS est généralement considéré comme le système à objets le plus général et le plus complet, en ce sens que les autres langages à objets peuvent être décrits dans les termes de CLOS, mais pas l'inverse. Les caractéristiques saillantes de CLOS sont les suivantes :
- c'est un système à classes (il existe en effet des systèmes à prototypes)
- les classes elles-mêmes sont des objets, ou instances de méta-classes (des classes de classes)
- il dispose d'un protocole à méta-objets (ou MOP pour Meta Object Protocol), lié à l'existence des méta-classes, et permettant de modifier la sémantique et le comportement du système,
- il permet l'héritage multiple entre classes,
- il offre la sélection multiple des méthodes, c’est-à-dire la sélection à l'exécution d'une méthode en fonction du type du tuple de ses paramètres obligatoires (et non pas d'un receveur privilégié comme dans les langages à sélection simple, qui sont la très grande majorité)
- il permet la combinaison de méthodes, c’est-à-dire la définition de méthodes auxiliaires s'exécutant avant et/ou après une méthode particulière. CLOS permet également de définir des méta-classes et des classes, de changer la classe d'un objet, à l'exécution. Le système de conditions de Common Lisp utilise CLOS pour définir les types des conditions pouvant survenir à l'exécution.

Fonctions et fermetures lexicales

Fonctions

En Common Lisp, les fonctions sont un type de donnée. Par exemple, il est possible d'écrire des fonctions qui prennent d'autres fonctions en paramètre, et retournent des fonctions (on les nomme fonctions d'ordre supérieur, ou de première classe). Cela rend possible d'écrire des opérateurs très généraux.
Exemple
Par exemple, la fonction sort (tri) prend une séquence et un opérateur de comparaison en paramètre. Elle peut être utilisée non seulement pour trier n'importe quel type de données, mais également pour trier des structures de données selon une clef. (sort (list 5 2 6 3 1 4) #'>) ;; -> (6 5 4 3 2 1), en utilisant la fonction > comme opérateur de comparaison (sort `((9 a) (3 b) (4 c)) (lambda (x y) (< (fist x) (first y)))) ;; -> ((3 b) (4 c) (9 a)), i.e. la liste triée sur le premier élément On peut appliquer la fonction FOO définie plus haut à une séquence : (mapcar #'foo (list 1 2 3 4 5)) ;; -> (2 3 4 5 6)
Espaces de noms
Common Lisp a un espace de nom respectivement pour les fonctions et pour les variables (à la différence de, par exemple, Scheme, qui est dit "Lisp-1"). Lisp-2 (ou plus) présente l'avantage qu'aucun nom de variable ne peut masquer un nom de fonction : on peut nommer une variable cons ou même if sans problème. Toutefois, pour faire référence à une fonction en tant que variable, on doit utiliser la fonction (function ...) ou la notation équivalente #' comme dans les exemples ci-dessus. Outre les fonctions et les variables, il y a un espace de noms distinct pour les couples d'opérateurs block/return-from et tagbody/go. Ajoutons pour finir que l'espace de nom des fonctions est en réalité partagé entre les fonctions proprement dites et les différentes sortes de macro.
Evaluation
Le modèle d'évaluation est simple : lorsque l'évaluateur rencontre une expression (F A1 A2 ... An), le symbole F peut représenter l'un de ces items : # Un opérateur spécial (comme if), # Une macro, # Une fonction, c’est-à-dire le nom d'une fonction définie par (defun ...) ou une fonction anonyme, toujours dénotée par (lambda ...) Si F est une fonction, les paramètres sont évalués successivement de gauche à droite et la fonction est invoquée avec les valeurs calculées des paramètres. Pour les opérateurs spéciaux ou les macros, cela dépend. Ces opérateurs tendent en effet à contrôler l'évaluation de leurs paramètres. Par exemple, l'opérateur if n'évalue pas tous ses paramètres, il doit évaluer sa condition et puis en fonction du résultat, une branche de l'aternative.

Capture lexicale

Une fermeture lexicale est une fonction dont les variables libres capturent les liaisons de l'environnement lexical dans lequel elles sont définie. Cela permet de construire des fonctions ayant un état interne (en C on utiliserait le mot-clef static pour obtenir l'état interne, mais la capture lexicale n'est pas possible). On peut construire des objets simples à partir de fermetures, par exemple une fabrique de compteurs : (defun fabriquer-compteur () ; fabriquer-compteur renvoie une fonction qui incrémente et affiche sa valeur interne (let ((valeur 0)) ; dans l'environnement de la fabrique, on crée la valeur du compteur (lambda () ; le nouveau compteur lui-même (incf valeur)))) ; ici, la référence à "valeur" capture sa définition dans la fabrique

Autres types

Les autres types de données de Common Lisp comprennent :
- les Pathnames (noms représentant des chemins) qui représentent fichiers et répertoires dans le système de fichier. L'outil de nommage de chemins en Common Lisp est plus général que la plupart des convention de nommage des systèmes d'exploitation, ce qui rend l'accès des programmes Lisp aux fichiers largement portable à travers différents systèmes.
- Les streams (flots) d'entrée et de sortie représentent des sources et des puits de données binaires et textuelles, comme le terminal ou des fichiers ouverts.
- Common Lisp possède son propre générateur de nombres pseudo-aléatoires. Les objets État aléatoire représentent des sources réutilisables de nombres pseudo-aléatoires, permettant à l'utilisateur d'initialiser le générateur ou de le forcer à rejouer une séquence.
- Les Conditions sont un type spécial utilisé pour représenter des erreurs, exceptions et autres évènements « intéressants » auxquels un programme doit pouvoir répondre. Common Lisp a l'un des système de gestion d'exceptions les plus complet ; il permet la reprise après erreur. Common Lisp incorpore également une boîte à outils pour la programmation orientée objet, le Common Lisp Object System, ou CLOS. Il est donc possible d'ajouter une infinité de types.

Macros

Une macro en Lisp ressemble superficiellement à une fonction. Toutefois, plutôt que de représenter une fonction qui est évaluée, elle représente une transformation du texte du programme contenu dans l'appel à la macro. Les macros permettent au programmeur Lisp de créer de nouvelles formes syntaxiques dans le langage. Par exemple, cette macro fournit la forme de boucle until (boucler... jusqu'à), qui est familière dans un langage comme Perl : (defmacro until (test &rest body) `(do () (,test) ,@body)) Exemple : (until (= (random 10) 0) (write-line "Hello")) Les macros ne sont pas évaluées à l'exécution comme les fonctions. On parle de macro-expansion, qui a lieu au moment de la compilation du code source. On peut toutefois les considérer comme des fonctions qui acceptent et retournent des arbres de syntaxe abstraits (les s-expressions), mais contrôlent l'évaluation de leurs paramètres. Comme les fonctions, elles peuvent utiliser l'ensemble du langage Common Lisp (et bibliothèques tierces) pour effectuer leur travail de transformation, contrairement aux macros du langage C qui ne permettent que des substitutions de chaînes de caractères au niveau du source, sans accès à l'ensemble du langage C lui-même.

Capture de variable

Les macros Common Lisp sont capables de capture de variable, une situation où des symboles situés dans le corps de la macro-expansion coincident avec des symboles du contexte apelant. Pour cette raison elles sont parfois apelées macro « non hygiéniques », par comparaison avec le système de « macro hygiéniques » de Scheme, qui garantit la séparation entre ces ensembles de symboles. La capture de variable est parfois un effet désiré ; lorsque ce n'est pas le cas, elle doit être évité par l'emploi de gensyms, ou symboles dont l'unicité est garantie.

Implémentations

Common Lisp est défini par une spécification (comme Ada et C) plutôt que par une seule implémentation (comme Perl ou Python). Il y a de nombreuses implémentations, et le standard décrit les points sur lesquels elles peuvent être divergentes pour de bonnes raisons. De plus, les implémentations sont généralement fournies avec différents ensembles de « paquets » de bibliothèques, qui fournissent des fonctionnalités non couvertes par le standard. Certaines de ces fonctionnalités ont été introduites par la suite dans le standard, comme CLOS et la forme LOOP ; d'autres restent propres à ces implémentations. De nombreuses commodités pour le programmeur moderne -- comme l'accès aux réseaux TCP/IP -- restent hors du standard, mais sont fournies par les différentes implémentations avec parfois des différences mineures. Un processus nommé CLRFI (Common Lisp Request For Improvement), similaire aux SRFI de Scheme, a pour objectif de faire converger les fonctionalités utiles laissées hors du standard ANSI de 1995. Selon une erreur répandue, les implémentations de Common Lisp sont toutes des interpréteurs. En fait, la compilation fait partie de la spécification du langage. La plupart des implémentations de Common Lisp compilent les fonctions vers du code machine. D'autres compilent vers du code objet, ce qui réduit la vitesse mais améliore la portabilité. Certaines implémentations en environnement UNIX, comme CLISP, peuvent être utilisées comme des interpréteurs de scripts (ou même comme shell).

Liste des implémentations

Les implémentations librement redistribuables incluent :
- [http://www.cons.org/cmucl/ CMU Common Lisp] (CMUCL), de Carnegie Mellon University. CMUCL est peut-être le CL le plus largement utilisé parmi les logiciels libres. Il est disponible sur Linux et BSD pour Intel x86; Linux pour Alpha; et Solaris, IRIX, et HP-UX sur leurs plate-formes natives.
- [http://clisp.sourceforge.net/ GNU CLISP], une implémentation compilant du code-objet. Il est portable et tourne sur de nombreux UNIX et systèmes de type UNIX, de même que sur Microsoft Windows et plusieurs autres systèmes.
- [http://sbcl.sourceforge.net/ Steel Bank Common Lisp] (SBCL), une branche de CMUCL. « En gros, SBCL se distingue de CMU CL par une plus grande insistance sur la maintenabilité.. ». Sur Linux 2.6, SBCL supporte les threads natifs. [http://sbcl.sourceforge.net/cmucl-relationship.php] SBCL tourne sur les mêmes plate-formes que CMUCL, moins HP/UX, et plus Linux pour PowerPC, SPARC, et MIPS.
- [http://www.gnu.org/software/gcl/gcl.html GNU Common Lisp] (GCL), le compilateur Lisp du projet GNU. Pas encore complètement conforme au standard ANSI, GCL est cependant l'implémentation de choix pour plusieurs gros projets, incluant les outils mathématiques Maxima et ACL2. GCL tourne sur GNU/Linux sur onze différentes architectures, et également sous Windows, Solaris, et FreeBSD.
- [http://ecls.sourceforge.net/ Embeddable Common Lisp] (ECLS), conçu pour être embarqué dans des applications écrites en C.
- [http://openmcl.clozure.com/ OpenMCL], une branche Open Source de Macintosh Common Lisp. Comme son nom l'indique, OpenMCL est natif sur le Macintosh ; il s'exécute sur Mac OS X, Darwin, et Linux pour PowerPC.
- [http://armedbear-j.sourceforge.net/ Armed Bear Common Lisp], est une implémentation, en développement, d'un compilateur CommonLisp → machine virtuelle Java. ABCL est fourni avec un éditeur à tout faire, J, écrit en Common Lisp. Il y a également des implémentations commerciales disponibles chez Franz, Xanalys, Digitool, Corman and Scieneer.

Voir aussi

Applications

Common Lisp est utilisé avec succès dans de nombreuses applications commerciales :
- [http://www.orbitz.com Orbitz], un site majeur de réservation de voyages,
- [http://www.izware.com/mirai/index.htm Mirai], une suite intégrée de création de graphismes 2D/3D offrant ce qui est presque universellement considéré comme le meilleur modeleur polygonal de l'industrie, un système d'animation IK/FK et non-linéaire (popularisé par des produits tels que l'Animanium de Sega et Softimage SXI), et de la coloration avancé pour la 2D et la 3D. Il est utilisé dans divers films (le plus fameux étant Le Seigneur des Anneaux de New Line Cinema), des jeux vidéos et des simulations militaires.
- [http://www.piano.aero/ Piano], un packetage pour la conception préliminaire d'avions commerciaux et l'évaluation de la compétition.
- [http://www.xanalys.com/ Xanalys Corp.] offre une ligne de logiciel d'investigation, utilisé par les services de police, de sécurité et de prévention de la fraude au niveau mondial.
- [http://www.ktiworld.com/ Knowledge Technologies International] le logiciel de conception mécanique ICAD.
- [http://www.genworks.com/ Genworks International] le Langage Déclaratif à usage Général (GDL), un outil de développement pour la création d'ingénierie Web, de conception et d'applications d'affaires. Il existe également des applications open-source écrites en Common Lisp, telles que :
- [http://www.cs.utexas.edu/users/moore/acl2/ Applicative Common Lisp], un démonstrateur de théorèmes complet pour un sous-ensemble de Common Lisp.
- [http://maxima.sourceforge.net/ Maxima], un système de calcul algébrique formel sophistiqué.
- [http://compo.sourceforge.net Compo], un langage permettant la description naturelle de structures musicales complexes.
- [http://lisa.sourceforge.net Lisa], un système à base de règles pour construire des agents logiciels "intelligents". Common Lisp est également utilisé par de nombreux gouvernements et institutions de type Loi 1901. Des exemples d'utilisation par la NASA incluent :
- [http://www.stsci.edu/resources/software_hardware/spike/ SPIKE], le système de planification et d'ordonnancement du téléscope spatial Hubble.
- [http://ic.arc.nasa.gov/projects/remote-agent/ Remote Agent], gagnant du prix logiciel de la NASA pour l'année 1999.

Liens externes


- L'aggrégateur [http://planet.lisp.org/ Planet Lisp]
- La [http://www.lisp.org/HyperSpec/FrontMatter/index.html Common Lisp HyperSpec], une version hypertextuelle du standard.
- Le [http://ww.telent.net/cliki/index CLiki], un Wiki Common Lisp.
- [http://www.lisp.org/ The Association of Lisp Users].
- [http://cl-cookbook.sourceforge.net/ The Common Lisp Cookbook], une collection de méthodes de programmation utiles.
- Le site de [http://www.paulgraham.com Paul Graham], un développeur et avocat de l'utilisation de Common Lisp ; son site contient de nombreux essais sur Lisp et la programmation en général.

Bibliographie

en ligne


- [http://www.psg.com/~dlamkins/sl/contents.html Successful Lisp], un livre sur la programmation en Common Lisp.
- [http://dept-info.labri.u-bordeaux.fr/~strandh/Teaching/Programmation-Symbolique/Common/Book/HTML/programmation.html Traité de Programmation en Common Lisp], un autre livre, en français, sur la programmation en Common Lisp.
- [http://www.paulgraham.com/onlisptext.html On Lisp], un livre de [http://www.paulgraham.com Paul Graham] sur des techniques avancées (en particulier l'art des macros).
- [http://www-2.cs.cmu.edu/~dst/LispBook Common Lisp: A Gentle Introduction to Symbolic Computation] Un livre très facile d'accès qui présente progressivement les concepts du langage Lisp.
- [http://www.gigamonkeys.com/book/ Practical Common Lisp], un livre publié en 2005 sur la constructions d'applications en Common Lisp. Excellente introduction à de nombreuses fonctionalités avancées du langage, d'un point de vue agréablement concret.

en papier


- ANSI Common Lisp, de Paul Graham chez Prentice Hall (1995)
- Common Lisp, de Wade Hennessey chez Mcgraw-Hill College (1989)
- Common Lisp: A Gentle Introduction to Symbolic Computation, de David Touretzky chez Benjamin-Cummings Pub Co (1989)
- Common LISP : The Language, de Guy L. Steele, Jr. chez Digital Press (1991)
- Object-Oriented Common LISP, de Stephen Slade chez Prentice Hall (1997)
- Object-Oriented Programming : The CLOS Perspective, de Adreas Paepcke chez MIT Press (1994)
- Object-Oriented Programming in Common Lisp: A Programmer's Guide to CLOS, de Sonya Keene chez Addison-Wesley (1989)
- Paradigms of Artificial Intelligence Programming : Case Studies in Common Lisp, de Peter Norvig chez Morgan Kaufmann (1991)
- Practical Common Lisp, de Peter Seibel chez Apress (2005)
- Successful Lisp: How to Understand and Use Common Lisp, de David Lamkins chez BookFix (2004)
- The ANSI Common Lisp Reference Book, de David Margolies chez Apress (2005)
- The Art of the Metaobject Protocol, de Gregor Kiczales chez MIT Press (1991) Catégorie:Langage de programmation Catégorie:Langage fonctionnel Catégorie:Langage impératif

Lisp

Catégorie:Langage fonctionnel Catégorie:Intelligence artificielle

Introduction

Lisp est la plus ancienne famille de langages impératifs et fonctionnels. Développé initialement en tant que modèle pratique pour représenter des programmes (par contraste avec la notion théorique de Machine de Turing), il est devenu dans les années 70 et 80 le langage de choix pour la recherche en Intelligence Artificielle. Les langages Lisp sont aujourd'hui utilisés dans de nombreux domaines, de la programmation Web à la finance, et dans les cursus de formation en Informatique. Le terme Lisp a été forgé à partir de l'anglais « list processing ». Tous les dialectes de Lisp partagent les mêmes opérateurs de manipulation de listes chaînées simples. Lisp se distingue en outre par une syntaxe simple en notation préfixée, son typage dynamique des données, le support pour la programmation fonctionnelle, sa gestion automatique de la mémoire et la faculté de manipuler le code source en tant que structure de données. Les langages Lisp sont reconnaissables immédiatement à leur apparence. Le code source des programmes est écrit en utilisant la même syntaxe que celle des listes - la syntaxe parenthésée des s-expressions. Chaque sous-expression d'un programme (ou structure de données) est délimitée par des parenthèses. Cela simplifie grandement l'analyse syntaxique des programmes Lisp et rend simple la Méta-programmation -- la création de programmes qui créent d'autres programmes. Si l'on excepte le langage machine et le langage d'assemblage (ou plus communément « assembleur »), Lisp est le deuxième langage le plus ancien (juste après Fortran) parmi les langages qui se sont largement diffusés. Lisp a beaucoup évolué depuis le début des années 1960 et a ainsi donné naissance à de nombreux dialectes.

Histoire

Le langage Lisp fut inventé par John McCarthy en 1958 alors qu'il était au Massachusetts Institute of Technology. Il publia un article intitulé « Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I » (soit « Fonctions Récursives d'expressions symboliques et leur évaluation par une Machine, partie I ») dans la revue CACM en 1960 ; la partie II ne fut jamais publiée. Le premier interpréteur fonctionnait sur un ordinateur IBM 704 et deux instructions de cette machine devinrent les deux opérations primitives de Lisp pour décomposer les listes :
- car (Contents of Address register) : le premier élément de la liste
- cdr (Contents of Decrement register) : le reste de la liste Dans son article, John McCarthy introduit deux syntaxes: les S-expressions (expressions symboliques, parfois appelées « sexp ») et les M-expressions (meta expressions, pour exprimer les fonctions manipulant des S-expressions). Les M-expressions n'ont jamais été très appréciées et la plupart des Lisps de nos jours utilisent des S-expressions pour les programmes comme pour les données. C'est la syntaxe des S-expressions qui fait que certains reprochent à Lisp d'être « plein de parenthèses », mais c'est aussi une des sources de la puissance et de la souplesse du langage. Probablement en raison de son expressivité et de sa flexibilité, Lisp eut beaucoup de succès dans la communauté de l'intelligence artificielle. Dans les années 1970, on créa des ordinateurs spécialisés dans l'exécution de programmes Lisp : les machines Lisp. Durant les années 1980 et 1990, on fit de grands efforts pour unifier les nombreux dialectes de Lisp qui étaient apparus. Le résultat fut appelé Common Lisp et en 1994, l'ANSI publia « ANSI X3.226-1994 Information Technology Programming Language Common Lisp », standardisant ainsi le langage. À ce moment, Lisp était bien moins florissant qu'à sa grande époque.

Syntaxe

Les listes sont délimitées par des parenthèses et leurs éléments sont séparés par des espaces : (1 2 « foo »). Un programme Lisp est un arbre de syntaxe composé avec des listes. Cette utilisation des parenthèses donne lieu à une moquerie sur le nom de LISP : « Lots of Irritating and Silly Parentheses ». Lisp est un langage orienté expression : il ne fait pas de distinction entre « expressions » et « statements » (instructions) comme le font de nombreux langage (par exemple Pascal) ; tout est expression et retourne une valeur ou un ensemble de valeurs. La plupart des expressions Lisp sont des applications de fonction. Ce que d'autres langages écrivent
 f(a,b,c) 
Lisp l'écrit
 (f a b c) 
Ainsi une somme ne se note pas
 1+2+3+4 
ni
somme(1,2,3,4)
mais
(+ 1 2 3 4)
On utilise la même notation préfixée pour les « formes spéciales » et les « macros » : le premier élément dans la liste, dans ces cas, détermine comment les éléments suivants seront traités. Une expression peut être une application de fonction, une forme spéciale ou une application de macro suivant la nature du premier élément.

Exemples

Les programmes suivants ne sont pas typiques des vrais programmes Lisp. Ils sont typiques de la présentation que l'on fait de Lisp dans les cours d'informatique. La factorielle est un grand classique :
(defun factorial (n)
  « Calcule la factorielle de l'entier n. »
  (if (<= n 1)
    1
    (
- n (factorial (- n 1)))))
On peut aussi écrire plus efficacement (voir récursion terminale) :
(defun factorial (n &optional (acc 1))
  « Compute the factorial of the integer n. »
  (if (<= n 1)
    acc
    (factorial (- n 1) (
- acc n))))
Un autre exemple typique est cette fonction qui renverse une liste (notez bien que Lisp a une fonction intégrée reverse à cet effet) :
(defun reverse (l &optional (acc '()))
  « renverse la liste l »
  (if (null l)
    acc
    (reverse (cdr l) (cons (car l) acc))))

Lisp et les Objets

Divers systèmes à objets ont été construits à partir de Lisp, notamment :
- Flavors, conçu au MIT
- Le Common Lisp Object System (CLOS), un descendant de Flavors CLOS offre de l'héritage multiple, la sélection multiple et un puissant système de combinaison de méthodes. Common Lisp (dont CLOS fait partie) fut le premier langage orienté-objet standardisé.

Citation

Grégory Chaitin sur Lisp : «Malheureusement, alors que les langages de programmation gagnent en sophistication, ils sont de plus en plus le reflet de la complexité de la société humaine et du monde immense des applications logicielles. Ainsi, ils deviennent d'énormes boîtes à outils, comme des garages et des greniers chargés de plus de trente ans d'histoire ! A contrario, LISP est un langage de programmation d'une grande beauté mathématique ; il ressemble plus à un scalpel de chirurgien ou à diamant affuté qu'à un garage à deux places encombrés de bricolages, où il ne reste plus de place pour une voiture. LISP a un petit nombre de concepts élémentaires puissants, et tout le reste est construit au-dessus de ça, ce qui correspond à la façon de travailler des mathématiciens ; c'est à ça que ressemblent les théories mathématiques. Ces théories, les bonnes théories, consistent à définir quelques nouveaux concepts clefs, et à partir de là le feu d'artifice commence : elles révèlent de nouvelles allées, elles ouvrent la porte à des mondes radicalement nouveaux. LISP est comme ça aussi ; il est plus proches des maths que la plupart des langages de programmation. Du moins si vous éliminez les parties utiles qui ont été ajoutées, les ajouts qui ont fait de LISP un outil pratique. Ce qui reste si vous faites cela, c'est le LISP original, le cœur conceptuel de LISP, un cœur qui est un joyau de beauté mathématique et de beauté intellectuelle austère.» Aujourd'hui, certains diraient que Scheme est le dérivé de Lisp atteignant à la beauté décrite par Chaitin ; et il est certain que Common Lisp, le descendant en ligne droite des grandes cuvées des dialectes passés de LISP (Maclisp, Interlisp, Zetalisp) penche plus du côté de la boîte à outils géante, bien qu'ayant conservé intact son cœur conceptuel. G. Chaitin a utilisé ce Lisp idéalisé pour ses recherches : [http://www.cs.auckland.ac.nz/CDMTCS/chaitin/lisp.html Elegant LISP Programs].

Généalogie et Variantes


- LISP (la version originale de John McCarthy lorsqu'il était au MIT)
- MACLisp (lié au projet MACSYMA du MIT - et sans lien avec l'Apple Macintosh), descendant direct de LISP
- ZetaLisp successeur de MACLisp, il fonctionna sur des machines LISP, descendant direct de MACLisp
- InterLisp, né BBN Lisp, qui servit à développer les premières interfaces utilisateurs graphiques,
- LeLisp, version française du célèbre langage, développée à l'INRIA, et commercialisé ensuite par Ilog
- EuLisp, un « Lisp Européen »
- ELISP (Emacs Lisp), version simplifiée utilisée pour programmer l'éditeur de texte Emacs
- AutoLISP, un LISP utilisé dans AutoCAD pour la programmation.
- Scheme, un LISP simplifié
- Nyquist, un LISP utilisé pour travailler avec des sons.

Liens externes


- [http://www.algo.be/clr.html Lisp : présentation et ressources], en français
- [http://www.cadxp.com/forumXForum-100.htm AutoLisp : forum de discussion], en français ja:LISP ko:리스프

Lisp

Catégorie:Langage fonctionnel Catégorie:Intelligence artificielle

Introduction

Lisp est la plus ancienne famille de langages impératifs et fonctionnels. Développé initialement en tant que modèle pratique pour représenter des programmes (par contraste avec la notion théorique de Machine de Turing), il est devenu dans les années 70 et 80 le langage de choix pour la recherche en Intelligence Artificielle. Les langages Lisp sont aujourd'hui utilisés dans de nombreux domaines, de la programmation Web à la finance, et dans les cursus de formation en Informatique. Le terme Lisp a été forgé à partir de l'anglais « list processing ». Tous les dialectes de Lisp partagent les mêmes opérateurs de manipulation de listes chaînées simples. Lisp se distingue en outre par une syntaxe simple en notation préfixée, son typage dynamique des données, le support pour la programmation fonctionnelle, sa gestion automatique de la mémoire et la faculté de manipuler le code source en tant que structure de données. Les langages Lisp sont reconnaissables immédiatement à leur apparence. Le code source des programmes est écrit en utilisant la même syntaxe que celle des listes - la syntaxe parenthésée des s-expressions. Chaque sous-expression d'un programme (ou structure de données) est délimitée par des parenthèses. Cela simplifie grandement l'analyse syntaxique des programmes Lisp et rend simple la Méta-programmation -- la création de programmes qui créent d'autres programmes. Si l'on excepte le langage machine et le langage d'assemblage (ou plus communément « assembleur »), Lisp est le deuxième langage le plus ancien (juste après Fortran) parmi les langages qui se sont largement diffusés. Lisp a beaucoup évolué depuis le début des années 1960 et a ainsi donné naissance à de nombreux dialectes.

Histoire

Le langage Lisp fut inventé par John McCarthy en 1958 alors qu'il était au Massachusetts Institute of Technology. Il publia un article intitulé « Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I » (soit « Fonctions Récursives d'expressions symboliques et leur évaluation par une Machine, partie I ») dans la revue CACM en 1960 ; la partie II ne fut jamais publiée. Le premier interpréteur fonctionnait sur un ordinateur IBM 704 et deux instructions de cette machine devinrent les deux opérations primitives de Lisp pour décomposer les listes :
- car (Contents of Address register) : le premier élément de la liste
- cdr (Contents of Decrement register) : le reste de la liste Dans son article, John McCarthy introduit deux syntaxes: les S-expressions (expressions symboliques, parfois appelées « sexp ») et les M-expressions (meta expressions, pour exprimer les fonctions manipulant des S-expressions). Les M-expressions n'ont jamais été très appréciées et la plupart des Lisps de nos jours utilisent des S-expressions pour les programmes comme pour les données. C'est la syntaxe des S-expressions qui fait que certains reprochent à Lisp d'être « plein de parenthèses », mais c'est aussi une des sources de la puissance et de la souplesse du langage. Probablement en raison de son expressivité et de sa flexibilité, Lisp eut beaucoup de succès dans la communauté de l'intelligence artificielle. Dans les années 1970, on créa des ordinateurs spécialisés dans l'exécution de programmes Lisp : les machines Lisp. Durant les années 1980 et 1990, on fit de grands efforts pour unifier les nombreux dialectes de Lisp qui étaient apparus. Le résultat fut appelé Common Lisp et en 1994, l'ANSI publia « ANSI X3.226-1994 Information Technology Programming Language Common Lisp », standardisant ainsi le langage. À ce moment, Lisp était bien moins florissant qu'à sa grande époque.

Syntaxe

Les listes sont délimitées par des parenthèses et leurs éléments sont séparés par des espaces : (1 2 « foo »). Un programme Lisp est un arbre de syntaxe composé avec des listes. Cette utilisation des parenthèses donne lieu à une moquerie sur le nom de LISP : « Lots of Irritating and Silly Parentheses ». Lisp est un langage orienté expression : il ne fait pas de distinction entre « expressions » et « statements » (instructions) comme le font de nombreux langage (par exemple Pascal) ; tout est expression et retourne une valeur ou un ensemble de valeurs. La plupart des expressions Lisp sont des applications de fonction. Ce que d'autres langages écrivent
 f(a,b,c) 
Lisp l'écrit
 (f a b c) 
Ainsi une somme ne se note pas
 1+2+3+4 
ni
somme(1,2,3,4)
mais
(+ 1 2 3 4)
On utilise la même notation préfixée pour les « formes spéciales » et les « macros » : le premier élément dans la liste, dans ces cas, détermine comment les éléments suivants seront traités. Une expression peut être une application de fonction, une forme spéciale ou une application de macro suivant la nature du premier élément.

Exemples

Les programmes suivants ne sont pas typiques des vrais programmes Lisp. Ils sont typiques de la présentation que l'on fait de Lisp dans les cours d'informatique. La factorielle est un grand classique :
(defun factorial (n)
  « Calcule la factorielle de l'entier n. »
  (if (<= n 1)
    1
    (
- n (factorial (- n 1)))))
On peut aussi écrire plus efficacement (voir récursion terminale) :
(defun factorial (n &optional (acc 1))
  « Compute the factorial of the integer n. »
  (if (<= n 1)
    acc
    (factorial (- n 1) (
- acc n))))
Un autre exemple typique est cette fonction qui renverse une liste (notez bien que Lisp a une fonction intégrée reverse à cet effet) :
(defun reverse (l &optional (acc '()))
  « renverse la liste l »
  (if (null l)
    acc
    (reverse (cdr l) (cons (car l) acc))))

Lisp et les Objets

Divers systèmes à objets ont été construits à partir de Lisp, notamment :
- Flavors, conçu au MIT
- Le Common Lisp Object System (CLOS), un descendant de Flavors CLOS offre de l'héritage multiple, la sélection multiple et un puissant système de combinaison de méthodes. Common Lisp (dont CLOS fait partie) fut le premier langage orienté-objet standardisé.

Citation

Grégory Chaitin sur Lisp : «Malheureusement, alors que les langages de programmation gagnent en sophistication, ils sont de plus en plus le reflet de la complexité de la société humaine et du monde immense des applications logicielles. Ainsi, ils deviennent d'énormes boîtes à outils, comme des garages et des greniers chargés de plus de trente ans d'histoire ! A contrario, LISP est un langage de programmation d'une grande beauté mathématique ; il ressemble plus à un scalpel de chirurgien ou à diamant affuté qu'à un garage à deux places encombrés de bricolages, où il ne reste plus de place pour une voiture. LISP a un petit nombre de concepts élémentaires puissants, et tout le reste est construit au-dessus de ça, ce qui correspond à la façon de travailler des mathématiciens ; c'est à ça que ressemblent les théories mathématiques. Ces théories, les bonnes théories, consistent à définir quelques nouveaux concepts clefs, et à partir de là le feu d'artifice commence : elles révèlent de nouvelles allées, elles ouvrent la porte à des mondes radicalement nouveaux. LISP est comme ça aussi ; il est plus proches des maths que la plupart des langages de programmation. Du moins si vous éliminez les parties utiles qui ont été ajoutées, les ajouts qui ont fait de LISP un outil pratique. Ce qui reste si vous faites cela, c'est le LISP original, le cœur conceptuel de LISP, un cœur qui est un joyau de beauté mathématique et de beauté intellectuelle austère.» Aujourd'hui, certains diraient que Scheme est le dérivé de Lisp atteignant à la beauté décrite par Chaitin ; et il est certain que Common Lisp, le descendant en ligne droite des grandes cuvées des dialectes passés de LISP (Maclisp, Interlisp, Zetalisp) penche plus du côté de la boîte à outils géante, bien qu'ayant conservé intact son cœur conceptuel. G. Chaitin a utilisé ce Lisp idéalisé pour ses recherches : [http://www.cs.auckland.ac.nz/CDMTCS/chaitin/lisp.html Elegant LISP Programs].

Généalogie et Variantes


- LISP (la version originale de John McCarthy lorsqu'il était au MIT)
- MACLisp (lié au projet MACSYMA du MIT - et sans lien avec l'Apple Macintosh), descendant direct de LISP
- ZetaLisp successeur de MACLisp, il fonctionna sur des machines LISP, descendant direct de MACLisp
- InterLisp, né BBN Lisp, qui servit à développer les premières interfaces utilisateurs graphiques,
- LeLisp, version française du célèbre langage, développée à l'INRIA, et commercialisé ensuite par Ilog
- EuLisp, un « Lisp Européen »
- ELISP (Emacs Lisp), version simplifiée utilisée pour programmer l'éditeur de texte Emacs
- AutoLISP, un LISP utilisé dans AutoCAD pour la programmation.
- Scheme, un LISP simplifié
- Nyquist, un LISP utilisé pour travailler avec des sons.

Liens externes


- [http://www.algo.be/clr.html Lisp : présentation et ressources], en français
- [http://www.cadxp.com/forumXForum-100.htm AutoLisp : forum de discussion], en français ja:LISP ko:리스프

Portée lexicale

ja:静的スコープ Catégorie:Programmation informatique En informatique, la portée lexicale (dite également portée statique), est une méthode pour déterminer la portée d'une variable en fonction de sa position dans le code. Une variable est dite lexicale si sa portée est définie par le texte du programme. Par exemple, une variable nommée balance : (defun solde (balance) ... (lambda (foo bar) (+ (
- foo balance) bar)) ...) Cette variable est définie dans la portée de la fonction solde, à partir de la liste des paramètres, et jusqu'à la fin de la définition de la fonction. La fermeture lexicale (dénotée par l'expression lambda) utilise la définition lexicale de balance de la fonction solde : c'est une capture de variable lexicale ; la variable ainsi capturée a la durée de vie de la fonction qui la capture, qui peut excéder la durée d'un appel à solde ; mais la portée de cette capture est strictement limitée par le texte de la fermeture. Elle est garantie ne pas interférer avec une autre variable de même nom qui serait définie dans un fragment de code du programme hors de la fonction solde, ou encore avec la variable nommée balance dans d'autres appels de la même fonction. Cela assure au programmeur que les variables privées ne sont pas accidentellement accédées ou modifiées par d'autres fonctions. Cela est considéré comme une amélioration considérable par rapport à la combinaison de portée globale et portée dynamique plus anciennes. La portée lexicale fut d'abord introduite par le langage Algol, puis utilisée par d'autres langages depuis. Certains langages ont introduit la portée lexicale dans leurs incarnations les plus modernes, comme Perl. D'autres offrent les variables dynamiques et lexicales, par choix (Common Lisp) ou par le fait d'une évolution progressive (Emacs Lisp, avec lexical-let). Voir également : fermeture, fermeture lexicale, portée dynamique, environnement lexical, environnement dynamique

Langage de programmation

ko:프로그래밍 ja:プログラミング La programmation dans le domaine informatique est l'ensemble des activités qui permettent l'écriture des programmes informatiques. C'est une étape importante de la conception de logiciel (voire de matériel, cf. VHDL).

Pratiques


- Algorithmique
- Codage
- Contrôle de version
- Optimisation du code
- Programmation système
- Refactoring
- Test unitaisre

Techniques de programmation


- Programmation impérative
  - Programmation orientée objet
  - Programmation par contrat
- Programmation déclarative
  - Programmation fonctionnelle
  - Programmation logique
  - Programmation par contraintes
- Programmation orientée composant
- Programmation orientée aspect
- Programmation concurrente

Langages de programmation

Les langages de programmation permettent de définir les ensembles d'instructions effectuées par l'ordinateur lors de l'exécution d'un programme. Il existe des milliers de langages de programmation, la plupart d'entre eux étant réservés à des domaines spécialisés. Ils font l'objet de recherches constantes dans les universités et dans l'industrie. Les langages de programmation peuvent être classifiés de nombreuses manières : généraliste/spécialisé, haut niveau/bas niveau, interprété/compilé, avec ou sans gestion de mémoire automatisée, système de gestion d'exceptions, typage fort/typage faible, typage statique/typage dynamique, syntaxe fixe/extensible ; non objet/orienté objet/purement objet, impératif/fonctionnel/déclaratif, fonctionnel pur/impur, etc. Nous incluons ci-dessous une classification sommaire des langages de programmation les plus connus. Il faut garder à l'esprit que de nombreux langages appartiennent simultanément à plusieurs catégories - ils sont dits « multi-paradigmes ». Par exemple, C++ permet la programmation impérative, orientée objet et la programmation générique (à base de classes et de fonctions paramétrées nommées templates). Common Lisp est à la fois impératif, fonctionnel, orienté objet -- et de par son caractère « programmable » (un langage de programmation programmable...), il peut intégrer d'autres « paradigmes » de programmation en son sein (par exemple la programmation logique, ou par contraintes). Ci-dessous, nous listons les langages les plus connus (nous mettons entre parenthèses certains langages dérivés ou les extensions requises).

Langages déclaratifs


- Oz
- Mercury
- Prolog pour PROgrammation LOGique
- Clips Ci-dessous, nous listons les langages spécialisés, c'est-à-dire dont l'utilisation est réservée à des domaines bien spécifiques ; les plus connus sont :

Langages de définition de données


- ASN.1
- DTD SGML
- DTD XML
- XML Schéma
- Relax NG

Langages spécialisés pour la communication avec une base de données


- 4GL

Langages de manipulation de chaînes de caractères


- SNOBOL StriNg Oriented symBOlic Language (Langage Symbolique Orienté Chaînes de Caractères)
- awk
- Perl
- sed

Langages spécialisés Web


- Exécution par le serveur HTTP (côté serveur) :
  - ASP
  - JSP (issu de Java, basé sur des Servlets)
  - PHP
  - XSP (issu de XML, soutenu par Apache)
  - D'une manière générale, les langages non spécialisés (notamment Perl et C) peuvent également être utilisés via Common Gateway Interface
- Exécution par le navigateur Web (côté client) :
  - JavaScript ou ECMAScript
  - VBScript
  - applets écrites en Java
  - ActionScript de Macromedia Flash

Langages de description de page

voir Langage de balisage

Langages de programmation théorique


- Lambda-calcul
- Pi-calcul
- Join-Calcul
- Récursion Primitive
- Système T de Kurt Gödel
- BNF

Langages de programmation de Commande Numérique (C.N.)

Une machine-outil automatisée, ou Commande Numérique (C.N.), a besoin d'un langage de programmation pour réaliser les opérations de tournage, ou de fraisage
- Programmation de Commande Numérique

Pour rendre la programmation plus difficile


- Brainfuck (ou encore F
- ckF
- ck
, Ook ou spoon)
- Intercal
- Malbolge
- Unlambda

Non classés


- Nosica
- SAS
- Langage K
- GOTO++

Langages spécialisés


- ABEL : langage pour la programmation électronique des PLD
- R : langage pour l'outil de statistiques du même nom
- VHDL : langage de description matérielle, permettant de synthétiser de l'électronique numérique (descriptions de portes logiques)
- VRML : description de scènes en trois dimensions

Bibliothèques graphiques


- Allegro - multi-plateforme, Multimédia, Jeux
- DirectX - 3D, Multimédia
- GTK+ - multi-plateforme, Environnement graphique
- JFC - Environnement graphique, 2D
- OpenGL - 3D
- Qt - multi-plateforme, Interface utilisateur
- Quartz - Environnement graphique
- SDL - Video
- SWT - multi-plateforme, Interface utilisateur
- Tk - multi-plateforme - Interface graphique associée à Tcl
- wxWidgets - multi-plateforme - Environnement graphique
- Xlib - 2D

Voir aussi

Liens internes


- Chronologie des langages de programmation
- [http://fr.wikibooks.org/wiki/Programmation Wikilivre sur la programmation]
- ABAP
- RIP

Liens externes


- [http://www.codes-sources.com/ CodeS-SourceS ] : site de passionnés qui partagent leurs connaissances
- [http://www.developpez.com/ Developpez.com, le club des développeurs] (de nombreux forums, cours et tutoriels de programmation)
- [http://www.levenez.com/lang/ Computer Languages History]
- [http://www.techbooksforfree.com/perlpython.shtml Free Python Books]
- [http://www.a525g.com/programmation/index-fr.htm A525G - Programmation]
- [http://www.99-bottles-of-beer.net/ 99 Bouteilles de Bière - Un même programme en plus de 780 langages]
- [http://coding.romainl.com Programmation Network Security]
- [http://rmdiscala.developpez.com/cours/ Package pédagogique multimédia V4.1] Catégorie:Programmation informatique

Programmation fonctionnelle

Un langage fonctionnel est un langage de programmation dont la syntaxe et les caractéristiques encouragent la programmation fonctionnelle. Le langage fonctionnel le plus ancien est le Lisp, créé en 1958 par Mac Carthy. Lisp a donné naissance à des variantes plus récentes telles que le Scheme (1975) et le Common Lisp (1984). D'autres langages fonctionnels récents incluent les langages ML (1973), Haskell (1987), Erlang, Clean et Oz. Dans la catégorie des langages non ML, il ne faut pas oublier XSLT.

Machine d'états et effets de bord

Programmation Impérative

En programmation impérative, on travaille sur le modèle de la machine de Turing, avec une mémoire centrale et des instructions qui modifient son état grâce à des assignations successives. On peut représenter un programme par une machine d'états qui représente les états successifs de la mémoire. Cela nécessite pour le programmeur de connaître à tout instant un modèle exact de l'état de la mémoire que le programme modifie. Afin de réduire la difficulté que représente cette tâche, de nombreuses techniques destinées à réduire le nombre de variables à gérer sont utilisées. On peut citer parmi ces techniques les variables dites automatiques, dont la portée (portée lexicale) se limite à la procédure dans laquelle elles ont été définies et qui sont désallouées par le compilateur à la sortie de la procédure, l'encapsulation des données, à l'origine de la programmation structurée, et la programmation orientée objet. Cependant, en programmation impérative, il est fréquent qu'une variable ou une zone de mémoire ait une portée ou une durée de vie supérieure à celle de la procédure dans laquelle elle a été créée, de façon à pouvoir être lue ou modifiée par d'autres procédures ou unités d'exécution. Il arrive cependant que des procédures doivent mettre à jour certaines variables ou zones de mémoire dans un but qui n'est pas directement lié à leur fonction, mais uniquement afin que les données partagées restent dans un état prévu par le programmeur. On regroupe ces modifications « à distance » sous le terme générique d'effets de bord. Les effets de bord, en programmation impérative, qui sont plus la règle que l'exception, compliquent grandement la compréhension des programmes et sont la source de nombreuses difficultés et de bogues : en effet, si on oublie de mettre à jour certaines données partagées, si l'ordre chronologique des assignations par les différentes parties du logiciel est incorrect, ou si une zone de mémoire a été désallouée au mauvais moment, le programme se retrouve dans un état imprévu.

Programmation Fonctionnelle

La programmation fonctionnelle s'affranchit de façon radicale des effets de bord en interdisant toute opération d'assignation. Le paradigme fonctionnel n'utilise pas de machine d'états pour décrire un programme, mais un emboîtement de fonctions que l'on peut voir comme des « boîtes noires » que l'on peut imbriquer les unes dans les autres. Chaque boîte possédant plusieurs paramètres en entrée mais une seule sortie, elle ne peut sortir qu'une seule valeur possible pour chaque tuple de valeurs présentées en entrée. Ainsi, les fonctions n'introduisent pas d'effets de bord. Un programme est donc une application, au sens mathématique, qui ne donne qu'un seul résultat pour chaque ensemble de valeurs en entrée. Cette façon de penser, qui est très différente de la pensée habituelle en programmation impérative est l'une des causes principales de la difficulté qu'ont les programmeurs formés aux langages impératifs pour aborder la programmation fonctionnelle. Cependant, il faut noter qu'elle ne pose généralement pas de difficultés particulières aux débutants qui n'ont jamais été exposés à des langages impératifs. Un avantage important des fonctions sans effet de bord est la facilité que l'on a à les tester unitairement. Par ailleurs, l'usage généralisé d'une gestion de mémoire automatique par l'intermédiaire d'un ramasse-miettes (garbage collector en anglais) simplifie la tâche du programmeur. En pratique, pour des raisons d'efficacité, et du fait que certains algorithmes s'expriment aisément avec une machine d'états, certains langages fonctionnels autorisent la programmation impérative en permettant de spécifier que certaines variables sont assignables (ou mutables selon la dénomination habituelle), et donc la possibilité d'introduire localement des effets de bord. Ces langages sont regroupés sont le nom de langages fonctionnels impurs. Les langages dit purement fonctionnels n'autorisent pas la programmation impérative. De fait, ils sont dénués d'effets de bord et protégés contre les problèmes que pose l'exécution concurrente. L'implémentation des langages fonctionnels fait un usage sophistiqué de la pile, car afin de s'affranchir de la nécessité de stocker des données temporaires dans des tableaux, ils font largement appel à la récursivité, - le fait d'inclure l'appel d'une fonction dans sa propre définition -. La récursivité peut être rendue plus efficace à l'aide d'une technique dénommée récursion terminale (tail-recursion en anglais), qui consiste à accumuler les résultats intermédiaires dans une case mémoire de la pile et à la passer en paramètre dans l'appel récursif. Ceci permet d'éviter d'empiler les appels récursifs dans la pile en les remplaçant par une simple succession de sauts. Le code généré par le compilateur est alors similaire à celui généré par une boucle en impératif. Certains langages comme Scheme optimisent automatiquement les appels récursifs de cette manière.

Transparence référentielle

Les langages fonctionnels ont comme autre propriété la transparence référentielle. Ce terme recouvre le principe simple selon lequel le résultat du programme ne change pas si on remplace une expression par une expression de valeur égale. Ce principe est violé dans le cas de procédures à effets de bord puisqu'une telle procédure ne dépendant pas uniquement de ses arguments d'entrée, ne se comporte pas forcément de façon identique à deux instants donnés du programme. Pourtant, la transparence référentielle est, comme nous allons le voir, une propriété extrêmement utile. Prenons un exemple. En C, si n désigne une variable globale contenant un entier à incrémenter (donc une case mémoire visible par tout le programme), et inc(k) une fonction qui augmente la valeur de n de la quantité k : int n = 2; int inc(int k) /
- incrémentation par effet de bord
- / f(inc(1) + inc(1)); Dans cet exemple, la fonction inc(i) ne retourne pas la même valeur lors des deux appels : l'un des arguments de la fonction f vaudra 2 + 1 = 3 et l'autre 3 + 1 = 4. Il s'avère donc impossible de remplacer inc(i) + inc(i) par 2
- inc(i)
car la valeur de inc(i) diffère à chaque appel. Ce comportement est fondamentalement différent de celui d'une fonction mathématique. À l'échelle d'un programme conséquent, cela signifie que le remplacement d'une fonction par une autre peut nécessiter des modifications à d'autres endroits du programme, et qu'il peut s'avérer nécessaire de retester l'intégralité du système, car on n'est pas assuré qu'un tel remplacement n'a pas modifié son comportement global. Au fur et à mesure que la complexité du système augmente, le coût d'un changement s'accroit aussi. A l'inverse, la propriété de transparence référentielle permet d'assurer que le remplacement d'une fonction par une autre équivalente ne risque pas de modifier le comportement global du programme. Autrement dit, elles sont mathématiquement égales. Cette propriété facilite la maintenance logicielle. Elle permet aussi d'appliquer de façon automatique des preuves de fonctionnement. Elle a enfin pour autre avantage de sensiblement réduire l'importance de l'ordre d'exécution, celui-ci étant assuré par l'ordre d'appel des fonctions ; un corollaire est que la parallélisation d'un programme fonctionnel peut être réalisée de façon automatique.

Une plus grande expressivité

Les langages fonctionnels emploient des types et des structures de données de haut niveau comme les listes extensibles. Il est ainsi généralement possible de réaliser facilement des opérations comme la concaténation de liste, ou l'application d'une fonction à une liste, - le parcours de la liste se faisant de façon récursive -, en une seule ligne de code. Un mécanisme puissant des langages fonctionnels est l'usage des fonctions d'ordre supérieur. Une fonction est dite d'ordre supérieur lorsqu'elle peut prendre des fonctions comme argument et/ou retourner une fonction comme résultat. On dit aussi que les fonctions sont des objets de première classe, ce qui signifie qu'elles sont manipulables aussi simplement que les types de base. Elles correspondent, en mathématiques, aux fonctionnelles. Les opérations de dérivation et d'intégration en sont deux exemples simples. Les fonctions d'ordre supérieur ont été étudiées par Alonzo Church et Stephen Kleene dans les années 1930, à partir du formalisme du lambda-calcul, un formalisme qui a grandement influencé le design de nombreux langages fonctionnels, en particulier Haskell.

Popularité du paradigme fonctionnel

Nombreux sont les programmeurs expérimentés qui pensent que la programmation fonctionnelle offre des avantages sans équivalent en impératif. Malgré cela, elle reste peu utilisée en dehors du milieu universitaire et des hobbyistes. Dans le milieu industriel, les langages Erlang (développé par Ericsson pour des besoins de programmation concurrentielle et des impératifs de robustesse), Common Lisp et Scheme sont utilisés. Mais le développement des langages fonctionnels est limité par le déficit en outils et en bibliothèques de qualité commerciale et surtout par le manque de programmeurs formés. Enfin, les langages fonctionnels souffrent encore d'une réputation de lenteur aujourd'hui complètement injustifiée : certains compilateurs Scheme, comme les compilateurs Stalin ou Bigloo, les compilateurs pour Common Lisp, les langages dans la lignée de ML, tels que Objective Caml, ou encore Haskell présentent des performances moyennes comparables ou supérieures à celles des compilateurs C++. Catégorie:Programmation informatique ja:関数型言語

Programmation orientée objet

ms:Pengaturcaraan Berorientasikan Objek ja:オブジェクト指向 th:การเขียนโปรแกรมเชิงวัตถุ La programmation orientée objet (POO, également appelée programmation à objets), est une façon d'architecturer une application informatique en regroupant les données et les traitements sur ces dernières au sein d'une même entité, les objets.

Origines

La programmation orientée objet (venant de l’anglais Object-Oriented Programming) est née des travaux sur la mise au point de langages de simulation dans les années 1960 (par exemple Simula-67), et a été utilisée également dans le cadre de recherches sur l’intelligence artificielle dans les années 1970-80. Pourquoi regrouper les données et les traitements? Pour permettre plus facilement des modifications fondamentales dans la façon de représenter et de traiter les données. Imaginons par exemple que nous souhaitions traiter des nombres complexes. Vaut-il mieux les représenter de façon cartésienne (x, y) ou polaire (angle, module)? Nous ne le saurons qu’en effectuant des statistiques dans l’application où nous les utiliserons, et nous ne pourrons effectuer ces statistiques qu’en créant justement un premier modèle opérationnel : il semble que nous soyons pris dans une autoréférence fâcheuse. Si nous convenons que nous disposons d’une boîte noire que nous nommerons un objet « nombre complexe » et qu’il soit possible de lui affecter des valeurs en représentation polaire ou cartésienne, comme d’aller les lire en polaire ou en cartésien selon ce qui nous arrange le mieux, nous pouvons différer ce choix, et le changer à tout moment rien qu’en modifiant la définition de ce seul objet. Par comparaison, une approche classique aurait obligé à faire la chasse à toutes les références utilisant des nombres complexes et à en réécrire (sans jamais rien omettre) toutes les lectures et écritures là où cela était nécessaire. C’est ici le compilateur qui se chargera à notre place de ce travail (à moins que le langage à objets ne soit interprété, comme Smalltalk, mais cela ne change rien à la flexibilité elle-même). Le support des concepts majeurs de la POO, comme ceux de classe, d’encapsulation, d’héritage simple et de polymorphisme ont été introduits avec le langage Simula, entre autre via la liaison dynamique, les restrictions d’accès (variables et fonctions privées) et la qualification. Différents autres concepts seront introduits plus tard par d’autres langages comme les métaclasses, l'héritage multiple ou les classes génériques (SmallTalk, Eiffel, etc.) Depuis, l'Objet n'a cessé d'évoluer aussi bien dans son aspect théorique que pratique et différents métiers et discours mercatiques à son sujet ont vu le jour comme l’analyse objet (AOO ou OOA en anglais), la conception objet (COO ou OOD en anglais) ou banque de données objet (SGBDOO). Aujourd’hui, l’Objet est vu davantage comme une approche utile, un paradigme, qu’une simple technique de programmation (bien que les programmes à objets soient également plus lisibles que les programmes classiques). C’est pourquoi, lorsque l’on parle de l’Objet, on le désigne plus volontiers comme approche Objet que par programmation à objets, cette dernière ne désignant plus que la partie codage d’un modèle à objets obtenu par AOO et COO.

Typage et classification

Dans l’approche Objet, les systèmes sont uniquement constitués d’entités (appelées objet) ayant chacune des caractéristiques qui sont définies par leur type. Il existe deux grandes théories des types. Celle, initiale, de Liskov et celle, plus actuelle, de Cook. Dans chacune, deux concepts sont définis : celle du typage et celle de classification.

Le typage selon Liskov

Les types, dans la théorie des types de Liskov, et dont une définition sémantique a été donnée par Cardelli, sont dits types de premier ordre. Un type, en programmation à objets, définit de façon syntaxique et sémantique les aspects visibles que présenteront les objets (les valeurs) du type ; ces propriétés sont tout ce que le programmeur et les autres objets auront besoin de connaître et connaîtront de l’objet. La question de sa représentation en mémoire ne sera pas posée et il sera possible de la changer à tout moment sans conséquences sur son utilisation. Ces aspects visibles forment l’interface de l’objet. Dans le typage du premier ordre, les classes fournissent l’implémentation (la façon dont les propriétés sont réellement représentées) des caractéristiques du ou des types qu’elles représentent ; les propriétés seront soit représentées sous forme d’emplacements mémoire (champs de données) que l’on appelle couramment attributs, soit sous forme de calculs (opérations) que l’on appelle couramment méthodes. Donc, si un type définit l’interface de l’objet, la classe lui fournit une implémentation. On a une dualité type/classe. À ce titre, la classe nombre complexe est le modèle selon lequel sont construits tous les objets de nombres complexes. Un objet est dit alors instance de sa classe. Par cette démarche, l’approche Objet introduit deux types d’héritage :
- le sous-typage : l'héritage d'interface,
- la sous-classification : l'héritage d'implémentation. L’héritage est la faculté d’une sous-classe ou d’un sous-type d’hériter des propriétés de son parent et de les raffiner. Le sous-typage est donc le processus par lequel on restreint l’espace des valeurs du type parent, et la sous-classification est le processus par lequel on récupère et on spécialise l’implémentation.

Le typage selon Cook

Toutefois, la théorie des types de Liskov se heurte au problème du sous-typage des types récursifs (les types avec au moins une opération qui accepte un objet de même type). La théorie des types de Cook (la théorie F-Bound) permet de résoudre ce problème en redéfinissant ce qu’est un type et ce qu’est une classe. Elle définit ce que l’on appelle le typage du second ordre. Dans cette approche, les concepts de classe et de type ne sont plus duals mais imbriqués. Une classe ne définit plus l’implémentation d’un ou de plusieurs types. Si le type définit toujours l’interface des objets, la classe définit l’implémentation d’une famille polymorphique finie et liée de types. Le type est devenu un élement d’une classe. Dans cette approche, l’accent n’est plus mis sur le sous-typage comme dans celle de Liskov, mais sur la sous-classification ; un héritage va décrire l’arborescence entre classes (et donc implicitement entre types). La sous-classification permet de restreindre l’espace des types. Ainsi, par exemple, le type Entier fait partie de la classe des nombres, mais aussi de celle des réels et des entiers, toutes deux sous-classes de nombres (la classe des entiers est sous-classe des réels aussi) : classe_nombre [ T -> classe_nombre [T] ] = classe_entier [ T -> classe_entier [T] ] = classe_entier <: classe_nombre entier = classe_entier [ entier -> classe_entier [entier] ] = < classe_nombre [ entier -> classe_nombre [entier] ] (Ici entier est le point fixe de la classe classe_entier et membre de la classe classe_nombre. classe_entier est sous-classe de classe_nombre.) Le typage du premier ordre inclut des langages comme Java et C++. Celui du second ordre inclut des langages comme Smalltalk et Eiffel.

Le principe d'encapsulation

Le paradigme objet, quelle que soit l'approche adoptée (le typage selon Liskov ou selon Cook), découple l’interface (ce qu'on voit d'un objet) de l'implémentation (la façon dont on va pratiquement représenter les choses par un savant mélange de mémoire et de calculs, comme dans nos nombres complexes du premier exemple : le programmeur peut les additionner ou les multiplier sans se soucier de leur représentation interne). C'est le principe d'encapsulation : l'implémentation est cachée de l'extérieur par l'interface (définition syntaxique et sémantique des propriétés). Les objets n'utilisent des autres objets que les propriétés définies par leur interface, quelque soit leur représentation interne. Cette encapsulation existait de façon embryonnaire dans des langages comme le PL/I (où par exemple le programmeur pouvait ignorer à son niveau si le nom qu'il utilisait dans une expression était celui d'une variable ou d'une fonction sans arguments (par exemple TIME), ou en APL qui de façon plus radicale permet de définir alternativement un même nom comme celui d'une variable ou d'une fonction selon l'empilement des contextes (« localisation des noms ») à l'exécution.

L'objet

Un objet est caractérisé, dans un système logiciel, par une identité et des propriétés. Les propriétés de l'objet sont définies par son type. Celles-ci peuvent-être découpées en deux parties distinctes : l'état et le comportement.
- L'identité, ou OID (Object IDentifier), permet généralement à un objet d'être référencé par d'autres de façon unique et constante pendant toute sa durée de vie. Généralement, les OID sont transparents pour le développeur ; il est à la charge du système. Dans les système d'information, celui-ci peut être global et universel et sans limite de durée : on utilise alors des identifiants supposément absolument uniques comme les UUID ou les URI.
- L’état valorise l’objet à un instant précis. Il est souvent variable et toujours conforme à son type.
- Le comportement permet de définir les opérations que peut accomplir l’objet, invoqué via l’interface de son type. Il peut, mais pas toujours, affecter l’état de l’objet. On distingue deux grandes familles de langages objet dans la déclaration du type des objets : les langages à typage dynamique et ceux à typage statique.
- dans le premier cas, le type des objets est implicite et déterminé à l’exécution lors de la création desdits objets,
- dans le second cas, le type des objets est spécifié explicitement par le développeur lors de leur déclaration. Dans la première famille, nous avons des langages comme Smalltalk, CLOS et Python. Dans la seconde famille, nous avons des langages comme Eiffel, C++ et Java.

Autres aspects

Les langages à objet peuvent être classés selon qu’ils ont les propriétés suivantes :
- l’héritage simple (Smalltalk, Java) ou multiple (C++, Python, CLOS)
- la sélection simple (Smalltalk, Java, C++, Python) ou multiple (CLOS, Dylan ...)
- l’encapsulation (Smalltalk, Java, C++) ou pas (Python, CLOS, ...)
- un protocole à méta-objets ou MOP (Smalltalk, CLOS, Python ...), c’est-à-dire la possibilité de (re-)spécifier le comportement du système à objet à travers la notion de métaclasses, à l’exécution,
- la combinaison de méthodes (CLOS, Dylan)

Modélisation objet

La modélisation objet consiste à créer un modèle informatique du système de l’utilisateur (un système informatique). Ce modèle peut rassembler aussi bien des éléments du monde réel que des concepts ou des idées propres au métier ou au domaine duquel fera partie le système. La modélisation Objet consiste à définir, à qualifier dans un premier temps ces élements sous forme de types, donc indépendamment de l’implémentation. C’est ce que l’on appelle lanalyse orientée objet ou OOA (Object-Oriented Analysis). Puis, on propose une ou des solutions techniques pour représenter les éléments définis dans le système informatique. C’est ce que l’on appelle la conception orientée objet ou OOD (Object-Oriented Design). Une fois un modèle de conception établi, il est possible au développeur de leur donner corps dans un langage de programmation. C’est ce que l’on appelle la programmation orientée objet ou OOP (Object-Oriented Programming). À un modèle d’analyse peuvent correspondre plusieurs modèles de conception. Pour écrire ces différents modèles, différents langages et méthodes ont été mis au point, dont OMT de Rumbaugh, BOOCH'93 de Booch et OOSE de Jacobson. Toutefois, ces méthodes ne permettaient de modéliser que certains types d’applications et se trouvaient limitées dans d’autres contextes. La méthode OMT prévalait sur l’ensemble des autres méthodes dans la première partie de la décennie 1990. À partir de 1994, Rumbaugh, Booch et Jacobson ont décidé de s’unir dans l’élaboration d’une nouvelle méthode, suffisamment générique, pour pouvoir s’appliquer à quasiment tous les contextes applicatifs. Ils ont commencé d’abord par définir un langage de modélisation fortement inspiré de celles des méthodes des trois auteurs : UML (Unified Modeling Language). Une fois celui-ci pris en charge par l’OMG (Object Management Group), un organisme destiné à standardiser des technologies objet, comme CORBA (Common Object Request Broker Architecture), un intergiciel (middleware) objet réparti, Rumbaugh, Booch et Jacobson se sont attaqués à la méthode proprement dite: USDP (Unified Software Development Process). Cette méthode définit un cadre générique de développement objet avec UML comme langage de modélisation. USDP (généralement raccourci en UP) est une méthode itérative et incrémentale, centrée sur l’architecture et guidée par les cas d’utilisation et la réduction des risques. C’est aux concepteurs de s’attribuer cette méthode en l’instanciant à leur métier et à leur domaine. Néanmoins pour un certain nombre de concepteurs objet, dont Bertrand Meyer, l’inventeur du langage orienté objet Eiffel, guider une modélisation objet par des cas d’utilisations est une erreur de méthode qui n’a rien d’objet et qui est plus proche d’une méthode fonctionnelle. Pour eux, les cas d’utilisations sont relégués à des utilisations plutôt annexes comme la validation d’un modèle par exemple.

Sujet lié


- Design patterns
- Simula

Bibliographie


- Bertrand Meyer (2000).
Conception et programmation orientées objet, ISBN 2-212-09111-7.
- Grady Booch, James Rumbaugh, Ivar Jacobson (2000).
Le guide de l’utilisateur UML, ISBN 2-2120-9103-6
- G. Masini, A. Napoli, D. Colnet, D. Léonard, K. Tombre (1997).
Les langages à Objets, Langages de classes, langages de frames, langages d’acteurs, Interéditions, 584 p., ISBN 2729602755 Catégorie:Programmation informatique

Common lisp object system

Catégorie:Langage orienté objet
Image:WikiLettreMini.png Cette page est considérée comme un article à approfondir.

Le Common Lisp Object System, souvent abrégé en CLOS (prononcé 'si-lauss'), est l'ensemble des primitives présentes dans le langage de programmation Common Lisp pour construire un programme orienté objet. Il existe également une version de CLOS pour le langage Scheme, nommée TinyClos.

Présentation

CLOS est un système objet à classes (il existe des systèmes à prototypes). Il descend de flavors et common loops, développés dans les années 1980. Common Lisp a été le premier langage à objet standardisé par l'ANSI, en 1995, précédant de peu SmallTalk.

Les objets et Lisp

D'un certain point de vue, Lisp est un langage orienté objet depuis le début : les structures de données manipulées ont une identité et sont réflexives. La seule chose qui lui manquait pour recevoir l'estampille orienté objet, est la capacité d'étendre le système de types de Lisp.

Caractéristiques


- héritage multiple
- sélection multiple
- combinaison de méthodes
- protocole à méta-objets (ou MOP)

Primitives

La plus petite définition de classe

(defclass foo () ()) ;; équivalent de (defclass foo (Object) ()) Par défaut, une classe est un sous-type de Object. Les types par défaut de Common Lisp (nombres, chaînes, hashages, vecteurs, etc.) ne sont pas dérivables.

Créer une instance

(make-instance 'foo)

Attributs : définition, initialiseur, accesseurs, allocation

À partir d'un exemple plus intéressant, on montre les principales caractéristiques : (defclass graphe () ((noeuds :accessor noeuds :initform (make-hash-table)) (arcs :accessor arcs :initform (make-hash-table))) (defclass noeud () ((data :accessor data :initarg :data) (in-arcs :accessor in-arcs :initform (make-hash-table)) (out-arcs :accessor out-arcs :initform (make-hash-table))) (defclass arc () ((in-noeud :accessor in-noeud :initarg :in-noeud) (out-noeud :accessor out-noeud :initarg :out-noeud))) Les attributs d'une classe sont nommés slots dans la terminologie de CLOS. Chaque slot est défini par une liste contenant au minimum son nom, en première position. Des accesseurs en lecture seule (:reader) ou lecture-écriture (:accessor ou la combinaison de :reader et :writer) peuvent être spécifiés, ainsi que la valeur initiale (:initform) et un nom pour donner une valeur au moment de l'instanciation. Ainsi, pour construire un nœud doté d'une boucle, on peut écrire : (defparameter
- un-noeud
- (make-instance 'noeud)) (defparameter
- un-arc
- (make-instance 'arc :in-noeud
- un-noeud
- :out-noeud
- un-noeud
- )) (setf (gethash (in-arcs
- un-noeud
- )
- un-arc
- )
- un-arc
- ) (setf (gethash (out-arcs
- un-noeud
- )
- un-arc
- )
- un-arc
- ) Ces manipulations de tables de hashage étant lourdes, on se construit un opérateur sethash pour en racourcir l'expression : (defmacro sethash (key value hash) `(setf (gethash ,key ,hash) ,value)) Ainsi armé, on peut réécrire les deux lignes précédents : (sethash (in-arcs
- un-noeud
- )
- un-arc
- ) (sethash (out-arcs
- un-noeud
- )
- un-arc
- )

Méthodes

Voici la plus grande originalité de CLOS : les méthodes n'y sont pas définies sur une classe. Au lieu de cela, elles sont définies un tuple de classes. Par exemple, on veut simplifier la connexion des nœuds et des arcs.

Sélection simple

Dans un langage à sélection simple, il faudrait attacher la méthode connecter à la classe nœud OU à la classe arrête, pour pouvoir écrire : un_arc.connecter (un_noeud, un_autre_noeud) Ou encore : un_noeud.connecter (un_arc, un_autre_noeud) Une méthode a un paramètre privilégié, son receveur (au sens de la réception d'un message). La notation pointée souligne ce privilège.

Sélection multiple

Avec la sélection multiple de la méthode sur le tuple des arguments obligatoires, on n'a plus besoin de choisir : (defgeneric connecter ((noeud noeud arc)) Une fonction générique est un sac de méthodes. On y place la première : (defmethod connecter ((noeud-1 noeud) (noeud-2 noeud) (un-arc arc)) (setf (in-noeud un-arc) noeud-1) (setf (out-noeud un-arc) noeud-2) (sethash out-arcs noeud-1 un-arc) (sethash in-arcs noeud-2 un-arc)) On peut maintenant écrire, pour construire la boucle : (connecter
- un-noeud
-
- un-noeud
-
- un-arc
- ) (... à continuer ...)

Typage dynamique

Tous les langages de programmation permettent, directement ou indirectement, de manipuler des valeurs. Généralement, c'est par l'entremise de variables, qui sont une association (on dit aussi une liaison) entre un symbole et une valeur. Par exemple, en langage C++, l'extrait de programme suivant : ... int a; a = 5; ... déclare une variable dénotée 'a', de type entier, et y affecte la valeur 5. C++ est un langage à typage statique. En Common Lisp, on écrira : (let ((a 5)) ...) Ici, la création d'une variable lexicale se fait sans spécifier le type. C'est la caractérique d'un langage à typage dynamique : le type de la donnée n'est pas associé au symbole qui dénote la variable mais est encodé dans la donnée elle-même. Ainsi, dans la portée du 'let', on peut interroger le type de la variable a (c'est une application de la réflexivité): (let ((a 5)) (when (numberp a) (print "a est un nombre")) ...) De plus, en cours d'exécution, 'a' pourrait recevoir un objet de type différent, si le programme l'autorise : (let ((a 5) (setf a "je suis une chaîne") ...) En résumé, le typage dynamique est une solution très commode pour le développement rapide de programmes, où le type des objets manipulés n'est pas forcément connu à l'avance. Il est nécessaire au développement d'un Protocole à Méta-Objets, ou MOP, car le typage statique l'interdit.

Implications du typage dynamique

La grande flexibilité que permet le typage dynamique se paye généralement par une surconsommation de mémoire correspondant à l'encodage du type dans la valeur, ainsi que par une perte de performance due aux indirections supplémentaires. Ce surcoût à l'exécution est toutefois contrebalancé par l'accroissement de la puissance des ordinateurs actuels, et permet le développement rapide d'applications. Pour les opérations algorithmiques coûteuses, les langages de script à typage dynamique, comme Python ou Ruby peuvent s'interfacer à des bibliothèques écrites en langage de bas niveau comme le langage C. De plus, certains langages de programmation généralistes (en particulier Common Lisp) permettent une déclaration statique facultative du type, afin de prouver la consistance du typage à la compilation et de supprimer les indirections. Lorsqu'une fonction reçoit une donnée d'un type incorrect (erreur de type à l'exécution), une exception est levée. Exemples de langages à typage dynamique :
- SmallTalk
- Common Lisp, Dylan, Scheme
- PHP, Perl, Python, Ruby, Bash

Voir aussi


- lambda-calcul

Système de gestion d'exceptions

Sujets connexes :
- interruptions
- notion de continuation =Principes= Nous étudions les SGE dans le contexte des langages de programmation fonctionnels et impératifs.

Justifications

Tout programme en exécution peut être sujet à des erreurs, pour lesquels des stratégies de détection et de réparation sont possibles. Ces erreurs ne sont donc pas des bogues des programmes, mais des conditions particulières, on parle aussi de conditions exceptionnelles ou exceptions dans le déroulement normal d'une partie d'un programme. Par exemple, l'absence d'un fichier utile n'est pas un bogue du programme ; par contre, ne pas dire quoi faire en son absence en serait un. Le traitement des situations exceptionnelles fait apparaître deux besoins :
- une syntaxe spéciale, pour distinguer l'exécution normale du traitement des erreurs,
- un flot de contrôle "non local", pour traiter et réparer les situations exceptionnelles. Dans les langages de programmation sans SGE, on n'a pas d'outil pour séparer l'exécution normale et l'exécution exceptionnelle du programme. Un algorithme, dont l'exécution normale s'exprime de façon simple et élégante, peut devenir illisible (et donc difficile à maintenir) une fois « enrobé » par une logique de traitement des situations exceptionnelles ; disposer au niveau du langage de programmation d'une syntaxe pour différencier l'exécution normale de l'exécution dans un contexte exceptionnel peut être utile. Le traitement d'une situation exceptionnelle peut nécessiter de revenir "dans le passé" de l'exécution du programme, c'est-à-dire remonter brutalement la chaîne d'appels pour annuler une opération fautive, ou encore modifier les valeurs de certaines variables, puis reprendre l'exécution du programme un peu avant le site de l'erreur. D'où le besoin d'associer, à la syntaxe spéciale, des opérateurs spéciaux pour effectuer des sauts et des modifications de variables à des points arbitraires de la chaîne d'appels.

Définitions

Dans un langage de programmation, un système de gestion d'exceptions (SGE) est conçu pour traiter les erreurs pouvant survenir à l'exécution d'un programme : c'est un mécanisme, associé à un ensemble de constructions syntaxiques. Le programmeur utilisant un SGE doit identifier les parties du programme devant être protégées de certaines erreurs à l'exécution ; l'occurrence de ces erreurs est bien sûr connue à l'avance, ainsi que la stratégie à adopter pour leur traitement.

Types d'erreurs

Les erreurs à l'exécution suivantes peuvent survenir dans tout programme :
- arithmétique (débordement, division par zéro, ...)
- collections (débordement d'indices)
- allocation mémoire
- signaux système D'autres erreurs peuvent être interceptées par le compilateur, par exemple les erreurs de type, dans les langages à typage statique. Dans les langages à objets, le type d'une exception est déterminé par sa classe. Ces langages fournissent une hiérachie prédéfinie et extensible de types d'exceptions, correspondant au type des erreurs qu'elles représentent.

Gestionnaire d'exceptions

Le premier outil est donc le gestionnaire d'exceptions. Un gestionnaire d'exception établit un ensemble de routines de traitement d'erreurs, définies par le programmeur, sur un bloc (dans une fonction ou une méthode du programme) ; ces routines sont activées pendant toute la durée d'exécution du bloc protégé. La notion d'exécution du bloc protégé inclut toute la chaîne d'appels (de fonctions, procédures ou méthodes) à partir de ce bloc : on dit que les gestionnaires d'exception sont actifs dans la portée dynamique du bloc. Le signalement d'une exception peut être automatique, s'il correspond à une exception définie dans le langage de programmation ou une bibliothèque fournie, ou bien déclenché par le programmeur par l'utilisation d'une primitive de signalement. Il est généralement possible de construire de nouveaux types d'exceptions et de programmer leur signalement. Le gestionnaire d'exception peut être complété par un ensemble de restarts, qui sont des routines permettant la modification des environnements lexicaux entre le site de signalement et le point d'établissement des gestionnaires d'exception. Un restart permet à un gestionnaire d'exception de choisir de réparer et redémarrer un calcul plutôt que de l'abandonner intégralement. Un restart est également actif dans la portée dynamique du bloc sur lequel il est défini.

Signalement d'une erreur

Lorsqu'une condition d'erreur est détectée (par une primitive de signalement, une trappe processeur, le système d'exploitation ou encore l'environnement d'exécution du programme), on dit qu'elle est signalée : un bloc de traitement d'erreur (un handler) est recherché dans la liste des gestionnaires actifs. L'exécution du programme est déférée au bloc de traitement, qui effectue des actions correctrices et décide si le calcul où l'erreur a été signalée est terminé ou bien repris (si c'est possible, c'est-à-dire en présence de restarts). Il se peut qu'aucun gestionnaire n'ait été prévu par le programmeur, auquel cas un gestionnaire par défaut, dont le comportement est pré-défini, est sélectionné.

Réparation d'une erreur, Reprise

Dans un bloc de traitement, le programmeur a deux options :
- arrêt brutal du calcul fautif et choix d'un autre calcul : la pile d'appel du bloc protégé est alors détruite,
- réparations en différents points de la chaîne d'appel ayant conduit à l'erreur, et reprise du calcul à un lieu préalablement défini. Il faut noter que les langages de programmation les plus populaires à l'heure actuelle (par exemple, C++, Java, Python), lorsqu'ils offrent un SGE, ne permettent que la terminaison pure et simple du bloc fautif. On ne peut réparer ni reprendre les opérations. =Opérateurs= Signalement d'une condition d'erreur (ne détruit pas la pile) :
- signal, error, cerror, ... L'installation d'un bloc de traitement d'erreur est réalisé avec des primitives du type :
- try/catch/finally, handler-bind, ... L'installation d'un restart (un bloc de réparation de contexte lexical) est permise par :
- restart-case, restart-bind ... La destruction de la pile d'appel entre l'expression signalant une exception et le bloc de traitement, ou un point de reprise est effectuée par :
- throw, raise En Java ou Python, par exemple, le signalement implique la destruction de la pile d'appels jusqu'au premier bloc de traitement disponible. Dans ce cas, throw (ou raise) est la seule primitive de signalement, et la réparation et la reprise sont impossibles. =Dans les langages de programmation= Avec Java, SmallTalk et CommonLisp, nous essayons de montrer les spécificités du SGE de différents langages.

Java

Java offre un SGE terminal, donc sans réparation ni reprise.

Syntaxe

try catch TypeException1 e1 catch TypeException2 e2 finally

Exemple

Un exemple simpliste de traitement d'une division par zéro : public class FooBar

Particularités

Dans CLU [Lyskov 92] et Java, une distinction est faite entre :
- les exceptions déclarées dans la signature d'une méthode (CheckedException en Java) ; par exemple void foo () throws ThisExceptionType ,
- les exceptions à l'exécution (RunTimeException en Java), qui correspondent à des évènements impossibles à localiser lexicalement à la compilation (les exceptions asynchrones), ou pouvant survenir à tout moment dans l'exécution du programme, comme les problèmes d'allocation mémoire. Les "checked exceptions" essayent de résoudre un problème de contrat. L'interface d'un module (d'une bibliothèque de classes) représente un contrat entre l'auteur du module et son utilisateur : l'argument est qu'un tel contrat ne devrait pas passer sous silence les exceptions susceptibles d'être propagées hors des frontières du module. En spécifiant les exceptions dans les signatures des méthodes, on introduit toutefois un problème. En effet, les méthodes clientes doivent choisir dans l'alternative :
- installer un GE pour les exceptions du module, ou bien
- déclarer à leur tour ces exceptions. Les méthodes utilisant des checked exceptions contaminent leurs clients avec l'obligation de décorer leur signature, s'ils n'installent pas de GE pour ces exceptions. Cette contamination trahit en partie le contrat d'indépendance entre le lieu du signalement d'une exception et le lieu de son traitement, en exposant toutes ces déclarations d'interfaces dans le chemin d'appel ; en somme elles nous ramènent aux inconvénients des langages de programmation sans SGE (transmission de l'exception par une valeur de retour spéciale, prévue en tout point de la chaîne d'appels). Les "checked exceptions" violent finalement la philosophie des exceptions (la non-localité entre le lieu de la condition et le lieu de son traitement). Du coup, la bibliothèque standard de Java utilise en pratique des runtime exceptions pour les opérateurs les plus courants (arithmétique, collections, allocation mémoire) afin éviter la pollution lexicale des checked exception.

Smalltalk

Les SGE à terminaison portent une vue radicale et rigide sur les exceptions : toute exception signalée entraîne la mort du signaleur. Le programmeur ne peut souvent utiliser le SGE que comme un « pare-feu » contre les situations exceptionnelles, pour contenir une situation urgente et irréparable, sauver ce qui peut l'être avant d'abandonner le navire ... En pratique, les exceptions signalées peuvent n'être que relativement bénignes, ou transitoires ; dans ce cas, un idiome combinant le SGE, des variables, des tests, des boucles, doit être mis en œuvre pour recommencer un calcul qui aurait échoué pour des raisons bénignes. De tels idiomes sont la réponse ad-hoc du programmeur enfermé dans un SGE sans réparation et reprise du contexte signalant. En Smalltalk, ces difficultés sont mitigées par les possibilités suivantes :
- réessayer un bloc protégé, avec de nouveaux arguments,
- reprendre l'exécution du calcul signalant, éventuellement en fournissant une "valeur de retour".

Ré-essayer

Les mots clefs retry et retryUsing: permettent, respectivement d'exécuter à nouveau le bloc protégé par le handler sans utiliser de bouclage explicite, ou d'exécuter un nouveau bloc à la place du bloc qui a signalé l'exception. Voici un exemple : | fileContents | fileContents := ['myfile.txt' asFilename readStream contents] on: Error do: [:ex | | newName | newName := Dialog prompt: 'Problem reading file. Another name?'. ex retryUsing: [newName asFilename readStream contents]]

Reprendre

Certaines exceptions sont dites « continuables ». Cela signifie qu'un gestionnaire peut envoyer un message resume (ou resume: qui transmet son argument au retour de l'expression signalante) à l'exception, ce qui provoque le transfert du contrôle sur le retour de l'expression signalante. Voyons un exemple, sur un bout de programme effectuant la lecture des « options » d'un fichier de configuration (couples variable = valeur). Le premier fragment analyse la prochaine option située dans un stream représentant le fichier : MyApplication>>readOptionsFrom: aStream | option | [aStream atEnd] whileFalse: [option := self parseOptionString. "nil if invalid" option isNil ifTrue: [InvalidOption signal] ifFalse: [self addOption: option]] Le second fragment utilise le premier pour lire la configuration complète ; le gestionnaire de l'exception « InvalidOption » y est défini. MyApplication>>readConfiguration [self readOptionsFrom: 'options' asFilename readStream] on: InvalidOption do: [:ex | (Dialog confirm: 'Invalid option line. Continue loading?') ifTrue: [ex resume] ifFalse: [ex