OpenScheme : Programmation objet (suite)
Par :
Guilhem de Wailly (gdw@erian-concept.com)
et :
Fernand Boéri (boeri@unice.fr)
Résumé
Le mois dernier, nous avons présenté la programmation orientée objets dans OpenScheme. Nous nous sommes arrêtés à la définition des classes.
Ce mois-ci, nous parlerons des fonctions génériques, qui permettent de définir des fonctions applicables à plusieurs types d'objets. Nous verrons comment OpenScheme sélectionne la méthode la plus spécialisée dans l'arbre de recherche, tout en laissant accès aux méthodes moins spécialisées.
L'environnement OpenScheme, version 1.3.2 pour Linux est maintenant disponible sur le CDROM de Linux Magazine.
Introduction
L'environnement de programmation OpenScheme (OSM) possède un moteur orienté objet puissant, notamment utilisé pour réaliser la bibliothèque graphique de haut niveau.
OpenScheme permet d'intégrer très facilement du code C à du code Scheme, dans le même fichier. On peut donc, grâce au moteur objet, écrire dans un seul fichier, de l'assembleur, du C, du C++, du Scheme et du Scheme objet. Ainsi, le pouvoir de description est énorme, permettant à l'environnement de s'adapter à toutes les situations.
La librairie graphique est ainsi constituée d'une couche logicielle en C de bas niveau offrant des services graphiques de base. Cette couche est dépendante du système d'exploitation utilisé. Par contre, son interface est identique dans tous les environnements. Cette interface est utilisée pour réaliser la bibliothèque de premier niveau en Scheme. C'est cette couche Scheme qui est utilisée par la bibliothèque graphique de haut niveau, en Scheme objet, et qui offre notamment les widgets ou éléments graphiques comme les boutons, les listbox, etc.
Cet article fait suite à la présentation générale de la méthode de programmation orientée objet OMT décrite dans l'article précédent et au suivant, décrivant les classes d'objet pré existantes dans le système et la manière de définir de nouvelles classes d'objet et la manière de les instancier.
Ce mois-ci, nous présentons les fonctions génériques qui permettent d'attacher des méthodes aux classes d'objets.
Le langage Scheme ne définit pas (encore) d'interface pour programmer des objets. Cependant, la puissance du langage permet de définir un moteur à objet de manière assez simple. Plusieurs implémentations de tels systèmes existent dans les différents environnements de programmation Scheme, comme MERON de C Queinnec (cf : références).
Dans OpenScheme, un moteur ressemblant à CLOS (Common Lisp Objets System) est implémenté sous la forme d'un plugin, c'est à dire d'un module séparé du moteur Scheme lui-même. Le plugin est automatiquement inclus dans la version statique d'OSM et il est chargé à la demande dans sa version dynamique.
OpenScheme 1.3.2 est librement disponible en version complète et illimitée dans le CDROM de Linux Magazine ou sur le site WEB (cf : références).
Présentation
Le mois dernier, nous avons vu qu'un certain nombre de classes été prédéfinies dans le système. Ces classes sont basées sur les objets standard de Scheme. On trouve par exemple la class <integer> correspondant aux entiers.
Nous avons vu comment définir des classes virtuelles correspondantes à des objets Scheme, avec la fonction define-virtual-classe. Il suffit de donner un nom de classe, une classe ancêtre et une fonction prédicat.
Puis nous avons décrit la fonction define-classe qui permet de définir de nouvelles classes d'objets. Cette fonction attend un nom de classe, une classe ancêtre et une liste de champs. Chaque champ peut être assorti d'options particulières.
Mais pour l'instant, nous ne savons pas utiliser ces objets.
Fonctions génériques
Les fonctions génériques vont permettre de définir des méthodes applicables aux objets.
Déclaration
Les fonctions génériques se déclarent avec define-generic et se définissent avec define-method.
La première forme, define-generic, déclare la variable Scheme correspondante à la fonction générique :
; Déclaration d'une
méthode
Osm> (define-generic f)
=> #unspecified
Utilisation
Une fois que la fonction générique est déclarée, elle peut être utilisée :
; Invocation de la
méthode
Osm> (f)
=> OsmError: f: no generic method found for `()'
L'erreur obtenue indique qu'aucune méthode n'est définie pour la liste d'arguments donnée, c'est à dire la liste vide.
Définition
Pour définir une méthode, nous utilisons define-method :
; Définition de la
méthode
Osm> (define-method (f)
(display "(f)\n"))
=> #unspecified
Nous venons de définir une méthode f lorsque f sera invoquée sans argument. Essayons :
; Invocation de la
méthode
Osm> (f)
=> (f)
=>
#unspecified
L'intérêt des fonctions génériques est de permettre de définir plusieurs comportements pour une méthode, en fonction du nombre et du type des paramètres :
; Spécialisation avec
un paramètre
Osm> (define-method (f
a)
(display "(f a)\n"))
=> #unspecified
Cette méthode sera invoquée lorsque f sera appliquée à un argument, quel que soit son type :
; avec un argument...
Osm>
(f 1)
=> (f a)
=>
#unspecified
; L'ancienne définition
existe toujours
Osm> (f)
=> (f)
=>
#unspecified
On voit que f possède plusieurs comportements, en fonction du nombre des paramètres : on dit que f est surchargée. Pour deux paramètres de n'importe quel type :
; Spécialisation avec
deux paramètres
Osm> (define-method
(f a b)
(display "(f a b)\n"))
=> #unspecified
; Invocation
Osm>
(f 1 #\a)
=> (f a b)
=>
#unspecified
Spécialisation
Mais ça ne s'arrête pas là : dans les cas précédents, les paramètres étaient implicitement du type <root>. En effet, tout objet Scheme appartient à la classe primitive <root>. Lorsque l'on définit une méthode avec un paramètre sans préciser son type, en fait le type associé à celui-ci est <root>. Ceci permet d'avoir un comportement orthogonal ( Christophe ;-).
Il est possible de préciser le type des paramètres dans la définition des méthodes. Définissons la méthode f appliquée à un entier de la classe <integer> :
; Spécialisation avec
un paramètre <integer>
Osm>
(define-method (f (a <integer>))
(display "(f <integer>)\n"))
=> #unspecified
; L'ancienne méthode
Osm>
(f #\a)
=> (f a)
=>
#unspecified
; La nouvelle
Osm>
(f 1)
=> (f <integer>)
=> #unspecified
La méthode f a été spécialisée pour un seul paramètre entier. La définition pour un seul paramètre d'un autre type reste toujours valide.
Cela est applicable pour tous les paramètres :
; Spécialisation avec
deux paramètres,
; dont le second <integer>
Osm>
(define-method (f a (b <integer>))
(display "(f a <integer>)\n"))
=> #unspecified
Ici, on spécialise f lorsque son second paramètre appartient à la classe <integer>.
Lorsque OpenScheme applique une méthode générique à des arguments, il commence à compter leur nombre, puis il recherche dans un arbre interne la méthode correspondant le mieux à la classe des arguments, en commençant par la classe la plus spécialisée. Une classe A est plus spécialisée qu'une classe B lorsque A hérite de B.
Les méthodes génériques ne permettent pas de définir des fonctions avec des arguments facultatifs, comme c'est le cas en Scheme.
Précédence
Le système objet ne se contente pas de sélectionner la méthode la plus adéquate en fonction des arguments passés : il permet à une méthode d'invoquer la méthode qui aurait était invoquée si elle n'avait pas été définie. Cette invocation se fait à l'aide de la fonction spéciale next-methode :
; déclaration de la
fonction générique
Osm>
(define-generic g)
=> #unspecified
; définition dans le
cas général
Osm> (define-method
(g a)
(display "cas <root>\n"))
=> #unspecified
; spécialisation
Osm> (define-method (g (a <integer>))
(next-method)
(display "cas
<integer>\n"))
=> #unspecified
; essais du cas <root>
Osm>
(f #\a)
=> cas <root>
=> #unspecified
; essais du cas
<integer>
Osm> (f 123)
=> cas <root>
=> cas <integer>
=> #unspecified
L'invocation de next-method se fait sans argument. En fait, cet appel est remplacé par l'appel à la méthode suivante dans l'arbre de recherche, avec tous les arguments.
Cela permet de chaîner les méthodes. Si aucune méthode moins spécialisée, l'invocation de next-method ne provoque pas d'erreur.
Fonction générique prédéfinie
oo:create
Pour rester simple, OpenScheme ne définit qu'une seule fonction générique oo:create qui est invoquée chaque fois qu'un objet est créé.
Cette fonction alloue la mémoire nécessaire pour stocker l'objet et initialise ses champs. Elle effectue aussi un contrôle, lorsque c'est spécifie avec les options :required ou :class des champs. Par convention, elle retourne l'objet nouvellement créé.
Pour illustrer cela, définissons une classe <A> :
Osm> (define-class <a> #f (the-a))
=> #unspecified
Cette classe n'hérite d'aucune autre classe. Elle possède un champ libre nommé the-a.
Spécialisons maintenant la fonction générique oo:create pour les objets de la classe <A> : Nous voulons compter le nombre d'objets de cette classe créés :
; compteur d'objet,
initialisé à 0
Osm> (define
<A>-count 0)
=> #unspecified
; Spécialisation de la
fonction
Osm> (define-method (oo:create
(self <a>)
args)
(set!
<A>-count (+ <A>-count 1))
(next-method))
=> #unspecified
Par convention, l'objet lui-même est appelé self. C'est une habitude prise avec le langage SmallTalk.
Il est important d'invoquer next-method ; si ce n'est pas fait, l'objet n'est pas créé. Par convention aussi, la méthode oo:create retourne l'objet créé. Lorsque next-method est placée en dernier, c'est elle qui retourne l'objet. Dans le cas contraire, la dernière expression dans le corps de la méthode est self ou le nom donné au premier paramètre.
Mot clefs
Le paramètre args de oo:create représente la liste des arguments passée à la fonction de création. Ainsi, si on crée un objet <A> avec des arguments, on aura :
Osm> (define
a-<A>
(build <A> :the-a 3 :other 5)
=> #unspecified
Dans ce cas, args vaut la liste (the-a 3 other 5). On note que les mots clefs commençant par le caractère ':' sont remplacés par des symboles Scheme. Dans notre cas, other et 5 ne servent à rien et ne sont pas pris en considération. Seul les arguments :the-a 3 vont être utilisés pour donner la valeur 3 au champ the-a de l'objet.
La valeur des arguments introduit avec un mot clef peut être facilement obtenue avec la fonction keyword :
Osm> (keyword 'a '(1 2 3 a 4))
=> (4)
Osm> (keyword 'b '(1 2 3 a 4))
=> #f
Osm> (keyword 'a '(1 2 3 a 4 5 6 7))
=> (4 5 6 7)
La fonction retourne la liste des valeurs situées après le mot-clef, s'il est trouvé, et la valeur fausse dans le cas contraire. On peut aussi indiquer le nombre de valeurs attendues :
Osm> (keyword 'a '(1 2 3 a 4 b 5 6) 1)
=> (4 b 5 6)
Osm> (keyword 'a '(1 2 3 a 4 b 5 6) 3)
=> #f
Dans le second cas, le symbole b dans la liste est considéré comme un mot clef, et donc le mot clef a n'introduit qu'une seule valeur au lieu des 3 attendues ; le résultat est donc #f.
La fonction keyword est très pratique pour introduire des paramètres placés dans n'importe quel ordre dans l'invocation d'une fonction.
Bientôt…
Dans un prochain article, nous présenterons la bibliothèque graphique, puis nous construirons un système orienté objet de widgets élémentaires à l'aide du moteur objet. Nous utiliserons la méthode OMT (du moins, un très petit sous-ensemble) pour spécifier la bibliothèque.
Les auteurs
Guilhem de Wailly, directeur de la société Erian Concept : support, formations, configurations, administration, développements Linux. Environnement OpenScheme.
http://www.erian-concept.com
Fernand Boéri, Professeur à l'Université de Nice - Sophia-Antipolis, membre de l'unité SPORTS du laboratoire I3S, spécialiste en modélisation, évaluation de performances et architectures parallèles.
http://www.i3s.unice.fr
Références
Programmation orientée objet
 OMT: 1-Modélisation et
conception orientées objet
James Bumbaugh et
al.
Masson / Prentice Hall
Scheme
 Structure et Interprétation
des programmes informatiques
H. Abelson, GJ. Sussman
InterEdition
 The Scheme Programming
Languages - Ansi Scheme
R. Kent Dybvig
Prentice Hall
 Les langages Lisps -
Christian Queinnec
InterEdition
 Programmer avec Scheme
- J. Chazarin
Thomson Publishing
 Revised4 Report on the
Algorithmic Language Scheme
W. Clinger, J.
Rees
ftp://ftp.nj.nec.com/pub/kelsey
Environnements Scheme
 Scm - A.
Jaffer
http://www-swiss.ai.mit.edu/~jaffer
La
référence des interprètes Scheme. Très
petit, rapide, pour beaucoup de plates-formes, extensible.
 PCScheme - Texas
Instrument
ftp://cui.unige.ch/public/pcs/pcscheme.exe
Un
très bon environnement de programmation Scheme pour DOS, avec
éditeur intégré.
 DrScheme -
Rice University
http://www.cs.rice.edu/CS/PLT/
Environnement
Scheme libre très avancé.
 ChezScheme -
Cadence, Inc
http://www.scheme.com/
Environnement
Scheme très performant.
 Inlab Scheme - Inlab
Software GmbH
http://www.munich.net/inlab/scheme/
Environnement
commercial
 EdScheme, 3Dscheme -
Scheme, Inc
http://www.schemers.com/
Environnement
de programmation Scheme pour Windows.
 OpenScheme -
Erian Concept
http://www.open-scheme.com
Environnement
professionnel de programmation Scheme comprenant un interprète,
un compilateur et un débogueur symbolique.
Existe en
version libre et commerciale, pour Linux, FreeBSD, Solaris, Windows
et BeOS, sur plateformes Intel.