32 votes

Comment les multiméthodes résolvent-elles le problème de l'espace de noms?

Je fais des recherches sur le langage de programmation de la conception, et je me suis intéressé à la question de comment faire pour remplacer le populaire répartition unique de transmission de message OO paradigme avec l'multimethods générique-fonction de paradigme. Pour la plupart, il semble très simple, mais j'ai récemment devenu coincé et apprécierait un peu d'aide.

De passage de Message OO, dans mon esprit, est une solution qui permet de résoudre deux différents problèmes. J'explique ce que je veux dire en détail dans le pseudo-code suivant.

(1) Il résout le problème de répartition:

=== dans le fichier de l'animal.code ===

   - Animals can "bark"
   - Dogs "bark" by printing "woof" to the screen.
   - Cats "bark" by printing "meow" to the screen.

=== dans le fichier myprogram.code ===

import animal.code
for each animal a in list-of-animals :
   a.bark()

Dans ce problème, "écorce" est une méthode avec plusieurs "branches" qui fonctionnent différemment selon les types d'argument. Nous mettons en œuvre "écorce" une fois pour chaque type d'argument qui nous intéresse (Chiens et Chats). Au moment de l'exécution, nous sommes en mesure de parcourir une liste d'animaux et de sélectionner de façon dynamique la branche appropriée à prendre.

(2) Il résout le problème de l'espace de noms:

=== dans le fichier de l'animal.code ===

   - Animals can "bark"

=== dans l'arborescence des fichiers.code ===

   - Trees have "bark"

=== dans le fichier myprogram.code ===

import animal.code
import tree.code

a = new-dog()
a.bark() //Make the dog bark

…

t = new-tree()
b = t.bark() //Retrieve the bark from the tree

Dans ce problème, "écorce" est en fait deux conceptuellement différentes fonctions qui vient d' arriver à avoir le même nom. Le type de l'argument (que ce soit chien ou d'un arbre) détermine la fonction que nous réellement dire.


Multimethods élégante de résoudre le problème numéro 1. Mais je ne comprends pas comment ils résolvent le problème numéro 2. Par exemple, le premier des deux exemples ci-dessus peut être traduit en un simple mode de multimethods:

(1) les Chiens et les Chats à l'aide de multimethods

=== dans le fichier de l'animal.code ===

   - define generic function bark(Animal a)
   - define method bark(Dog d) : print("woof")
   - define method bark(Cat c) : print("meow")

=== dans le fichier myprogram.code ===

import animal.code
for each animal a in list-of-animals :
   bark(a)

Le point clé est que la méthode de l'écorce(le Chien) est conceptuellement liées à l'écorce(Cat). Le deuxième exemple n'a pas cet attribut, c'est pourquoi je ne comprends pas comment multimethods résoudre la question de l'espace de noms.

(2) Pourquoi multimethods ne travaille pas pour les Animaux et les Arbres

=== dans le fichier de l'animal.code ===

   - define generic function bark(Animal a)

=== dans l'arborescence des fichiers.code ===

   - define generic function bark(Tree t)

=== dans le fichier myprogram.code ===

import animal.code
import tree.code

a = new-dog()
bark(a)   /// Which bark function are we calling?

t = new-tree
bark(t)  /// Which bark function are we calling?

Dans ce cas, où la fonction générique-elles être définies? Devrait-il être défini au niveau supérieur, au-dessus à la fois des animaux et des arbres? Il n'a pas de sens de parler de l'écorce pour en faire des animaux et des arbres comme les deux méthodes de la même fonction générique parce que les deux fonctions sont conceptuellement différentes.

Autant que je sache, je n'ai pas trouvé de travail dans le passé qui a résolu ce problème. J'ai regardé Clojure multimethods, et le CLOS multimethods et ils ont le même problème. Je suis en croisant les doigts et en espérant une solution élégante au problème, ou de persuader les arguments sur pourquoi c'est pas vraiment un problème dans la vraie programmation.

S'il vous plaît laissez-moi savoir si la question a besoin de clarification. C'est une assez subtile (mais important) point je pense.


Merci pour les réponses à la santé mentale, Rainer, Marcin, et Matthias. Je comprends vos réponses et tout à fait d'accord que la distribution dynamique et résolution d'espace de noms sont deux choses différentes. CLOS ne fait pas l'amalgame entre les deux idées, alors que le traditionnel passage de message OO n'. Cela permet également une extension de multimethods à l'héritage multiple.

Ma question est spécifiquement dans la situation où l'amalgame est souhaitable.

L'exemple suivant est un exemple de ce que je veux dire.

=== le fichier XYZ.code ===

define class XYZ :
   define get-x ()
   define get-y ()
   define get-z ()

=== fichier: POINT.code ===

define class POINT :
   define get-x ()
   define get-y ()

=== fichier: GÈNE.code ===

define class GENE :
   define get-x ()
   define get-xx ()
   define get-y ()
   define get-xy ()

==== fichier: my_program.code ===

import XYZ.code
import POINT.code
import GENE.code

obj = new-xyz()
obj.get-x()

pt = new-point()
pt.get-x()

gene = new-point()
gene.get-x()

En raison de la conjonction de résolution d'espace de noms avec l'expédition, le programmeur peut naïvement appel de get-x() sur les trois objets. C'est également parfaitement univoque. Chaque objet possède son propre ensemble de méthodes, alors il n'y a pas de confusion quant à ce que le programmeur voulait dire.

Contraste avec l'multimethod version:


=== le fichier XYZ.code ===

define generic function get-x (XYZ)
define generic function get-y (XYZ)
define generic function get-z (XYZ)

=== fichier: POINT.code ===

define generic function get-x (POINT)
define generic function get-y (POINT)

=== fichier: GÈNE.code ===

define generic function get-x (GENE)
define generic function get-xx (GENE)
define generic function get-y (GENE)
define generic function get-xy (GENE)

==== fichier: my_program.code ===

import XYZ.code
import POINT.code
import GENE.code

obj = new-xyz()
XYZ:get-x(obj)

pt = new-point()
POINT:get-x(pt)

gene = new-point()
GENE:get-x(gene)

Parce que get-x() de XYZ n'a pas conceptuel rapport à x() de GÈNES, ils sont mis en œuvre en tant que distincte des fonctions génériques. Par conséquent, la fin programmeur (en my_program.code) doit explicitement qualifier get-x() et dire le système qui get-x() il veut dire, en fait appel.

Il est vrai que cette approche explicite est plus clair et facilement généralisable à de multiples expédition et de l'héritage multiple. Mais à l'aide de (abuser) d'expédition pour résoudre les problèmes d'espace de noms est une option extrêmement utile de passage de message OO.

Personnellement, je pense que 98% de mon propre code est suffisamment exprimé à l'aide de répartition unique et unique héritage. J'utilise cette commodité d'utilisation de l'expédition pour la résolution d'espace de noms bien plus que ce que j'multiples pour l'expédition, de sorte que je suis réticent à l'abandonner.

Est-il possible de me faire le meilleur des deux mondes? Comment puis-je éviter la nécessité de qualifier explicitement mes appels de fonction dans un multi-paramètre de méthode?


Il semble que le consensus est que les

  • multimethods résoudre l'envoi problème, mais n'attaque pas le problème de l'espace de noms.
  • les fonctions qui sont conceptuellement différents doivent avoir des noms différents, et les utilisateurs devraient être devraient manuellement les qualifier.

Ensuite, je crois que, dans les cas où seul l'héritage de répartition unique est suffisante, de passage de message OO est plus pratique que les fonctions génériques.

Cela sonne comme il est ouvert de recherche. Si une langue à fournir un mécanisme pour multimethods qui peut également être utilisé pour la résolution d'espace de noms, ce serait une fonction souhaitée?

J'aime le concept de fonctions génériques, mais se sentent actuellement ils sont optimisés pour faire "des choses très dures pas si dur" au détriment de "faire les choses triviales légèrement ennuyeux". Étant donné que la majorité du code est trivial, je crois toujours que c'est un bon problème à résoudre.

21voto

Rainer Joswig Points 62532

Répartition dynamique et résolution d'espace de noms sont deux choses différentes. Dans de nombreux systèmes d'objets de classes sont également utilisés pour les espaces de noms. Également de noter que souvent les deux de la classe et de l'espace de noms sont liés à un fichier. De sorte que ces systèmes d'objets amalgame entre au moins trois choses:

  • les définitions de classe avec leurs fentes et les méthodes
  • l'espace de noms pour les identificateurs
  • l'unité de stockage de code source

Common Lisp et de son objet (système CLOS) fonctionne différemment:

  • les classes ne forment pas un espace de noms
  • générique de fonctions et de méthodes n'appartiennent pas à des catégories et ne sont donc pas définies à l'intérieur des classes
  • les fonctions génériques sont définis comme des fonctions de niveau supérieur et donc ne sont pas imbriqués ou local
  • les identificateurs de fonctions génériques sont des symboles
  • les symboles ont leur propre mécanisme d'espace de noms appelé paquets
  • les fonctions génériques sont "ouvertes". On peut ajouter ou supprimer des méthodes à tout moment
  • les fonctions génériques sont des objets de première classe
  • mathods sont des objets de première classe
  • les classes et les fonctions génériques sont également à ne pas confondre avec les fichiers. Vous pouvez définir plusieurs classes et plusieurs fonctions génériques dans un fichier ou dans autant de fichiers que vous le souhaitez. Vous pouvez également définir des classes et des méthodes d'exécuter du code (donc pas liée à des fichiers) ou quelque chose comme un REPL (read eval impression de boucle).

Style CLOS:

  • si une fonctionnalité besoins dynamiques de l'expédition et de la fonctionnalité est étroitement lié, puis utiliser une fonction générique avec différentes méthodes
  • si il ya beaucoup de différentes fonctionnalités, mais avec un nom commun, ne les mettez pas dans le même fonction générique. Créer différentes fonctions génériques.
  • générique de fonctions avec le même nom, mais dont le nom est dans différents packages sont différentes fonctions génériques.

Exemple:

(defpackage "ANIMAL" (:use "CL")) 
(in-package "ANIMAL")

(defclass animal () ())
(deflcass dog (animal) ())
(deflcass cat (animal) ()))

(defmethod bark ((an-animal dog)) (print 'woof))
(defmethod bark ((an-animal cat)) (print 'meow)) 

(bark (make-instance 'dog))
(bark (make-instance 'dog))

Notez que la classe ANIMAL , et le paquet ANIMAL ont le même nom. Mais ce n'est pas nécessaire pour. Les noms ne sont pas reliés de quelque façon. Le DEFMETHOD crée implicitement un correspondant de la fonction générique.

Si vous ajoutez un autre paquet (par exemple, GAME-ANIMALS), puis l' BARK de la fonction générique sera différent. À moins que ces paquets sont liées (par exemple, un package utilise les autres).

À partir d'un autre package (symbole de l'espace de noms en Common Lisp), on peut appeler ces:

(animal:bark some-animal)

(game-animal:bark some-game-animal)

Un symbole a la syntaxe

PACKAGE-NAME::SYMBOL-NAME

Si le paquet est le même que l'actuel paquet, il peut être omis.

  • ANIMAL::BARK désigne le symbole nommé BARK dans le package ANIMAL. Notez qu'il existe deux points.
  • AINMAL:BARK se réfère à la exporté symbole BARK dans le package ANIMAL. Notez qu'il n'est qu'un deux-points. L'exportation, l'importation et à l'aide de mécanismes définis pour les paquets et leurs symboles. Ainsi, ils sont indépendants de classes et de fonctions génériques, mais il peut être utilisé pour la structure de l'espace de noms pour les symboles de nommage de ceux-ci.

Le plus intéressant est quand multimethods sont effectivement utilisés dans les fonctions génériques:

(defmethod bite ((some-animal cat) (some-human human))
  ...)

(defmethod bite ((some-animal dog) (some-food bone))
  ...)

Ci-dessus utilise les classes CAT, HUMAN, DOG et BONE. Ce qui classe la fonction générique appartient-il? Quel serait l'espace de noms particulier ressemble?

Puisque les fonctions de répartition plus de tous les arguments, il ne fait pas de sens direct, de faire un amalgame entre la fonction générique avec un espace de noms particulier et d'en faire une définition dans une seule classe.

Motivation:

Les fonctions génériques ont été ajoutés dans les années 80 pour Lisp par les développeurs au Xerox PARC (pour le Commun des BOUCLES) et à la Symbolique de Nouvelles Saveurs. On voulait se débarrasser d'un appel supplémentaire mécanisme (message passing) et apporter de l'expédition ordinaire (de haut niveau) des fonctions. De nouvelles Saveurs, avait seul envoi, mais les fonctions génériques avec plusieurs arguments. La recherche en Commun des BOUCLES alors apporté de multiples expédition. De nouvelles Saveurs et de la Commune de BOUCLES ont été ensuite remplacé par le normalisée CLOS. Ces idées ont ensuite été porté à d'autres langues comme Dylan.

Depuis le code d'exemple dans la question, ne pas utiliser quoi que ce soit des fonctions génériques ont à offrir, il ressemble à un de quelque chose.

Lors de l'envoi, la transmission de message et de l'héritage simple est suffisante, alors les fonctions génériques peut ressembler à un pas en arrière. La raison pour cela est, comme mentionné, que l'on ne veut pas mettre toutes sortes de similaire nommé les fonctionnalités d'une fonction générique.

Lorsque

(defmethod bark ((some-animal dog)) ...)
(defmethod bark ((some-tree oak)) ...)

se ressemblent, ils sont deux conceptuellement différentes actions.

Mais en plus:

(defmethod bark ((some-animal dog) tone loudness duration)
   ...)

(defmethod bark ((some-tree oak)) ...)

Maintenant, tout à coup les listes de paramètres pour le même nom générique de la fonction est différente. Devraient-ils être autorisés à être une fonction générique? Si non, comment nous l'appelons BARK sur plusieurs objets dans une liste de choses avec les bons paramètres?

Dans la vraie du code Lisp fonctions génériques en général un aspect beaucoup plus compliqué avec plusieurs arguments obligatoires et facultatifs.

En Common Lisp fonctions génériques d'ailleurs ne pas avoir un seul type de méthode. Il existe différents types de méthodes et de diverses manières de les combiner. Il n'a de sens qu'à les combiner entre eux, lorsqu'ils appartiennent à une certaine fonction générique.

Puisque les fonctions sont également des objets de première classe, ils peuvent être passés autour, retour de fonctions et stockées dans des structures de données. À ce stade, la fonction générique de l'objet lui-même est important, pas le son nom plus.

Pour le cas simple où j'ai un objet qui a des coordonnées x et y et peut agir comme un point, je n'héritent pour les objets de la classe à partir d'un POINT classe (peut-être que certains mixin). Puis-je importer la GET-X et GET-Y symboles dans certains espaces de noms, si nécessaire.

Il y a d'autres langues qui sont les plus différents l'un de Lisp/CLOS et qui tentent(ed) à l'appui de multimethods:

Il semble y avoir beaucoup de tentatives pour l'ajouter à Java.

9voto

sanityinc Points 9156

Votre exemple de "Pourquoi multimethods ne fonctionne pas" suppose que vous pouvez définir deux noms identiques des fonctions génériques dans le même espace de noms du langage. Ce n'est généralement pas le cas; par exemple, Clojure multimethods appartiennent explicitement à un espace de noms, donc si vous avez deux telles fonctions génériques du même nom, vous devez préciser que vous utilisez.

En bref, les fonctions qui sont "conceptuellement différent" va toujours avoir des noms différents, ou de vivre dans des espaces de noms différents.

3voto

Vatine Points 8884

Les fonctions génériques doivent effectuer le même "verbe" pour toutes les classes de sa méthode est mise en œuvre pour les.

Chez les animaux/arbre "écorce" cas, l'animal-verbe est "effectuer une action sonore" et dans l'arbre de cas, eh bien, je suppose que c'est faire de l'environnement ou de bouclier.

L'anglais arrive à les appeler à la fois "l'écorce" est juste une linguistique de la co-incidence.

Si vous avez un cas où plusieurs types de GFs (fonctions génériques) ne devrait vraiment avoir le même nom, à l'aide des espaces de noms pour les séparer est (probablement) la bonne chose.

2voto

Asumu Takikawa Points 5157

OO passe-message ne résout pas, en général, le problème d'espacement de noms dont vous parlez. Les langages OO avec des systèmes de type structurel ne font pas la différence entre une méthode bark dans un Animal ou un Tree tant qu'ils ont le même type. C'est seulement parce que les langages OO populaires utilisent des systèmes de type nominal (par exemple, Java) que cela semble être le cas.

2voto

Clayton Stanley Points 2211

Parce que get-x() de XYZ n'a pas conceptuel rapport à x() de GÈNE, ils sont mis en œuvre en tant que distincte des fonctions génériques

Assurez-vous. Mais depuis leur arglist est le même (juste de passage de l'objet à la méthode), puis vous "pourrait" mettre en œuvre des méthodes différentes sur la même fonction générique.

La seule contrainte lors de l'ajout d'une méthode à une fonction générique, c'est que le arglist de la méthode correspond à la arglist de la fonction générique.

Plus généralement, les méthodes doivent avoir le même nombre de personnes nécessaires et les paramètres facultatifs et doivent être capables d'accepter les arguments correspondant à l'une &de repos ou &les principaux paramètres spécifiés par le générique fonction.

Il n'y a pas de contrainte que les fonctions doivent être conceptuellement liés. La plupart du temps, ils sont (remplacement d'une super-classe, etc.), mais ils ne doivent certainement pas être.

Bien que même cette contrainte (besoin de la même arglist) semble limiter à la fois. Si vous regardez Erlang, les fonctions ont arité, et vous pouvez définir plusieurs fonctions avec le même nom, qui ont différents arité (fonctions avec le même nom et les différents arglists). Et puis une sorte d'expédition prend soin d'appeler la bonne fonction. J'aime cette. Et en lisp, je pense que ce serait la carte pour avoir une fonction générique accepter les méthodes qui ont divers arglists. Peut-être que c'est quelque chose qui est configurable dans le MOP?

Bien que la lecture un peu plus ici, il semble que le mot-clé arguments pourraient permettre au programmeur de parvenir à avoir une fonction générique encapsuler des méthodes complètement différentes arité, en utilisant différentes touches dans des méthodes différentes pour varier le nombre de leurs arguments:

Une méthode peut "accepter" &key et &reste arguments définis dans son générique fonction par le fait d'avoir un &reste paramètre, en ayant le même &key paramètres, ou en spécifiant &allow-autres-clés avec &key. Un la méthode peut également spécifier &les principaux paramètres ne figurent pas dans le générique fonction du paramètre de la liste: lorsque le générique de la fonction est appelée, tous les &touche paramètre spécifié par la fonction générique en vigueur méthode sera accepté.

Notez également que cette sorte de flou, où les différentes méthodes stockées dans la fonction générique ne conceptuellement différentes choses, qui se passe derrière les scènes dans votre "arbre de l'écorce', 'chiens aboient' exemple. Lors de la définition de la classe de l'arbre, vous devez créer automatiquement un getter et setter pour la méthode de l'écorce de la fente. Lors de la définition de la classe chien, vous souhaitez définir une écorce de méthode sur le chien type qui ne fait les aboiements. Et ces deux méthodes sont stockés dans un #'écorce de la fonction générique.

Puisqu'ils sont tous deux inclus dans la même fonction générique, vous auriez du appeler exactement de la même manière:

(bark tree-obj) -> Returns a noun (the bark of the tree)
(bark dog-obj) -> Produces a verb (the dog barks)

Comme code:

CL-USER> 
(defclass tree ()
  ((bark :accessor bark :initarg :bark :initform 'cracked)))
#<STANDARD-CLASS TREE>
CL-USER> 
(symbol-function 'bark)
#<STANDARD-GENERIC-FUNCTION BARK (1)>
CL-USER> 
(defclass dog ()
  ())
#<STANDARD-CLASS DOG>
CL-USER> 
(defmethod bark ((obj dog))
  'rough)
#<STANDARD-METHOD BARK (DOG) {1005494691}>
CL-USER> 
(symbol-function 'bark)
#<STANDARD-GENERIC-FUNCTION BARK (2)>
CL-USER> 
(bark (make-instance 'tree))
CRACKED
CL-USER> 
(bark (make-instance 'dog))
ROUGH
CL-USER> 

J'ai tendance à favoriser ce genre de "dualité de la syntaxe", ou le flou de fonctionnalités, etc. Et je ne pense pas que toutes les méthodes sur une fonction générique doivent être similaires sur le plan conceptuel. C'est juste une ligne directrice de l'OMI. Si une linguistique de l'interaction dans la langue anglaise qui se passe (de l'écorce comme substantif et verbe), c'est agréable d'avoir un langage de programmation qui gère le cas normalement.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X