Discussion:
delegation de type
(trop ancien pour répondre)
Laurent Deniau
2005-09-14 13:39:53 UTC
Permalink
Je cherche a savoir si il existe des langages qui sont capables de faire
de la delagation de type. Mon contexte est le langage C, mais ca n'a en
principe pas d'importance.

Objective-C avec sa methode forward: ou son objet NSInvocation est
capable de faire de la delegation de *comportement*. Le premier cas est
tres restrictif quand a la propagation des arguments et le type de
retour (sans parler de non-portabilite) et le second est tres lourd et
tres lent. D'une maniere generale on peut se demander si la delegation
de comportement peut etre a la fois simple, efficace et sure
(verification des types). D'autres langages qu'Objective-C supportent
aussi plus ou moins bien la delegation de comportement.

Au lieu de propager un comportement, la delagation de type (beaucoup
plus simple) consiste a demander en dernier recours a l'objet lui-meme
un objet de substitution qui correspond au type demande et qui supporte
directement le comportement plutot que de le deleguer. Concretement dans
mon systeme, cela se traduit par un operateur de cast dynamique (disons
un equivalent de dynmique_cast a la C++) qui si la conversion echoue,
appelle une fonction virtuelle cast(type) heritee par toutes les
classes. Par defaut cette methode renvoie 0 (ou NULL ou nil suivant
affinite). Mais une classe est libre de redefinir cette methode et de
retourner autre chose. Par exemple un objet contenu dans l'instance, un
singleton, un objet cree a la volee, etc... L'avantage de la delegation
de type est que la delegation de comportement, l'heritage multiple ou
l'heritage dynamique peuvent etre simules simplement et efficacement.
Cela ouvre aussi la voie a de nouveau 'design pattern' tres flexibles
(pour un langage dynamiquement type). Cependant, l'instance retournee
pouvant ne plus etre l'objet original, il ne doit donc pas etre detruit
mais cela s'avere une contrainte mineure et simple. Un cast vers l'objet
original est normalement impossible (mais peut le devenir si
necessaire), mais la aussi, c'est un besoin rare (que je n'ai pas encore
rencontre).

Je ne connais pas de langage OO general (type C++, Java, Eiffel) qui
offre ce genre de facilite (ou je n'en ai pas reconnu le principe).
Auriez-vous des exemples, reference ou papiers?

a+, ld.
drkm
2005-09-14 15:01:19 UTC
Permalink
Post by Laurent Deniau
Au lieu de propager un comportement, la delagation de type (beaucoup
plus simple) consiste a demander en dernier recours a l'objet lui-meme
un objet de substitution qui correspond au type demande et qui supporte
directement le comportement plutot que de le deleguer.
J'ai du mal à comprendre le mécanisme auquel tu penses.

Puisque l'on appelle une fonction par son nom, et que l'on ne
précise pas le type auquel on pense qu'elle devrait s'appliquer,
je ne vois pas bien comment on pourrait déléguer sur base d'un
« type cible » plutôt que sur un nom de fonction.

Pourrais-tu donner une séquence d'appel d'exemple dans laquelle
intervient de la délégation de type ?
Post by Laurent Deniau
Je ne connais pas de langage OO general (type C++, Java, Eiffel) qui
offre ce genre de facilite (ou je n'en ai pas reconnu le principe).
Auriez-vous des exemples, reference ou papiers?
CLOS, comme d'autres, permet de définir une méthode à appeler
lorsque l'on appelle une fonction générique pour laquelle on ne
trouve pas de méthode. Mais si je te suis, cela permet de mettre
en place de la délégation de comportement, pas de type.

--drkm
Laurent Deniau
2005-09-14 16:38:23 UTC
Permalink
Post by drkm
Post by Laurent Deniau
Au lieu de propager un comportement, la delagation de type (beaucoup
plus simple) consiste a demander en dernier recours a l'objet lui-meme
un objet de substitution qui correspond au type demande et qui supporte
directement le comportement plutot que de le deleguer.
J'ai du mal à comprendre le mécanisme auquel tu penses.
Je pense a castTo de OOC (que tu connais).

OOC ne supporte que l'heritage simple (et ne supportera jamais
l'heritage multiple). Si par exemple on veut un IOStream implemente en
terme de InStream et OutStream, avec la delegation de type on fait par
exemple: IOStream derive de OutStream et contient un InStream. Le tout
etant ramene a la classe abstraite Stream.

Maintenant quand je veux utiliser mon Stream comme un OutStream, je fais:

void my_write(Stream stm, constString msg)
{
OutStream out = castTo(stm, OutStream);
// use of out
}

Comme OutStream derive de Stream, pas de probleme, le systeme de type
fait le boulot. Si je veux utiliser mon Stream comme un InStream, je fais:

void my_read(Stream stm, constString msg)
{
InStream in = castTo(stm, InStream);
// use of in
}

Mais pour que cela marche, la classe IOStream doit redefinir la methode
cast et qui sera appelee par l'operateur castTo une fois qu'il aura
reconnu que le Stream fourni ne derive pas de InStream:

constMethod(constTYPE, cast, constClass cls)
{
if (isClassOf(cls, InStream))
return self->in;

return 0;
}

Cet exemple montre comment simuler l'heritage multiple a l'aide d'un
heritage simple et d'une composition. Avec les protocols (interface a la
Java) qui permettent l'heritage multiple de comportement, on s'oriente
vers la delegation *dynamique* et *typee* de comportement, ce que
Objective-C fait avec NSInvocation grace a l'usine a gaz qui est
derriere et la collaboration du compilateur (notament qques build in
pour gcc).
Post by drkm
Puisque l'on appelle une fonction par son nom, et que l'on ne
Il ne s'agit pas de fonction, mais de methode soit object+fonction.
Post by drkm
précise pas le type auquel on pense qu'elle devrait s'appliquer,
je ne vois pas bien comment on pourrait déléguer sur base d'un
« type cible » plutôt que sur un nom de fonction.
Pourrais-tu donner une séquence d'appel d'exemple dans laquelle
intervient de la délégation de type ?
cf mon exemple ci-dessus. Une autre application specifique OOC, c'est
pour detecter dans la reflection si un objet est issu d'un
Boxing-Unboxing ou non et s'il faut le detruire (par exemple valeur de
retour de l'invocation d'une methode via la reflection).

// obj est-il un boxing d'un type de base du C fait par la reflection?
if (isA(obj, CTypeWrap))
delete(obj);
Post by drkm
Post by Laurent Deniau
Je ne connais pas de langage OO general (type C++, Java, Eiffel) qui
offre ce genre de facilite (ou je n'en ai pas reconnu le principe).
Auriez-vous des exemples, reference ou papiers?
CLOS, comme d'autres, permet de définir une méthode à appeler
lorsque l'on appelle une fonction générique pour laquelle on ne
trouve pas de méthode. Mais si je te suis, cela permet de mettre
en place de la délégation de comportement, pas de type.
Oui. Que ce passe-t-il avec les arguments de la methode non trouvee,
sont-ils propages au handler?

a+, ld.
drkm
2005-09-14 17:44:17 UTC
Permalink
Post by Laurent Deniau
Post by drkm
J'ai du mal à comprendre le mécanisme auquel tu penses.
Je pense a castTo de OOC (que tu connais).
Ok. Mais d'après l'exemple que tu donnes, il y a intervention
explicite de l'utilisateur. Je pensais à quelque chose plus
comme le 'doesNotUnderstand' de Smalltalk ou la fonction
générique 'no-applicable-method' de CLOS, qui sont des mécanismes
déclenchés automatiquement par l'appel d'une méthode qui n'existe
pas pour un objet.

En fait, je ne vois pas la différence avec les opérateurs de
conversion de C++, par exemple. Tu veux que l'opérateur de
conversion d'OOC, non seulement se serve du graphe d'héritage,
mais puisse également demander à la classe si elle connaît un
moyen d'effectuer la conversion demandée (soit si le graphe
d'héritage ne convient pas, donc après, soit même pour pouvoir le
surcharger, donc avant).

~> cat ~/drafts/fco/casts.cc
#include <iostream>
#include <ostream>

struct A {
void f() const {
std::cout << "A::f()" << std::endl;
}
};

struct B {
operator A const&() const {
return myA;
}
private:
A myA;
};

int main() {
B b;
A const& a = b;
a.f();
}

~> g++ -o ~/drafts/fco/casts ~/drafts/fco/casts.cc \
-ansi -Wall -pedantic

~> ~/drafts/fco/casts
A::f()
Post by Laurent Deniau
<snip exemple>
cf mon exemple ci-dessus. Une autre application specifique OOC, c'est
pour detecter dans la reflection si un objet est issu d'un
Boxing-Unboxing ou non et s'il faut le detruire (par exemple valeur de
retour de l'invocation d'une methode via la reflection).
// obj est-il un boxing d'un type de base du C fait par la reflection?
if (isA(obj, CTypeWrap))
delete(obj);
Ok. Tu utilises donc ici une classe dont la sémantique est un
peu comme un marqueur, pour préciser que l'objet n'est qu'un
wrapper autour d'un objet C. Ne pourrais-tu pas également ici
utiliser un protocole, et donc un 'pcast' au lieu du 'isA' (si je
me souviens bien) ? Note, je n'ai pas réfléchi aux performances,
mais il faut rechercher dans la liste des protocoles implémentés
plutôt que de chercher dans le graphe d'héritage + appel à la
méthode 'cast' (puisque 'CTypeWrap' ne se trouve pas dans le
graphe d'héritage, n'est-ce pas ?), a priori ça ne devait pas
être très loin.
Post by Laurent Deniau
Post by drkm
CLOS, comme d'autres, permet de définir une méthode à appeler
lorsque l'on appelle une fonction générique pour laquelle on ne
trouve pas de méthode. Mais si je te suis, cela permet de mettre
en place de la délégation de comportement, pas de type.
Oui. Que ce passe-t-il avec les arguments de la methode non trouvee,
sont-ils propages au handler?
Oui :

CL-USER> (defclass C () ())
#<STANDARD-CLASS C>
CL-USER> (defmethod no-applicable-method ((f generic-function) &rest args)
(format t "~A~{, ~A~}~%" f args))
#<STANDARD-METHOD NO-APPLICABLE-METHOD (GENERIC-FUNCTION) {97B2289}>
CL-USER> (defgeneric f (obj))
#<STANDARD-GENERIC-FUNCTION F (0)>
CL-USER> (f (make-instance 'C))
#<STANDARD-GENERIC-FUNCTION F (0)>, #<C {97C1581}>
NIL
CL-USER>

--drkm
Laurent Deniau
2005-09-15 11:18:43 UTC
Permalink
Post by drkm
Post by Laurent Deniau
Post by drkm
J'ai du mal à comprendre le mécanisme auquel tu penses.
Je pense a castTo de OOC (que tu connais).
Ok. Mais d'après l'exemple que tu donnes, il y a intervention
explicite de l'utilisateur.
Non du developpeur. Dans les deux cas l'utilisateur utilise un up-cast
sans savoir qui est quoi (et il n'a pas besoin de le savoir). Le fait
que IOStream derive de OutStream et contient un InStream est une
decision du developpeur de la classe. Idem pour l'override de la methode
cast est simule un class IOStream : public OutStream, public InStream {
... }; en C++.
Post by drkm
Je pensais à quelque chose plus
comme le 'doesNotUnderstand' de Smalltalk ou la fonction
générique 'no-applicable-method' de CLOS, qui sont des mécanismes
déclenchés automatiquement par l'appel d'une méthode qui n'existe
pas pour un objet.
Delegation de comportement. Que devient les arguments et comment la
valeur de retour est-elle propagee? Cela demande soit une encapsulation
des arguments et de la valeur de retour, type NSInvocation, soit des
restriction tres fortes sur les arguments possibles (nombre et type),
soit une manipulation special de la pile et des registres de la part du
compilateur (cas de gcc).
Post by drkm
En fait, je ne vois pas la différence avec les opérateurs de
conversion de C++, par exemple. Tu veux que l'opérateur de
Cela ne represente qu'une partie. C'est vrai que dynamic_cast + heritage
multiple de C++ permet de faire la meme chose pour le cas de la
simulation de l'heritage multiple.

Il y a d'autre cas impossible en C++, notament la delegation de
comportement. Pense au design pattern Bridge qui en C++ est rigide et
demande de refaire toute l'interface pour ensuite deleguer a
l'implementation. En OOC, tu pars d'une classe abstraite quelconque
(sans delegation ni meme implementation) et tu demandes a un objet (qui
contient differente instance de ce qu'il peut faire) de s'y conformer,
soit par un castTo (transtypage) soit par un castToProto (transtypage
base sur une delegation de comportement).

la methode cast selectionnera dans l'objet composite un autre objet
contenu ou reference en fonction du type (de la classe). Pour le
protocol c'est un peu plus complique mais c'est pcast qui fait le boulot
et renvoie a la fois l'objet conforme au protocol demande et le protocol
lui-meme.

On peut voir ca comme du dispatch de type (classe) ou de comportement
(protocol). Une fois recupere l'objet conforme a la demande,
l'utilisateur l'utilise de facon transparente et sans delegation. Seule
contrainte, ne pas detruire un objet retourne par castTo ou castToProto.
On peut toujours comparer les adresses pour verifier que c'est le meme
objet (cas ou l'heritage repond positivement) pour savoir si oui ou non
on peut detruire l'objet.
Post by drkm
conversion d'OOC, non seulement se serve du graphe d'héritage,
mais puisse également demander à la classe si elle connaît un
moyen d'effectuer la conversion demandée (soit si le graphe
d'héritage ne convient pas, donc après, soit même pour pouvoir le
surcharger, donc avant).
Exactement, mais dans le but indirect de pouvoir effectuer une action.
Post by drkm
~> cat ~/drafts/fco/casts.cc
#include <iostream>
#include <ostream>
struct A {
void f() const {
std::cout << "A::f()" << std::endl;
}
};
struct B {
operator A const&() const {
return myA;
}
A myA;
};
C'est a peut pres ca, sauf que c'est statique (meme si la conversion est
dynamique). C++ doit connaitre A et B pour pouvoir le faire. Mais tu ne
peux pas demander a dynamic_cast de faire la meme chose. D'autre part,
pense que B contient plutot une reference sur une instance de A, pas un
A directement (sinon un heritage prive ferait l'affaire).
Post by drkm
int main() {
B b;
A const& a = b;
a.f();
}
~> g++ -o ~/drafts/fco/casts ~/drafts/fco/casts.cc \
-ansi -Wall -pedantic
~> ~/drafts/fco/casts
A::f()
Post by Laurent Deniau
<snip exemple>
cf mon exemple ci-dessus. Une autre application specifique OOC, c'est
pour detecter dans la reflection si un objet est issu d'un
Boxing-Unboxing ou non et s'il faut le detruire (par exemple valeur de
retour de l'invocation d'une methode via la reflection).
// obj est-il un boxing d'un type de base du C fait par la reflection?
if (isA(obj, CTypeWrap))
delete(obj);
Ok. Tu utilises donc ici une classe dont la sémantique est un
peu comme un marqueur, pour préciser que l'objet n'est qu'un
wrapper autour d'un objet C. Ne pourrais-tu pas également ici
Exactement, cela evite de mettre un flag dans toutes les instances car
on utilise le systeme de typage dynamique pour ajouter une information
sur la notion de boxing temporaire.
Post by drkm
utiliser un protocole, et donc un 'pcast' au lieu du 'isA' (si je
me souviens bien) ?
Le protocol permettent deja l'heritage multiple de comportement (comme
les interfaces Java). Donc l'enjeux est un peu different. pcast n'existe
pas comme operateur, c'est en fait la methode appellee par castToProto
si la recherche directe echoue (comme la methode cast pour l'operateur
castTo). Maintenant castToProto renvoie un objet conforme au protocol et
son protocol (les protocols sont aussi des objets).

Les prototypes ressemblent a:

Object castTo(Object obj, Class cls);
Object castToProto(Object obj, Protocol proto, Protocol *proto_ret);
Post by drkm
Note, je n'ai pas réfléchi aux performances,
mais il faut rechercher dans la liste des protocoles implémentés
non.
Post by drkm
plutôt que de chercher dans le graphe d'héritage + appel à la
méthode 'cast' (puisque 'CTypeWrap' ne se trouve pas dans le
graphe d'héritage, n'est-ce pas ?), a priori ça ne devait pas
être très loin.
Pour castTo on recherche dans le graphe d'heritage (note que c'est un
arbre, par un graphe a cause de l'heritage simple). Si cela echoue on
renvoie le resultat de la methode cast.

Les protocols n'interviennent que dans castToProto et c'est la methode
pcast qui est appelee en dernier ressort.
Post by drkm
Post by Laurent Deniau
Post by drkm
CLOS, comme d'autres, permet de définir une méthode à appeler
lorsque l'on appelle une fonction générique pour laquelle on ne
trouve pas de méthode. Mais si je te suis, cela permet de mettre
en place de la délégation de comportement, pas de type.
Oui. Que ce passe-t-il avec les arguments de la methode non trouvee,
sont-ils propages au handler?
J'ai un peu du mal a decripter. J'aimerais que tu details un peu.
Post by drkm
CL-USER> (defclass C () ())
Ok tu definis la classe C.
Post by drkm
#<STANDARD-CLASS C>
CL-USER> (defmethod no-applicable-method ((f generic-function) &rest args)
(format t "~A~{, ~A~}~%" f args))
Tu definis quoi faire si la fonction generique f n'est pas supportee par
une classe. Mais je ne sais pas ce qu'est &rest (la valeur de retour?).
J'intuite vaguement que tu affiches le nom de la methode et ses arguments.
Post by drkm
#<STANDARD-METHOD NO-APPLICABLE-METHOD (GENERIC-FUNCTION) {97B2289}>
CL-USER> (defgeneric f (obj))
Tu definis la fonction generique f ne faisant rien. A priori, pas de
specialisation pour C.
Post by drkm
#<STANDARD-GENERIC-FUNCTION F (0)>
CL-USER> (f (make-instance 'C))
Tu appelles f avec une instance de C ce qui devrait arriver a ta
definition du no-applicable-method pour f.
Post by drkm
#<STANDARD-GENERIC-FUNCTION F (0)>, #<C {97C1581}>
NIL
Et la je rame ;-) C'est quoi ce NIL? le resultat de l'evaluation de
no-applicable-method? Je me serais attendu a qqchose comme "f
instance-of-C (ou une adresse ou un id)".
Post by drkm
CL-USER>
C-USER!

Note (si tu n'avais pas devine) que je ne connais (toujours) pas CLOS.

Ceci dit ca ne m'etonne pas que Lisp sache faire de la delegation de
comportement, vu que les arguments sont des listes (donc equivalent a un
seul argument container) ce qui simplifie beaucoup semantiquement
parlant (en interne c'est sa cuisine).

a+, ld.
Laurent Deniau
2005-09-16 12:39:54 UTC
Permalink
[...]
Post by Laurent Deniau
Le protocol permettent deja l'heritage multiple de comportement (comme
les interfaces Java). Donc l'enjeux est un peu different. pcast n'existe
pas comme operateur, c'est en fait la methode appellee par castToProto
si la recherche directe echoue (comme la methode cast pour l'operateur
castTo). Maintenant castToProto renvoie un objet conforme au protocol et
son protocol (les protocols sont aussi des objets).
La j'ai dit une betise. castToProto renvoie un protocol conforme a celui
demande, mais il ne peut pas transtyper l'objet lui-meme car si c'etait
le cas, ce transtypage serait silencieux (changement de type pas connu
du compilateur), ce qui est dangeureux.
Donc je mets a jour la correction des prototypes.
Post by Laurent Deniau
Object castTo (Object obj, Class cls );
Protocol castToProto(Object obj, Protocol proto);
En revanche, en cas d'echec, castTo et castToProto atterissent
respectivement sur les methodes

constMethod(Object , cast , Class cls)
constMethod(Protocol, pcast, Protocol proto)
Post by Laurent Deniau
Post by drkm
Note, je n'ai pas réfléchi aux performances,
Elle sont bonnes, on est en C ;-)

La delegation de type est plus performante que la delegation de
comportement, mais elle est aussi moins flexible (moins de possibilite).
Ceci dit, je n'ai pas d'exemple ou la delegation de comportement
dynamique (message forwarding) peut faire qqchose que la derivation +
delegation de type et/ou protocol + delegation de comportement statique
ne peut pas faire. Excepter peut-etre l'interpretation dynamique (a
partir d'une string) de methode, mais pour ca on a la reflection ;-)

a+, ld.

bruno modulix
2005-09-14 18:04:05 UTC
Permalink
Post by Laurent Deniau
Je cherche a savoir si il existe des langages qui sont capables de faire
de la delagation de type. Mon contexte est le langage C, mais ca n'a en
principe pas d'importance.
Objective-C avec sa methode forward: ou son objet NSInvocation est
capable de faire de la delegation de *comportement*. Le premier cas est
tres restrictif quand a la propagation des arguments et le type de
retour (sans parler de non-portabilite) et le second est tres lourd et
tres lent. D'une maniere generale on peut se demander si la delegation
de comportement peut etre a la fois simple, efficace et sure
(verification des types). D'autres langages qu'Objective-C supportent
aussi plus ou moins bien la delegation de comportement.
Au lieu de propager un comportement, la delagation de type (beaucoup
plus simple) consiste a demander en dernier recours a l'objet lui-meme
un objet de substitution qui correspond au type demande et qui supporte
directement le comportement plutot que de le deleguer.
Pour ma culture personnelle, est-ce que tu pourrais donner un (autre)
exemple d'utilisation, s'il te plait ? J'ai bien vu ta réponse à dkrm,
mais connaissant très mal ooc, je ne suis pas sûr d'avoir suivi (désolé).

(snip)
--
bruno desthuilliers
ruby -e "print '***@xiludom.gro'.split('@').collect{|p|
p.split('.').collect{|w| w.reverse}.join('.')}.join('@')"
Laurent Deniau
2005-09-15 11:19:53 UTC
Permalink
Post by bruno modulix
Post by Laurent Deniau
Je cherche a savoir si il existe des langages qui sont capables de faire
de la delagation de type. Mon contexte est le langage C, mais ca n'a en
principe pas d'importance.
Objective-C avec sa methode forward: ou son objet NSInvocation est
capable de faire de la delegation de *comportement*. Le premier cas est
tres restrictif quand a la propagation des arguments et le type de
retour (sans parler de non-portabilite) et le second est tres lourd et
tres lent. D'une maniere generale on peut se demander si la delegation
de comportement peut etre a la fois simple, efficace et sure
(verification des types). D'autres langages qu'Objective-C supportent
aussi plus ou moins bien la delegation de comportement.
Au lieu de propager un comportement, la delagation de type (beaucoup
plus simple) consiste a demander en dernier recours a l'objet lui-meme
un objet de substitution qui correspond au type demande et qui supporte
directement le comportement plutot que de le deleguer.
Pour ma culture personnelle, est-ce que tu pourrais donner un (autre)
exemple d'utilisation, s'il te plait ? J'ai bien vu ta réponse à dkrm,
mais connaissant très mal ooc, je ne suis pas sûr d'avoir suivi (désolé).
J'espere que ma deuxieme reponse a drkm est plus claire...

a+, ld.
Loading...