Common Lisp Object System

Présentation
Le Common Lisp Object System est souvent abrégé en CLOS (prononcé 'si-lauss').
C'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.
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 80. 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, c'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)
Syntaxe
La plus petite définition de classe
(defclass foo () ()) ;; équivalent à (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.
Instancier un objet
(make-instance 'foo)
Attributs : définition, initialiseur, accesseurs, allocation
A 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 nœud () ((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-nœud :accessor in-nœud :initarg :in-nœud) (out-nœud :accessor out-nœud :initarg :out-nœud)))
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-nœud* (make-instance 'nœud)) (defparameter *un-arc* (make-instance 'arc :in-nœud *un-nœud* :out-nœud *un-nœud*))
(setf (gethash (in-arcs *un-nœud*) *un-arc*) *un-arc*) (setf (gethash (out-arcs *un-nœud*) *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-nœud*) *un-arc*) (sethash (out-arcs *un-nœud*) *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. 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)
Avec la sélection multiple de la méthode sur le tuple des arguments obligatoires, on n'a plus besoin de choisir :
(defgeneric connecter ((nœud nœud arc))
Une fonction générique est un sac de méthodes. On y place la première :
(defmethod connecter ((nœud-1 nœud) (nœud-2 nœud) (un-arc arc)) (setf (in-nœud un-arc) nœud-1) (setf (out-nœud un-arc) nœud-2) (sethash out-arcs nœud-1 un-arc) (sethash in-arcs nœud-2 un-arc))
On peut maintenant écrire, pour construire la boucle :
(connecter *un-nœud* *un-nœud* *un-arc*)
(... à continuer ...)