Y a-t-il un objectif spécifique pour les listes hétérogènes?

13

Issu d'un background C # et Java, je suis habitué à ce que mes listes soient homogènes, et cela a du sens pour moi. Quand j'ai commencé à prendre Lisp, j'ai remarqué que les listes peuvent être hétérogènes. Quand j'ai commencé à déconner avec le dynamicmot - clé en C #, j'ai remarqué que, depuis C # 4.0, il peut aussi y avoir des listes hétérogènes:

List<dynamic> heterogeneousList

Ma question est quel est le point? Il semble qu'une liste hétérogène aura beaucoup plus de temps lors du traitement et que si vous devez stocker différents types en un seul endroit, vous aurez peut-être besoin d'une structure de données différente. Ma naïveté est-elle en train de se révéler laide ou y a-t-il vraiment des moments où il est utile d'avoir une liste hétérogène?

Jetti
la source
1
Voulez-vous dire ... J'ai remarqué que les listes peuvent être hétérogènes ...?
World Engineer
Comment est List<dynamic>différent (pour votre question) de faire simplement List<object>?
Peter K.
@WorldEngineer oui, je le fais. J'ai mis à jour mon message. Merci!
Jetti
@PeterK. Je suppose que pour un usage quotidien, il n'y a aucune différence. Cependant, tous les types en C # ne dérivent pas de System.Object, il y aurait donc des cas extrêmes où il y a des différences.
Jetti

Réponses:

16

Le document Strongly Typed Heterogenous Collections par Oleg Kiselyov, Ralf Lämmel et Keean Schupke contient non seulement une implémentation de listes hétérogènes dans Haskell, mais aussi un exemple motivant de quand, pourquoi et comment vous utiliseriez les HLists. En particulier, ils l'utilisent pour accéder à la base de données vérifiée lors de la compilation en toute sécurité. (Pensez LINQ, en fait, le document auquel ils font référence est le document Haskell d'Erik Meijer et al qui a conduit à LINQ.)

Citant le paragraphe introductif du document HLists:

Voici une liste ouverte d'exemples typiques qui appellent à des collections hétérogènes:

  • Une table de symboles censée stocker des entrées de différents types est hétérogène. Il s'agit d'une carte finie, où le type de résultat dépend de la valeur de l'argument.
  • Un élément XML est de type hétérogène. En fait, les éléments XML sont des collections imbriquées qui sont contraintes par des expressions régulières et la propriété 1-ambiguïté.
  • Chaque ligne renvoyée par une requête SQL est une carte hétérogène des noms de colonne aux cellules. Le résultat d'une requête est un flux homogène de lignes hétérogènes.
  • L'ajout d'un système d'objet avancé à un langage fonctionnel nécessite des collections hétérogènes d'un type qui combinent des enregistrements extensibles avec un sous-typage et une interface d'énumération.

Notez que les exemples que vous avez donnés dans votre question ne sont vraiment pas des listes hétérogènes dans le sens où le mot est couramment utilisé. Ce sont des listes faiblement typées ou non typées . En fait, ce sont en fait des listes homogènes , puisque tous les éléments sont du même type: objectou dynamic. Vous êtes ensuite obligé d'effectuer des transtypages ou des instanceoftests non vérifiés ou quelque chose comme ça, afin de pouvoir réellement travailler de manière significative avec les éléments, ce qui les rend faiblement typés.

Jörg W Mittag
la source
Merci pour le lien et votre réponse. Vous faites valoir que les listes ne sont vraiment pas hétérogènes mais faiblement typées. J'ai hâte de lire ce document (probablement demain, j'ai obtenu un mi-mandat ce soir :))
Jetti
5

Les conteneurs courts et hétérogènes à long terme échangent les performances d'exécution pour plus de flexibilité. Si vous voulez avoir une «liste de choses» sans tenir compte du type particulier de choses, l'hétérogénéité est la voie à suivre. Les lisps sont typiquement dynamiquement typés, et presque tout est une contre-liste de valeurs encadrées de toute façon, donc le plus petit succès de performance est attendu. Dans le monde Lisp, la productivité du programmeur est considérée comme plus importante que les performances d'exécution.

Dans un langage typé dynamiquement, les conteneurs homogènes auraient en fait un léger surcoût par rapport aux conteneurs hétérogènes, car tous les éléments ajoutés devraient être vérifiés par type.

Votre intuition sur le choix d'une meilleure structure de données est à propos. De manière générale, plus vous pouvez mettre en place de contrats sur votre code, plus vous en savez sur son fonctionnement, et plus fiable, maintenable, etc. il devient. Cependant, parfois, vous voulez vraiment un conteneur hétérogène, et vous devriez pouvoir en avoir un si vous en avez besoin.

Jon Purdy
la source
1
"Cependant, parfois vous voulez vraiment un conteneur hétérogène, et vous devriez pouvoir en avoir un si vous en avez besoin." - Mais pourquoi? Telle est ma question. Pourquoi auriez-vous jamais besoin de mélanger un tas de données dans une liste aléatoire?
Jetti
@Jetti: Supposons que vous disposez d'une liste de paramètres saisis par l'utilisateur de différents types. Vous pouvez créer une interface IUserSettinget l'implémenter plusieurs fois, ou créer un générique UserSetting<T>, mais l'un des problèmes du typage statique est que vous définissez une interface avant de savoir précisément comment elle va être utilisée. Les choses que vous faites avec les paramètres entiers sont probablement très différentes de celles que vous faites avec les paramètres de chaîne, alors quelles opérations sont logiques à mettre dans une interface commune? Jusqu'à ce que vous en soyez certain, il est préférable d'utiliser judicieusement la frappe dynamique et de la concrétiser plus tard.
Jon Purdy
Voir c'est là que je rencontre des problèmes. Pour moi, cela ressemble à un mauvais design, faire quelque chose avant de savoir ce qu'il fera / sera utilisé. De plus, dans ce cas, vous pouvez créer l'interface avec une valeur de retour d'objet. Fait la même chose que la liste hétérogène mais est plus clair et plus facile à corriger une fois que vous savez avec certitude quels sont les types utilisés dans l'interface.
Jetti
@Jetti: C'est essentiellement le même problème, cependant - une classe de base universelle ne devrait pas exister en premier lieu parce que, quelles que soient les opérations qu'elle définit, il y aura un type pour lequel ces opérations n'auront pas de sens. Mais si C # facilite l'utilisation d'un objectplutôt que d'un dynamic, alors bien sûr, utilisez le premier.
Jon Purdy
1
@Jetti: C'est à cela que sert le polymorphisme. La liste contient un certain nombre d'objets "hétérogènes" même s'ils peuvent être des sous-classes d'une seule superclasse. D'un point de vue Java, vous pouvez obtenir les bonnes définitions de classe (ou d'interface). Pour d'autres langages (LISP, Python, etc.), il n'y a aucun avantage à obtenir toutes les bonnes déclarations, car il n'y a pas de différence d'implémentation pratique.
S.Lott
2

Dans les langages fonctionnels (comme lisp), vous utilisez la correspondance de modèles pour déterminer ce qui arrive à un élément particulier dans une liste. L'équivalent en C # serait une chaîne d'instructions if ... elseif qui vérifient le type d'un élément et effectuent une opération basée sur cela. Il va sans dire que la mise en correspondance de modèles fonctionnels est plus efficace que la vérification du type d'exécution.

L'utilisation du polymorphisme serait une correspondance plus étroite avec la correspondance de motifs. C'est-à-dire que les objets d'une liste correspondent à une interface particulière et appellent une fonction sur cette interface pour chaque objet. Une autre alternative serait de fournir une série de méthodes surchargées qui prennent un type d'objet spécifique comme paramètre. La méthode par défaut prenant Object comme paramètre.

public class ListVisitor
{
  public void DoSomething(IEnumerable<dynamic> list)
  {
    foreach(dynamic obj in list)
    {
       DoSomething(obj);
    }
  }

  public void DoSomething(SomeClass obj)
  {
    //do something with SomeClass
  }

  public void DoSomething(AnotherClass obj)
  {
    //do something with AnotherClass
  }

  public void DoSomething(Object obj)
  {
    //do something with everything els
  }
}

Cette approche fournit une approximation de la correspondance de motifs Lisp. Le modèle de visiteur (tel qu'implémenté ici, est un excellent exemple d'utilisation de listes hétérogènes). Un autre exemple serait pour la répartition des messages où, il y a des écouteurs pour certains messages dans une file d'attente prioritaire et en utilisant la chaîne de responsabilité, le répartiteur transmet le message et le premier gestionnaire qui correspond au message le gère.

Le revers est d'avertir tous ceux qui s'inscrivent pour un message (par exemple, le modèle d'agrégateur d'événements couramment utilisé pour le couplage lâche des ViewModels dans le modèle MVVM). J'utilise la construction suivante

IDictionary<Type, List<Object>>

La seule façon d'ajouter au dictionnaire est une fonction

Register<T>(Action<T> handler)

(et l'objet est en fait une référence faible au gestionnaire passé dans). Donc, ici, je dois utiliser List <Object> car au moment de la compilation, je ne sais pas quel sera le type fermé. Au Runtime cependant, je peux imposer que ce sera ce type qui sera la clé du dictionnaire. Quand je veux déclencher l'événement que j'appelle

Send<T>(T message)

et encore je résous la liste. Il n'y a aucun avantage à utiliser List <dynamic> car je dois quand même le caster. Donc, comme vous le voyez, il y a des avantages aux deux approches. Si vous allez distribuer dynamiquement un objet à l'aide de la surcharge de méthode, dynamique est le moyen de le faire. Si vous êtes forcé de lancer quoi que ce soit, vous pourriez aussi bien utiliser Object.

Michael Brown
la source
Avec la correspondance de modèles, les cas sont (généralement - au moins en ML et Haskell) gravés dans la pierre par la déclaration du type de données correspondant. Les listes contenant de tels types ne sont pas non plus hétérogènes.
Je ne suis pas sûr de ML et Haskell mais Erlang peut faire face à tout ce qui signifie, si vous avez atteint ici parce qu'aucun autre match n'était satisfait, faites-le.
Michael Brown
@MikeBrown - C'est bien mais cela ne couvre pas POURQUOI on utiliserait des listes hétérogènes et ne fonctionnerait pas toujours avec List <dynamic>
Jetti
4
En C #, les surcharges sont résolues au moment de la compilation . Ainsi, votre exemple de code sera toujours appeler DoSomething(Object)(au moins lors de l' utilisation objectdans la foreachboucle, dynamicest tout autre chose).
Heinzi
@Heinzi, vous avez raison ... J'ai sommeil aujourd'hui: P corrigé
Michael Brown
0

Vous avez raison de dire que l'hétérogénéité entraîne une surcharge d'exécution, mais plus important encore, elle affaiblit les garanties de compilation fournies par le vérificateur de typage. Néanmoins, il existe certains problèmes où les alternatives sont encore plus coûteuses.

D'après mon expérience, en traitant des octets bruts via des fichiers, des sockets réseau, etc., vous rencontrez souvent de tels problèmes.

Pour donner un exemple réel, considérons un système de calcul distribué utilisant des futures . Un travailleur sur un nœud individuel peut générer du travail de tout type sérialisable, ce qui donne un avenir de ce type. Dans les coulisses, le système envoie le travail à un pair, puis enregistre un enregistrement associant cette unité de travail à l'avenir particulier qui doit être rempli une fois que la réponse à ce travail revient.

Où ces enregistrements peuvent-ils être conservés? Intuitivement, ce que vous voulez est quelque chose comme un Dictionary<WorkId, Future<TValue>>, mais cela vous limite à gérer un seul type de futures dans tout le système. Le type le plus approprié est Dictionary<WorkId, Future<dynamic>>, car l'ouvrier peut lancer le type approprié lorsqu'il force l'avenir.

Remarque : Cet exemple vient du monde Haskell où nous n'avons pas de sous-typage. Je ne serais pas surpris s'il y ait une solution plus idiomatique pour cet exemple particulier en C #, mais j'espère qu'elle est toujours illustrative.

Adam
la source
0

ISTR que Lisp n'a pas de structures de données autres qu'une liste, donc si vous avez besoin de n'importe quel type d'objet de données agrégé, cela devra être une liste hétérogène. Comme d'autres l'ont souligné, ils sont également utiles pour sérialiser des données pour la transmission ou le stockage. Une fonctionnalité intéressante est qu'ils sont également ouverts, vous pouvez donc les utiliser dans un système basé sur une analogie de tuyaux et de filtres et avoir des étapes de traitement successives pour augmenter ou corriger les données sans nécessiter un objet de données fixe ou une topologie de flux de travail .

TMN
la source