Pourquoi les langages POO statiques forts traditionnels empêchent-ils d'hériter des primitives?


53

Pourquoi est-ce correct et surtout attendu:

abstract type Shape
{
   abstract number Area();
}

concrete type Triangle : Shape
{
   concrete number Area()
   {
      //...
   }
}

... alors que ce n'est pas correct et que personne ne se plaint:

concrete type Name : string
{
}

concrete type Index : int
{
}

concrete type Quantity : int
{
}

Ma motivation est de maximiser l'utilisation du système de types pour la vérification de l'exactitude à la compilation.

PS: oui, j'ai lu ceci et l'emballage est une solution de rechange .


1
Les commentaires ne sont pas pour une discussion prolongée; cette conversation a été déplacée pour discuter .
maple_shaft

J'ai eu une motivation similaire dans cette question , vous pourriez trouver cela intéressant.
default.kramer

J'allais ajouter une réponse confirmant l'idée "vous ne voulez pas d'héritage", et cet habillage est très puissant. Il vous permet notamment de choisir le type de transtypage implicite ou explicite (ou d'échecs) souhaité, notamment avec les optimisations JIT suggérant que vous obtenez presque toujours la même performance, mais vous avez un lien avec cette réponse :-) J'ajouterais que ce serait bien si les langues ajoutaient des fonctionnalités pour réduire le code standard nécessaires au transfert des propriétés / méthodes, surtout s'il n'y a qu'une seule valeur.
Mark Hurd

Réponses:


82

Je suppose que vous pensez à des langages comme Java et C #?

Dans ces langues, les primitives (comme int) sont fondamentalement un compromis pour la performance. Ils ne prennent pas en charge toutes les fonctionnalités des objets, mais ils sont plus rapides et nécessitent moins de temps système.

Pour que les objets prennent en charge l'héritage, chaque instance doit "savoir" au moment de l'exécution quelle classe elle est une instance. Sinon, les méthodes remplacées ne peuvent pas être résolues au moment de l'exécution. Pour les objets, cela signifie que les données d'instance sont stockées en mémoire avec un pointeur sur l'objet de classe. Si de telles informations devaient également être stockées avec des valeurs primitives, les besoins en mémoire augmenteraient. Une valeur entière de 16 bits nécessiterait ses 16 bits pour la valeur et en plus une mémoire de 32 ou 64 bits pour un pointeur sur sa classe.

Outre la surcharge de mémoire, vous devriez également être en mesure de remplacer les opérations courantes sur les primitives telles que les opérateurs arithmétiques. Sans sous-typage, les opérateurs similaires +peuvent être compilés en une simple instruction de code machine. Si cela pouvait être remplacé, vous auriez besoin de résoudre les méthodes au moment de l'exécution, opération beaucoup plus coûteuse. (Vous savez peut-être que C # prend en charge la surcharge des opérateurs, mais ce n'est pas la même chose. La surcharge des opérateurs est résolue au moment de la compilation. Il n'y a donc pas de pénalité d'exécution par défaut.)

Les chaînes ne sont pas des primitives, mais elles restent "spéciales" dans la façon dont elles sont représentées en mémoire. Par exemple, ils sont "internés", ce qui signifie que deux chaînes de caractères identiques peuvent être optimisées pour la même référence. Cela ne serait pas possible (ou du moins beaucoup moins efficace) si les instances de chaîne devaient également garder une trace de la classe.

Ce que vous décrivez serait certes utile, mais sa prise en charge nécessiterait une surcharge de performances pour chaque utilisation de primitives et de chaînes, même lorsqu'elles ne tirent pas parti de l'héritage.

Le langage Smalltalk fait (je crois) d'entiers permet le sous. Mais lors de la conception de Java, Smalltalk était considéré comme trop lent et le fait de tout avoir comme objet était considéré comme l'une des principales raisons. Java a sacrifié de l'élégance et de la pureté conceptuelle pour obtenir de meilleures performances.


12
@ Den: stringest scellé car il est conçu pour se comporter de manière immuable. Si l'on pouvait hériter d'une chaîne, il serait possible de créer des chaînes mutables, ce qui la rendrait vraiment sujette aux erreurs. Des tonnes de code, y compris le framework .NET lui-même, reposent sur des chaînes sans effets secondaires. Voir aussi ici, vous dit la même chose: quora.com/Why-String-class-in-C-is-a-sealed-class
Doc Brown

5
@ DocBrown C'est aussi la raison Stringest marquée finalen Java aussi.
Dev

47
"Lors de la conception de Java, Smalltalk était considéré comme trop lent [...]. Java a sacrifié une certaine élégance et une pureté conceptuelle pour obtenir de meilleures performances." - Paradoxalement, bien entendu, Java n’a pas obtenu ces performances tant que Sun n’a pas racheté une société Smalltalk pour pouvoir accéder à la technologie Smalltalk VM, car la machine virtuelle Java de Sun était lente, et elle a lancé HotSpot JVM, une VM légèrement modifiée.
Jörg W Mittag

3
@underscore_d: la réponse à laquelle vous avez lié très explicitement déclare que C♯ n'a pas de types primitifs. Certes, certaines plates-formes pour lesquelles il existe une implémentation de C♯ peuvent ou non avoir des types primitifs, mais cela ne signifie pas que C♯ possède des types primitifs. Par exemple, il y a une implémentation de Ruby pour la CLI, et la CLI a des types primitifs, mais cela ne signifie pas que Ruby a des types primitifs. L'implémentation peut choisir ou non d'implémenter les types de valeur en les mappant aux types primitifs de la plate-forme, mais il s'agit d'un détail d'implémentation interne privé et ne faisant pas partie des spécifications.
Jörg W Mittag

10
Tout est question d'abstraction. Nous devons garder la tête claire, sinon nous nous retrouvons avec un non-sens. Par exemple: C♯ est implémenté sur .NET. .NET est implémenté sur Windows NT. Windows NT est implémenté sur x86. x86 est mis en œuvre sur le dioxyde de silicone. SiO₂ est juste du sable. Alors, un stringC♯ n'est que du sable? Non, bien sûr que non, un stringen C♯ est ce que dit la spécification C says. La façon dont il est mis en œuvre est sans importance. Une implémentation native de C♯ implémenterait des chaînes sous forme de tableaux d'octets, une implémentation ECMAScript les mapperait vers Strings ECMAScript , etc.
Jörg W Mittag

20

Ce que propose un langage n’est pas un sous-classement, mais un sous-typage . Par exemple, Ada vous permet de créer des types dérivés ou des sous - types . La section Ada Programming / Type System vaut la peine d’être lue pour comprendre tous les détails. Vous pouvez restreindre la plage de valeurs, ce que vous souhaitez la plupart du temps:

 type Angle is range -10 .. 10;
 type Hours is range 0 .. 23; 

Vous pouvez utiliser les deux types sous forme d'entiers si vous les convertissez explicitement. Notez également que vous ne pouvez pas utiliser l'un à la place de l'autre, même lorsque les plages sont structurellement équivalentes (les types sont vérifiés par leur nom).

 type Reference is Integer;
 type Count is Integer;

Les types ci-dessus sont incompatibles, même s'ils représentent la même plage de valeurs.

(Mais vous pouvez utiliser Unchecked_Conversion; ne dites pas aux gens que je vous ai dit ça)


2
En fait, je pense que c'est plus une question de sémantique. Utiliser une quantité où un indice est attendu provoquerait alors, espérons-le, une erreur de temps de compilation
Marjan Venema

@MarjanVenema C'est le cas, et cela est fait exprès pour intercepter les erreurs de logique.
Coredump

Mon point était que pas tous les cas où vous voulez la sémantique, vous auriez besoin des plages. Vous auriez alors type Index is -MAXINT..MAXINT;ce qui en quelque sorte ne fait rien pour moi comme tous les entiers seraient valables? Alors, quel genre d'erreur pourrais-je obtenir en passant un Angle à un Index si tout ce qui est vérifié est la plage?
Marjan Venema

1
@MarjanVenema Dans son deuxième exemple, les deux types sont des sous-types d'Integer. Toutefois, si vous déclarez une fonction qui accepte un compte, vous ne pouvez pas transmettre de référence car la vérification du type est basée sur l' équivalence de nom , contrairement à "tout ce qui est vérifié est la plage". Cela ne se limite pas aux entiers, vous pouvez utiliser des types énumérés ou des enregistrements. ( archive.adaic.com/standards/83rat/html/ratl-04-03.html )
coredump

1
@Marjan Un exemple intéressant de pourquoi les types de marquage peuvent être assez puissants se trouve dans la série d' Eric Lippert sur l'implémentation de Zorg dans OCaml . Cela permet au compilateur d'attraper de nombreux bogues - par contre, si vous autorisez à convertir implicitement des types, cela semble rendre la fonctionnalité inutile. Cela n'a pas de sens sémantique de pouvoir affecter un type PersonAge à un type PersonId parce qu'ils se trouvent tous les deux avoir le même type sous-jacent.
Voo

16

Je pense que cela pourrait très bien être une question X / Y. Points saillants, de la question ...

Ma motivation est de maximiser l'utilisation du système de types pour la vérification de l'exactitude à la compilation.

... et de votre commentaire en précisant:

Je ne veux pas pouvoir en substituer un à un autre implicitement.

Excusez-moi si quelque chose me manque, mais ... Si ce sont vos objectifs, alors pourquoi parlez-vous d'héritage? La substituabilité implicite est ... comme ... tout son contenu. Vous savez, le principe de substitution de Liskov?

Ce que vous semblez vouloir, en réalité, est le concept de "typedef fort" - par lequel quelque chose "est" par exemple un inten termes de gamme et de représentation mais ne peut pas être substitué dans des contextes qui attendent un intet vice-versa. Je suggérerais de rechercher des informations sur ce terme et quelle que soit la langue choisie par votre langue. Encore une fois, c'est à peu près littéralement le contraire de l'héritage.

Et pour ceux qui n'aimeraient peut-être pas une réponse X / Y, je pense que le titre pourrait encore être rendu compte du LSP. Les types primitifs sont primitifs parce qu'ils font quelque chose de très simple, et c'est tout ce qu'ils font . Leur permettre d'être hérités et ainsi rendre infinis leurs effets possibles conduirait au mieux à une grande surprise et au pire à une violation fatale. Si je peux présumer avec optimisme que Thales Pereira ne voudra pas que je cite ce commentaire phénoménal:

Il y a aussi le problème suivant: si quelqu'un était capable d'hériter de Int, vous auriez un code innocent comme "int x = y + 2" (où Y est la classe dérivée) qui écrit maintenant un journal dans la base de données, ouvre une URL et ressuscite en quelque sorte Elvis. Les types primitifs sont supposés être sûrs et avoir un comportement plus ou moins garanti et bien défini.

Si quelqu'un voit un type primitif, dans un langage sain d'esprit, il présume à juste titre qu'il fera toujours son petit geste, très bien, sans surprises. Les types primitifs n'ont pas de déclaration de classe disponible indiquant si ils peuvent ou non être hérités et dont les méthodes sont remplacées. S'ils l'étaient, ce serait vraiment très surprenant (et de briser totalement la compatibilité en amont, mais je suis conscient que c'est une réponse en arrière à «pourquoi X n'a-t-il pas été conçu avec Y»).

... bien que, comme Mooing Duck l’a souligné en réponse, les langages autorisant la surcharge des opérateurs permettent à l’utilisateur de se confondre dans une mesure similaire ou égale s’ils le souhaitent vraiment, il est donc douteux que ce dernier argument soit valable. Et je vais arrêter de résumer les commentaires des autres maintenant, heh.


4

Afin de permettre l'héritage avec l'envoi virtuel 8 (ce qui est souvent considéré comme très souhaitable dans la conception d'applications), il est nécessaire de disposer d'informations de type à l'exécution. Pour chaque objet, certaines données concernant le type de l'objet doivent être stockées. Une primitive, par définition, manque de cette information.

Il existe deux langages OOP grand public (gérés, exécutés sur une machine virtuelle) qui comportent des primitives: C # et Java. Beaucoup d'autres langues n'ont pas de primitives en premier lieu, ou utilisent un raisonnement similaire pour les autoriser / les utiliser.

Les primitives sont un compromis pour la performance. Pour chaque objet, vous avez besoin d’espace pour son en-tête (En Java, généralement 2 * 8 octets sur les ordinateurs virtuels 64 bits), de ses champs et d’un remplissage éventuel (En Hotspot, chaque objet occupe un nombre d'octets multiple de 8). Ainsi, un intobjet as aurait besoin d’au moins 24 octets de mémoire, au lieu de 4 octets (en Java).

Ainsi, des types primitifs ont été ajoutés pour améliorer les performances. Ils facilitent beaucoup de choses. Que a + bsignifie si les deux sont des sous-types de int? Une sorte de dispathcing doit être ajouté pour choisir le bon ajout. Cela signifie une répartition virtuelle. Avoir la possibilité d'utiliser un opcode très simple pour l'ajout est beaucoup plus rapide et permet des optimisations au moment de la compilation.

Stringest un autre cas. En Java et en C #, Stringc’est un objet. Mais en C #, il est scellé et en Java, il est définitif. Cela parce que les bibliothèques standard Java et C # exigent que Strings soit immuable, et les sous-classer briserait cette immuabilité.

Dans le cas de Java, la VM peut (et fait) interner des chaînes et les "mettre en pool", ce qui permet de meilleures performances. Cela ne fonctionne que lorsque les chaînes sont vraiment immuables.

De plus, il est rarement nécessaire de sous-classer les types primitifs. Tant que les primitives ne peuvent pas être sous-classées, les maths nous en disent beaucoup. Par exemple, nous pouvons être sûrs que l'addition est commutative et associative. C’est quelque chose que la définition mathématique d’entiers nous dit. De plus, on peut facilement prédier les invariants sur des boucles par induction dans de nombreux cas. Si nous autorisons le sous-classement de int, nous perdons les outils fournis par les mathématiques, car nous ne pouvons plus garantir que certaines propriétés sont valables. Ainsi, je dirais que la possibilité de ne pas pouvoir sous-classer les types primitifs est en fait une bonne chose. Moins de choses peuvent fuir, plus un compilateur peut souvent prouver qu'il est autorisé à faire certaines optimisations.


1
Cette réponse est abys ... étroite. to allow inheritance, one needs runtime type information.Faux. For every object, some data regarding the type of the object has to be stored.Faux. There are two mainstream OOP languages that feature primitives: C# and Java.Quoi, le C ++ n'est-il pas courant maintenant? Je vais l'utiliser comme réfutation, car les informations de type à l'exécution sont un terme C ++. Ce n'est absolument pas nécessaire à moins d'utiliser dynamic_castou typeid. Et même si les RTTI sont activés, l'héritage ne consomme de l'espace que si une classe a des virtualméthodes auxquelles une table de méthodes par classe doit être pointée par instance
underscore_d

1
L'héritage en C ++ fonctionne de manière très différente de celle des langages exécutés sur une machine virtuelle. l'envoi virtuel nécessite RTTI, quelque chose qui ne faisait pas initialement partie de C ++. L'héritage sans envoi virtuel est très limité et je ne sais même pas si vous devriez le comparer à l'héritage avec l'envoi virtuel. De plus, la notion d'objet est très différente en C ++, alors qu'elle l'est en C # ou en Java. Vous avez raison, il y a des choses que je pourrais dire mieux, mais entrer dans tous les points assez compliqués conduit rapidement à devoir écrire un livre sur la conception linguistique.
Polygnome

3
En outre, "l'envoi virtuel requiert RTTI" en C ++ n'est pas le cas. Encore une fois, seulement dynamic_castet l' typeinfoexiger. La répartition virtuelle est pratiquement mise en œuvre à l'aide d'un pointeur sur la table vtable pour la classe concrète de l'objet, permettant ainsi d'appeler les fonctions appropriées, mais sans exiger les détails de type et de relation inhérents à RTTI. Tout ce que le compilateur a besoin de savoir, c'est si la classe d'un objet est polymorphe et, dans l'affirmative, quel est le vptr de l'instance. On peut trivialement compiler des classes virtuellement expédiées avec -fno-rtti.
underscore_d

2
C'est en fait l'inverse, RTTI nécessite une répartition virtuelle. Littéralement, C ++ n'autorise pas dynamic_castles classes sans envoi virtuel. La raison de la mise en œuvre est que RTTI est généralement implémenté en tant que membre caché d'une table virtuelle.
MSalters

1
@MilesRout C ++ a tout ce dont un langage a besoin pour la programmation orientée objet, du moins les normes un peu plus récentes. On pourrait soutenir que les anciennes normes C ++ ne disposent pas de certains éléments nécessaires à un langage POO, mais même cela ne va pas. C ++ n'est pas un langage OOP de haut niveau , car il permet un contrôle plus direct et de bas niveau sur certaines choses, mais il permet néanmoins la POO. (Niveau élevé / niveau faible ici en termes d' abstraction , d'autres langages comme ceux gérés abstruisent plus le système que le C ++, d'où leur abstraction est plus élevée).
Polygnome

4

Dans les langages POO statiques forts classiques, le sous-typage est principalement considéré comme un moyen d'étendre un type et de remplacer les méthodes actuelles du type.

Pour ce faire, les «objets» contiennent un pointeur sur leur type. Il s'agit d'une surcharge: le code dans une méthode qui utilise une Shapeinstance doit d'abord accéder aux informations de type de cette instance avant de connaître la Area()méthode correcte à appeler.

Une primitive a tendance à n'autoriser que des opérations pouvant être traduites en instructions informatiques simples et ne comportant aucune information de type. Rendre un nombre entier plus lent afin que quelqu'un puisse sous-classer cette classe était assez peu attrayant pour empêcher toute langue qui le deviendrait de devenir la norme.

Donc, la réponse à:

Pourquoi les langages POO statiques forts traditionnels empêchent-ils d'hériter des primitives?

Est:

  • Il y avait peu de demande
  • Et cela aurait rendu la langue trop lente
  • Le sous-typage était principalement considéré comme un moyen d'étendre un type, plutôt que comme un moyen d'améliorer la vérification de type statique (définie par l'utilisateur).

Cependant, nous commençons à avoir des langages qui permettent une vérification statique basée sur des propriétés de variables autres que 'type', par exemple F # a "dimension" et "unité" de sorte que vous ne pouvez pas, par exemple, ajouter une longueur à une zone. .

Il existe également des langages qui autorisent les «types définis par l'utilisateur» qui ne changent pas (ni n'échangent) ce qu'un type fait, mais aident simplement à la vérification de type statique; voir la réponse de coredump.


Les unités de mesure F # sont une fonctionnalité intéressante, bien que malheureusement mal nommée. En outre, il s'agit uniquement de la compilation, ce qui n'est donc pas très utile, par exemple lorsque vous utilisez un paquet NuGet compilé. Bonne direction, cependant.
Den

Il est peut-être intéressant de noter que "dimension" n'est pas "une propriété autre que" type "", c'est simplement un type de type plus riche que celui auquel vous êtes habitué.
dimanche

3

Je ne suis pas sûr d'oublier quelque chose, mais la réponse est assez simple:

  1. La définition des primitives est la suivante: les valeurs primitives ne sont pas des objets, les types primitifs ne sont pas des types d'objets, les primitives ne font pas partie du système d'objets.
  2. L'héritage est une caractéristique du système d'objet.
  3. Ainsi, les primitives ne peuvent pas prendre part à l'héritage.

Notez qu'il n'existe en réalité que deux langages POO statiques forts qui ont même des primitives, AFAIK: Java et C ++. (En fait, je ne suis même pas sûr de ce dernier, je ne connais pas grand chose au C ++, et ce que j'ai trouvé en cherchant était déroutant.)

En C ++, les primitives sont essentiellement un héritage hérité (jeu de mots) de C. Elles ne participent donc pas au système d'objets (et donc à l'héritage) car C n'a ni système d'objet ni héritage.

En Java, les primitives résultent d’une tentative erronée d’améliorer les performances. Les primitives sont également les seuls types de valeur du système. En fait, il est impossible d'écrire des types de valeur en Java et il est impossible que les objets soient des types de valeur. Donc, mis à part le fait que les primitives ne participent pas au système d'objet et que l'idée "d'héritage" n'a même pas de sens, même si vous pouviez en hériter, vous ne pourriez pas conserver le " valeur-ness ". Ceci est différent de par exemple C♯ qui n'ont les types de valeurs ( S), qui sont tout de même des objets.struct

Une autre chose est que ne pas être en mesure d'hériter n'est en réalité pas unique aux primitives. Dans C♯, structs hérite implicitement de System.Objectet peut implémenter interfaces, mais ils ne peuvent ni hériter ni hérité de classes ou de structs. En outre, sealed classes ne peut pas être hérité de. En Java, final classil ne peut pas en être hérité.

tl; dr :

Pourquoi les langages POO statiques forts traditionnels empêchent-ils d'hériter des primitives?

  1. les primitives ne font pas partie du système d'objet (par définition, si elles l'étaient, elles ne seraient pas primitives), l'idée d'héritage est liée au système d'objet, ergo l'héritage primitif est une contradiction dans les termes
  2. les primitives ne sont pas uniques, beaucoup d'autres types ne peuvent pas non plus être hérités ( finalou sealeden Java ou C♯, structs en C, case classes en Scala)

3
Euh ... Je sais que c'est prononcé "C Sharp", mais, euh
M. Lister

Je pense que vous vous trompez assez du côté C ++. Ce n'est pas du tout un langage OO pur. Les méthodes de classe par défaut ne le sont pas virtual, ce qui signifie qu'elles n'obéissent pas à LSP. Eg std::stringn'est pas un primitif, mais cela se comporte plutôt comme une valeur supplémentaire. Une telle sémantique de valeur est assez courante, l’ensemble de la partie STL de C ++ l’assume.
MSalters

2
"En Java, les primitives résultent d'une tentative erronée d'améliorer les performances." Je pense que vous n'avez aucune idée de l'ampleur de l'impact négatif sur la performance de la mise en œuvre de primitives en tant que types d'objet extensibles par l'utilisateur. Cette décision en java est à la fois délibérée et bien fondée. Imaginez juste avoir à allouer de la mémoire pour chaque intutilisation. Chaque allocation prend environ 100 nbs plus les frais généraux du ramassage des ordures. Comparez cela avec le seul cycle de processeur consommé en ajoutant deux primitives int. Vos codes java ramperaient si les concepteurs de la langue en avaient décidé autrement.
cmaster

1
@cmaster: Scala n'a pas de primitives et ses performances numériques sont exactement les mêmes que celles de Java. Parce que, eh bien, il compile des entiers dans les primitives JVM int, de sorte qu’ils fonctionnent exactement de la même manière. (Scala-native les compile dans des registres machine primitifs, Scala.js les compile dans des ECMAScript Numbers primitifs .) Ruby n'a pas de primitives, mais YARV et Rubinius compilent des entiers en machines primitives, JRuby les compile en longs primitives JVM . Pratiquement toutes les implémentations Lisp, Smalltalk ou Ruby utilisent des primitives dans la VM . C'est là que l'optimisation des performances…
Jörg W Mittag

1
… Appartient: dans le compilateur, pas dans la langue.
Jörg W Mittag

2

Joshua Bloch dans «Effective Java» recommande de concevoir explicitement pour l'héritage ou l'interdire. Les classes primitives ne sont pas conçues pour l'héritage, car elles sont conçues pour être immuables et permettre un héritage pourrait changer cela dans les sous-classes, brisant ainsi le principe de Liskov et ce serait une source de nombreux bugs.

Quoi qu'il en soit, pourquoi est- ce une solution de contournement? Vous devriez vraiment préférer la composition à l'héritage. Si la raison en est la performance, vous avez un point et la réponse à votre question est qu'il n'est pas possible de mettre toutes les fonctionnalités en Java, car il faut du temps pour analyser tous les aspects de l'ajout d'une fonctionnalité. Par exemple, Java n'avait pas de génériques avant la 1.5.

Si vous avez beaucoup de patience, vous avez de la chance, car il est prévu d'ajouter des classes de valeur à Java, ce qui vous permettra de créer vos classes de valeur, ce qui vous aidera à augmenter les performances tout en vous offrant davantage de flexibilité.


2

Au niveau abstrait, vous pouvez inclure tout ce que vous voulez dans la langue que vous concevez.

Au niveau de la mise en œuvre, il est inévitable que certaines de ces choses soient plus simples à mettre en œuvre, que certaines soient compliquées, que certaines soient rapides, que certaines soient forcément plus lentes, etc. Pour en tenir compte, les concepteurs doivent souvent prendre des décisions difficiles et faire des compromis.

Au niveau de la mise en œuvre, l’un des moyens les plus rapides pour accéder à une variable est de trouver son adresse et de charger le contenu de cette adresse. Il existe des instructions spécifiques dans la plupart des CPU pour le chargement des données à partir d’adresses et ces instructions ont généralement besoin de savoir combien d’octets elles doivent charger (un, deux, quatre, huit, etc.) et où placer les données qu’elles chargent (registre unique, registre). paire, registre étendu, autre mémoire, etc.). En connaissant la taille d'une variable, le compilateur peut savoir exactement quelle instruction émettre pour les utilisations de cette variable. En ignorant la taille d'une variable, le compilateur devrait recourir à quelque chose de plus compliqué et probablement plus lent.

Au niveau abstrait, le point de sous-typage est de pouvoir utiliser des instances d'un type où un type égal ou plus général est attendu. En d'autres termes, il est possible d'écrire du code qui attend un objet d'un type particulier ou quoi que ce soit de plus dérivé, sans savoir à l'avance ce que ce serait exactement. Et, de toute évidence, étant donné que plus de types dérivés peuvent ajouter plus de membres de données, un type dérivé n’a pas nécessairement les mêmes besoins en mémoire que ses types de base.

Au niveau de la mise en œuvre, il n’existe aucun moyen simple pour une variable d’une taille prédéterminée de contenir une instance de taille inconnue et d’y accéder de la manière que vous appelleriez normalement efficace. Mais il existe un moyen de déplacer un peu les choses et d'utiliser une variable non pour stocker l'objet, mais pour identifier l'objet et laisser cet objet être stocké ailleurs. De cette façon, il s’agit d’une référence (par exemple une adresse de mémoire) - un niveau supplémentaire d’indirection qui garantit qu’une variable doit seulement contenir une sorte d’information de taille fixe, tant que nous pouvons trouver l’objet à travers cette information. Pour ce faire, il suffit de charger l’adresse (taille fixe), puis de travailler comme d’habitude en utilisant les décalages de l’objet que nous savons valables, même si cet objet a plus de données en décalage que nous ne connaissons pas. Nous pouvons le faire parce que nous ne

Au niveau abstrait, cette méthode vous permet de stocker une (référence à a) stringdans une objectvariable sans perdre les informations qui la rendent a string. Cela convient à tous les types de travailler comme ça et vous pourriez aussi dire que c'est élégant à bien des égards.

Néanmoins, au niveau de la mise en œuvre, le niveau supplémentaire d'indirection implique plus d'instructions et rend la plupart des architectures plus lentes chaque accès à l'objet. Vous pouvez autoriser le compilateur à augmenter les performances d'un programme si vous incluez dans votre langage certains types couramment utilisés qui ne possèdent pas ce niveau supplémentaire d'indirection (la référence). Mais en supprimant ce niveau d'indirection, le compilateur ne peut plus vous permettre de sous-taper de manière sécurisée pour la mémoire. En effet, si vous ajoutez plus de membres de données à votre type et que vous affectez un type plus général, tous les membres de données supplémentaires qui ne rentrent pas dans l'espace alloué à la variable cible seront découpés en tranches.


1

En général

Si une classe est abstraite (métaphore: une boîte avec un (des) trou (s)), il est OK (même nécessaire d'avoir quelque chose utilisable!) Pour "combler le (s) trou (s)", c'est pourquoi nous sous-classons les classes abstraites.

Si une classe est concrète (métaphore: une boîte pleine), il n'est pas correct de modifier l'existant, car si elle est complète, elle est saturée. Nous n'avons pas la possibilité d'ajouter quelque chose de plus dans la boîte, c'est pourquoi nous ne devrions pas sous-classer les classes concrètes.

Avec des primitives

Les primitives sont des classes concrètes par conception. Ils représentent quelque chose de connu, parfaitement défini (je n'ai jamais vu de type primitif avec quelque chose d'abstrait, sinon ce n'est plus un primitif) et largement utilisé dans le système. Permettre de sous-classer un type primitif et de fournir sa propre implémentation à d'autres qui s'appuient sur le comportement conçu par les primitives peut entraîner de nombreux effets secondaires et des dégâts énormes!



Le lien est un avis de conception intéressant. Besoin de plus de réflexion pour moi.
Den

1

Généralement, l'héritage n'est pas la sémantique que vous souhaitez, car vous ne pouvez pas remplacer votre type spécial là où une primitive est attendue. Pour emprunter à votre exemple, a Quantity + Indexn'a pas de sens sémantiquement, donc une relation d'héritage est une mauvaise relation.

Cependant, plusieurs langues ont le concept d'un type de valeur qui exprime le type de relation que vous décrivez. Scala est un exemple. Un type de valeur utilise une primitive comme représentation sous-jacente, mais a une identité de classe et des opérations différentes à l'extérieur. Cela a pour effet d'étendre un type primitif, mais il s'agit davantage d'une composition plutôt que d'une relation d'héritage.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.