OpenScheme : Programmation objet
Par :
Guilhem de Wailly (gdw@erian-concept.com)
et :
Fernand Boéri (boeri@unice.fr)
Résumé
Depuis plusieurs mois, nous décrivons dans ces colonnes le langage Scheme. Cette description était générale et s'appuyait sur le standard de fait appelé Revised4 Report On The Algoritmic Language Scheme.
Nous avons présenté les éléments importants du langage et tenté d'illustrer cela par des exemples, comme un moteur base de donnée reposant sur les tables de hachage ou un simulateur de circuits électroniques logiques.
Cette nouvelle série d'article présente plus spécifiquement l'environnement de programmation OpenScheme.
Après la description succincte de la méthode de modélisation objet OMT de l'article du mois dernier, nous montrons dans cet article comment utiliser le moteur objet d'OpenScheme pour définir des classes. Dans le prochain article, nous décrirons les méthodes génériques, et la manière de les définir.
L'environnement OpenScheme, version 1.3.1 est disponible sur le CDROM de Linux Magazine.
Introduction
L'environnement de programmation OpenScheme (OSM) possède un puissant moteur orienté objet. Ce moteur est 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 utiliser pour réaliser la bibliothèque de premier niveau en Scheme. C'est cette couche Scheme qui est utiliser 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. Cet article décrit 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. Dans le prochain article, nous décrirons les méthodes génériques.
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émentation de tels systèmes existent dans les différents environnements de programmation Scheme, comme MERON de C Queinnec.
Dans OpenScheme, un moteur ressemblant à CLOS (Common Lisp Objets System) est implémenta 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.1 est disponible en version complète et illimitée dans le CDROM de Linux Magazine.
Conventions
Ces conventions prendront leur signification au cours de la lecture.
Le nom des classes est écrit entre < et >. Cette convention n'est pas une obligation mais elle permet de différencier clairement les noms de classes des autres noms.
Toutes les classes héritent de la classe <root> qui est l'ancêtre de toutes les classes.
On appelle super la classe ancêtre d'une autre classe.
L'argument d'une fonction nommé self représente l'instance de l'objet au quel est rattaché la fonction.
Classes
OpenScheme reconnaît deux types de classes :
ï‚· Les classes virtuelles sont définies au-dessus des objets standard du langage Scheme, comme les entiers ou les procédures. Elles identifient les objets leur appartenant à l'aide d'une fonction prédicat donnée lors de la définition de la classe virtuelle, comme la fonction Scheme integer? ou procedure?.
ï‚· Les classes construites par l'utilisateur sont explicitement définies en donnant la liste des champs qui les composent et leurs propriétés.
Classes prédéfinies
Le système possède un certain nombre de classes prédéfinies. Ces classes sont calquées sur les objets Scheme comme les entiers ou les procédures.
On trouve les classes prédéfinies suivantes :
Classe |
Ancêtre |
Prédicat Scheme |
Signification |
<root> |
|
|
Classes ancêtres de toutes les classes. |
<class> |
<root> |
|
Classes des classes. Le moteur objet est méta circulaire. |
<boolean> |
<root> |
boolean? |
Booléens #f ou #t. |
<symbol> |
<root> |
symbol? |
Symboles Scheme. |
<char> |
<root> |
char? |
Caractères. |
<pair> |
<root> |
pair? |
Paires. |
<string> |
<root> |
string? |
Chaînes de caractères. |
<procedure> |
<root> |
procedure? |
Fonctions. |
<vector> |
<root> |
vectort? |
Vecteurs. |
<number> |
<root> |
number? |
Nombres (entier, réel, ...). |
<complex> |
<number> |
complex? |
Nombres complexes. |
<real> |
<complex> |
real? |
Nombres réels. |
<rational> |
<real> |
rational? |
Nombres rationnels. |
<integer> |
<rational> |
integer? |
Nombres entiers. |
<port> |
<root> |
port? |
Ports d'entrée / sortie. |
<input-port> |
<port> |
Input-port? |
Port d'entrée. |
<output-port> |
<port> |
Output-port? |
Port de sortie. |
<input-string> |
<input-port> |
Input-string? |
Port d'entrée à partir d'une chaîne de caractères. |
<output-string> |
<output-port> |
Output-string ? |
Port de sortie dans une chaîne de caractères. |
Ainsi, les objets standard de Scheme appartiennent tous à une classe du moteur objet. Les classes <root> et <class> sont définies pour rendre le moteur méta-circulaire. Dans un système à objets méta-circulaire, une classe est une instance de l'objet des classes.
Pour créer un objet appartenant à ces classes primitives (mise à part <root> et <class>), il suffit de les écrire : ainsi, l'écriture Scheme 123 crée un objet Scheme ENTIER mais aussi un objet du moteur objet appartenant à la classe <integer>.
Les classes reposant sur des objets du langage Scheme sont appelées classes virtuelles, dans le sens ou elles ne sont pas explicitement construites. Elles reposent sur les prédicats associés aux objets : ainsi, la classe <integer> repose sur le prédicat integer?. De cette manière, il est possible de créer autant de classes d'objets qu'il y a de prédicats.
Prédicat de classe
Lorsqu'une classe est définie, une fonction prédicat est aussi créer en utilisant le nom de la classe suivie d'un point d'interrogation. Cette fonction a un argument et retourne vrai (#t) si s'il appartient à cette classe et faux (#f) dans le cas contraire.
Par exemple, si la classe <xxx> est définie, la fonction <xxx>? est aussi définie. Elle retourne vrai si l'objet en argument appartient à la classe <xxx> et faux sinon.
Ainsi, il est possible écrire :
Osm> <integer>
#class
Osm> (<integer>? 123)
#t
Osm> (<integer>? 123.456)
#t
Un objet appartient à une classe et aussi aux classe ancêtres. Ainsi :
Osm> (<integer>? 123)
#t
Osm> (<real>? 123)
#t
Ce dernier résultat et obtenu car la classe <real> est un ancêtre de la classe <integer>, comme on peut le lire dans le tableau des classes virtuelles.
Autres fonctions
D'autres fonctions utilitaires permettent de manipuler les objets :
ï‚· (is-a? objet classe) : retourne vrai si objet appartient à classe et faux sinon.
ï‚· (super? classe-1 classe-2) : retourne vrai si la classe classe-1 est ancêtre de la classe classe-2 et faux sinon.
ï‚· (class-of objet) : retourne la classe de l'objet objet. La classe de l'objet est elle-même un objet, représenté sous la forme d'un vecteur.
ï‚· (class-name classe) : retourne le nom de classe, sous la forme d'un symbole Scheme et faux sinon.
Ainsi, on peut écrire :
Osm> (is-a?.123 <real>)
#t
Osm> (super?.<integer> <port>)
#f
Osm> (class-name (class-of 123))
<integer>
Définition d'une classe virtuelle
Une classe virtuelle se définie simplement à l'aide d'un nom de classes, une super classe et une fonction prédicat, avec la macro define-virtual-class :
Osm> (define-virtual-class
nom ; nom de la classe
super ;
super classe
prédicat ; fonction prédicat
)
En utilisant cette macro, la classe virtuelle <integer> est définie par :
> (define-virtual-class
<integer>
<rational>
integer?)
Toutes les autres classes virtuelles du système sont définies de la même manière. On peut ajouter autant de classes virtuelles que nécessaire afin de réutiliser en Scheme objet des objets existants.
Classes construites
Les classes virtuelles permettent d'utiliser les objets Scheme standard dans le système orienté objet d'OpenScheme.
Les classes construites permettent de définir de nouveaux types d'objets en utilisant les possibilités offertes par les méthodes orientées objet.
Le définition d'une classe se fait à l'aide de la fonction define-classe dont la syntaxe est la suivante :
(define-class
nom
; nom de la classe
super ; super classe
champ-1 ; premier champ
champ-2 ; second champ
... ; champs suivants
)
L'argument nom est le nom de la classe définie, super est la classe dont va hériter la classe que l'on est en train de définir ; lorsque la classe n'hérite d'aucune autre classe, l'argument vaut #f.
Les champs de la classe sont champ-1, champ-2, etc. Ils sont soit un symbole représentant le nom du champ, soit une liste dont le premier élément est un symbole représentant le nom du champ et les éléments suivants, les options du champ.
Lorsqu'une classe est définie, deux fonctions par champ sont aussi définies, l'une pour la lecture de la valeur du champ, et l'autre pour l'écriture. Par défaut, ces procédures sont déclarées comme :
; lecteur
(define
(classe:champ objet)...)
; écrivain
(define
(classe:champ! objet valeur)...)
où classe est le nom de la classe définie, et champ, le nom du champ. Si la classe hérite d'une autre classe, des procédures identiques sont déclarées pour chacun des champs hérités.
Ainsi, la déclaration :
(define-class <a> #f x y)
déclare la classe <a> sans héritage, qui possède les champs x et y, sans option. Les procédures suivantes sont aussi déclarées :
; prédicat
(define
(<a>? objet) ...)
; champ x
(define
(<a>:x self) ...)
(define (<a>:x! self value)
...)
; champ y
(define (<a>:y self)
...)
(define (<a>:y! self value) ...)
Lorsque les champs possèdent des options, ils sont déclarés dans une liste dont le premier élément est le nom du champ, et les éléments suivants, ses options. Les options de champs peuvent être :
:initform valeur : l'argument valeur représente la valeur initiale du champ, si elle n'est pas précisée lors de la création de l'objet.
:initkey nom : l'argument nom est le nom du mot clef permettant de donner une valeur au champ lors de la création de l'objet (cf : voir plus loi).
:required : la valeur doit être spécifiée lors de la création de l'objet.
:class classe : la valeur du champ doit appartenir à la classe. Une erreur est provoquée sinon.
:accessor nom : la fonction de lecture du champ est (nom self). La fonction d'écriture du champ est (nom! self valeur).
:virtual lecteur écrivain : La valeur du champ n'est pas stockée dans l'objet, mais calculée : la procédure (lecteur self) est invoquée chaque fois que la valeur du champ de l'objet est lue. la fonction (écrivain self valeur) est invoquée chaque fois que la valeur du champ de l'objet est modifiée pour valeur. Les classes virtuelles sont souvent utilisées pour ajouter de nouveaux champs à un objet, dont les valeurs peuvent être déduites de la valeur des autres champs.
Création
Après avoir définit des classes, il est possible de créer des objets correspondants avec la fonction build :
Osm> (define-class
<a> ; nom de la classe
#f ; pas d'héritage
x ; premier champ
y ; second champ
)
#unspecified
Osm> (define un-objet
(build <a>
; x
initialisé à 123
:x 123))
#unspecified
La procédure build retourne une nouvelle instance de la classe donnée en argument. Les champs peuvent être initialisés lors la création en utilisant leur nom préfixés par le caractère :, suivit de la valeur du champ. Les champs non-initialisés ont comme valeur #unbounded ou la valeur introduite par l'option :initform.
On pourrait écrire :
Osm> (<a>? un-objet)
#t
Osm> (<integer>? un-objet)
#f
Osm> (<root>? un-objet)
#t
Osm> (<a>:x un-objet)
123
Osm> (<a>:y un-objet)
#unbounded
Osm> (<a>:y! un-objet 456)
#unspoecified
Osm> (<a>:y un-objet)
#456
En utilisant les options des champs, on a :
Osm> (define-class <a>
#f
[x :initkey the-x
:class <integer>]
[y
:initform 456])
: #unspecified
> (define un-objet (build <a> :the-x 123))
: #unspecified
> (<a>:x un-objet)
: #123
> (<a>:y un-objet)
: #456
> (<a>:x! un-objet "une chaîne")
: OsmError: <integer> required instead of <string>
L'option :virtual permet de calculer la valeur d'un champ. Par exemple :
> (define-class <a>
#f
[x :virtual
;
lecteur en premier
(lambda (self)
(+ (<a>:y self) 34))
;
lecteur en premier
(lambda (self
value)
(- (<a>:y! self) 34))]
[y :initform 456])
: #unspecified
> (define un-objet (build <a>))
: #unspecified
> (<a>:x un-objet)
: 422
> (<a>:y un-objet)
: 456
> (<a>:x! un-objet 123)
: unspecified
> (<a>:x un-objet)
: 55
> (<a>:y un-objet)
: 89
La prochaione fois
Ce premier article montre comment utiliser les objets Scheme existants et les incorporer à l'aide des classes virtuelles dans le système à objet. Il montre aussi comment dé finir de nouvelles classes d'objets.
Le prochain article traitera des fonctions génériques qui permettent de définir des méthodes associées aux classes des objets.
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.