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.