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 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 noeud 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 noeuds et des arcs. Dans un langage à sélection simple, il faudrait attacher la méthode connecter à la classe noeud 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 ((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*)
Héritage Multiple
Classe Racine
Pour des raisons d'efficacité, il a été décidé que les types de base de Common Lisp ne pouvaient pas servir de base à de nouveaux types définis par le programmeur. Les classes de CLOS ont donc un ancètre commun : la class Object.
Sélection Multiple
A l'exception de Dylan, CLOS est le seul système à objets qui propose le principe de la sélection multiple.
On a l'habitude, depuis SmallTalk, de considérer les méthodes d'un objet (définies dans sa classe), comme des procédures ayant un paramètre privilégié : l'objet, que l'on nomme le receveur de la méthode. On parle aussi, métaphoriquement, d' envoi de message à un objet.
Une syntaxe particulière est généralement associé à l'envoi de message. Par exemple, en Python, on écrit ceci :
un_foo = Foo () # on instancie un objet de la classe Foo un_foo.mogrify (3.1415, "bonjour") # on envoie le message mogrify (...) à l'objet
Dans ce cas, la méthode mogrify est recherchée dans la classe de l'objet un_foo, ou la plus spécifique de ses superclasses qui (re)définit la méthode. Cela s'appelle la sélection simple.
Dans le cas de CLOS (et Dylan), les méthodes ne sont pas définies sur des classes : elles peuvent en effet être appliquées à un ensemble d'objets.
(... à continuer ...)