Des types spécifiques sont-ils encore nécessaires?

20

Une chose qui m'est venue à l'esprit l'autre jour, ce sont des types spécifiques encore nécessaires ou un héritage qui nous retient. Ce que je veux dire, c'est: avons-nous vraiment besoin de court, int, long, bigint, etc., etc.

Je comprends le raisonnement, les variables / objets sont conservés en mémoire, la mémoire doit être allouée et donc nous devons savoir quelle taille peut avoir une variable. Mais vraiment, un langage de programmation moderne ne devrait-il pas être capable de gérer des "types adaptatifs", c'est-à-dire que si quelque chose n'est alloué que dans la plage courte, il utilise moins d'octets et si quelque chose est soudainement alloué à un très grand nombre, la mémoire est allouée conformément à ce cas particulier.

Float, real et double's sont un peu plus délicats car le type dépend de la précision dont vous avez besoin. Les chaînes devraient cependant être en mesure de prendre upp moins de mémoire dans de nombreux cas (en .Net) où la plupart du temps ascii est utilisé, mais les chaînes buth occupent toujours le double de la mémoire en raison du codage Unicode.

Un argument pour des types spécifiques pourrait être qu'il fait partie de la spécification, c'est-à-dire par exemple qu'une variable ne devrait pas pouvoir être plus grande qu'une certaine valeur, nous la définissons donc en shortint. Mais pourquoi ne pas avoir à la place des contraintes de type? Il serait beaucoup plus flexible et puissant de pouvoir définir des plages et des valeurs autorisées sur des variables (et des propriétés).

Je me rends compte de l'immense problème de refonte de l'architecture de type car elle est si étroitement intégrée au matériel sous-jacent et des choses comme la sérialisation pourraient devenir délicates. Mais du point de vue de la programmation, ça devrait être super non?

Homde
la source
6
PHP, Ruby, Perl et autres ne vous obligent pas à indiquer les types de variables. L'environnement le comprend pour vous.
FrustratedWithFormsDesigner
7
Les chaînes Unicode n'ont pas besoin de prendre de la mémoire supplémentaire lorsqu'elles sont utilisées pour ASCII uniquement (UTF-8).
2
Mais il y a une différence entre les types IMO variant et adaptatifs. Les variantes ne sont pas tapées du tout, mais sont tapées lorsqu'elles sont attribuées, tandis que les types adaptatifs sont tapés, mais de manière plus lâche. (et j'aime le concept de contraintes de type)
Homde
Cela me rappelle ce projet: tom.lokhorst.eu/media/…
LennyProgrammers
4
Et Ada? type hour is range 0 .. 23;
mouviciel

Réponses:

12

Je crois totalement que c'est le cas. Les contraintes sémantiques valent plus que les contraintes d'implémentation. S'inquiéter de la taille de quelque chose revient à s'inquiéter de la vitesse de quelque chose lors de la programmation orientée objet.

Il n'a pas remplacé la programmation essentielle aux performances. Cela a simplement rendu la programmation critique non liée aux performances plus productive.

Mark Canlas
la source
1
Consultez les contrats de code dans .NET 4.0.
Steven Jeuris
+1 En matière de stockage / transmission de données (ex. Mise en réseau), les contraintes sont fondamentales pour maximiser l'efficacité du protocole / implémentation. De plus, il y a beaucoup à gagner si des collections typées sont disponibles. En dehors de cela, il est sûr de supposer que l'efficacité peut prendre un siège arrière (surtout si elle diminue le risque d'erreurs sémantiques).
Evan Plaice
9

Les types adaptatifs signifient la logique pour faire l'adaptation, signifie travailler au moment de l'exécution pour exécuter cette logique (les modèles et la compilation nécessiteraient un type spécifique, l' inférence de type étant un cas spécial où vous obtenez le meilleur des deux mondes). Ce travail supplémentaire pourrait être acceptable dans des environnements où les performances ne sont pas critiques et où le système conserve une taille raisonnable. Dans d'autres environnements, ce n'est pas le cas (les systèmes intégrés en sont un, où vous devez parfois utiliser des types entiers 32/64 bits pour les performances du processeur et des types entiers 8/16 bits pour l'optimisation de la sauvegarde de la mémoire statique).

Même les langages à usage général qui prenaient en charge la liaison tardive (résolution des types au moment de l'exécution, comme VB6) ont tendance à promouvoir un typage fort maintenant (VB.NET), en raison des performances qui se produisaient auparavant lorsque la liaison tardive était abusive, et parce que vous souvent se retrouver avec du code laid lorsque les types ne sont pas explicites ( refactoring de référence / professionnel en Visual Basic - Danijel Arsenovski ).

Matthieu
la source
Veuillez définir "saisie automatique".
@delnan: remplacement de la saisie automatique par une liaison tardive, ce que je voulais dire :)
Matthieu
Il existe de nombreux langages à usage général qui résolvent les types à l'exécution, Common Lisp pour n'en nommer qu'un. (À des fins de performances, vous pouvez déclarer des types en Common Lisp, vous pouvez donc le faire uniquement dans les sections critiques pour les performances.)
David Thornley
@David Thornley: "appliquer" une frappe forte peut avoir été trop fort, "promouvoir" serait plus approprié, a mis à jour ma réponse en conséquence. Une langue qui vous permet de choisir entre les deux types de reliure selon la situation vaut certainement mieux que d'être forcée d'une manière ou d'une autre. Surtout lorsque vous ne faites pas de programmation de bas niveau et que vous vous concentrez sur la logique.
Matthieu
4

Simplicité, mémoire et vitesse Lorsque je déclare une variable, la mémoire de cette variable est allouée dans un bloc. Afin de prendre en charge une variable à croissance dynamique, je devrais ajouter le concept de mémoire non contiguë à cette variable (que ce soit ou réserver le plus grand bloc que la variable peut représenter). La mémoire non contiguë réduirait les performances lors de l'affectation / récupération. Allouer le plus grand possible serait un gaspillage dans le scénario où je n'ai besoin que d'un octet mais le système réserve longtemps.

Pensez aux compromis entre un tableau et un vecteur (ou une liste liée). Avec un tableau, la recherche d'une position spécifique consiste simplement à obtenir la position de départ et à déplacer le pointeur de mémoire x espaces pour localiser cette nouvelle position dans la mémoire. Considérez un entier comme un bit [32] la lecture d'un entier implique de parcourir ce tableau pour obtenir toutes les valeurs de bits.

Pour créer un type de nombre dynamique, vous devez le changer d'un tableau de bits en un vecteur de bits. La lecture de votre numéro dynamique implique d'aller en tête, d'obtenir ce bit, de demander où se trouve le bit suivant, de se déplacer vers cet emplacement, d'obtenir ce bit, etc. Pour chaque bit du numéro dynamique, vous effectuez trois opérations de lecture ( actuel), lire (adresse du suivant), déplacer (suivant). Imaginez lire les valeurs d'un million de nombres. C'est un million d'opérations supplémentaires. Cela peut sembler insignifiant. Mais pensez aux systèmes (comme les finances) où chaque milliseconde compte.

Il a été décidé que le fardeau du développeur de vérifier la taille et de valider est un petit compromis par rapport à la performance du système.

Michael Brown
la source
1
L'autre alternative consiste à implémenter des nombres similaires aux listes d'array où le tableau est réalloué lorsque le nombre dépasse la taille actuelle. Vous devez également tenir compte du cas où l'utilisateur VEUT que le débordement boucle.
Michael Brown
C'est vrai, mais en quelque sorte une simplification. Vous pourriez trouver une structure de tableau plus efficace, mais pas aussi rapide que le type statique pourrait être "assez rapide" pour la plupart des cas. par exemple, vous pourriez enregistrer des informations sur des blocs de différents types, si le tableau n'était pas complètement irrégulier, ce qui ne prendrait pas beaucoup plus de mémoire ou de performances. Ou le tableau pourrait sacrifier de la mémoire pour avoir un index quelconque. Le tableau pourrait même s'auto-optimiser en fonction de son contenu. Vous pouvez toujours avoir la possibilité de taper la taille de la mémoire via une contrainte de type si vous avez besoin de performances.
Homde
Pour être juste, ce n'est pas aussi brutal que vous le dites. Cf ma prochaine réponse.
Paul Nathan
3

Des types spécifiques sont requis pour les langages et projets centrés sur le matériel. Un exemple est les protocoles de réseau sur le fil.

Mais créons - pour le plaisir - un type varint dans un langage comme C ++. Construisez-le à partir d'un newtableau de dts.

Il n'est pas difficile d'implémenter l'ajout: il suffit de xor les octets ensemble et de vérifier les bits élevés: s'il y a une opération de report, newdans un nouvel octet supérieur et de reporter le bit. La soustraction suit trivialement dans la représentation du complément à 2. (Ceci est également connu comme un additionneur de report d'ondulation).

La multiplication suit de la même manière; utiliser l'ajout / décalage itératif. Comme toujours, la vraie torsion dans votre queue est la division [*].

Mais qu'avez-vous perdu quand cela se produit?

  • Temps déterministe. Vous avez un syscall ( new) qui peut se déclencher à des points qui ne sont pas nécessairement contrôlables.

  • Espace déterministe.

  • Les mathématiques semi-logicielles sont lentes.

Si vous devez utiliser un langage de couche matérielle et devez également fonctionner à un niveau élevé (lent) et que vous ne souhaitez pas intégrer de moteur de script, varintcela a beaucoup de sens. C'est probablement écrit quelque part.

[*] Cf algorithmes mathématiques matériels pour des moyens plus rapides de le faire - généralement, l'astuce consiste à effectuer des opérations parallèles.

Paul Nathan
la source
2

C'est une bonne question. Il explique pourquoi un langage tel que Python n'a pas besoin de "short, int, long, bigint etc.": les entiers sont, eh bien, des entiers (il existe un seul type d'entier en Python 3), et n'ont pas de taille limite (au-delà de celle de la mémoire de l'ordinateur, bien sûr).

Quant à Unicode, le codage UTF-8 (qui fait partie d'Unicode) n'utilise qu'un seul caractère pour les caractères ASCII, donc ce n'est pas si mal.

Plus généralement, les langages dynamiques semblent aller dans le sens que vous mentionnez. Cependant, pour des raisons d'efficacité, des types plus contraints sont utiles dans certains cas (comme les programmes qui doivent s'exécuter rapidement). Je ne vois pas beaucoup de changements dans un avenir prévisible, car les processeurs organisent les données en octets (ou 2, 4, 8, etc. octets).

Eric O Lebigot
la source
1

Sur une base de théorie du langage, vous avez raison. Les types doivent être basés sur un ensemble d'états légaux, les transformations disponibles pour ces états et les opérations exécutables sur ces états.

C'est à peu près ce que vous offre la programmation OOP dans sa forme typique. En fait, en Java, vous parlez effectivement des classes BigIntegeret BigDecimal, qui allouent de l'espace en fonction de la quantité requise pour stocker l'objet. (Comme l'a noté FrustratedWithFormsDesigner, de nombreux langages de type scripting sont encore plus loin sur ce chemin et ne nécessitent même pas de déclaration de type et stockent tout ce que vous leur donnez.)

Cependant, les performances sont toujours pertinentes, et comme il est coûteux de changer de type au moment de l'exécution et que les compilateurs ne peuvent pas garantir la taille maximale d'une variable au moment de la compilation, nous avons toujours des variables de taille statique pour les types simples dans de nombreux langages.

jprete
la source
Je me rends compte qu'une sorte de typage dynamique / adaptatif semble coûteux et moins performant que ce que nous avons actuellement, et en utilisant des compilateurs actuels, ce serait certainement le cas. Mais sommes-nous sûrs à 100% que si vous construisez un langage et un compilateur à partir de zéro, vous ne pouvez pas les faire, si ce n'est aussi rapide que typé statiquement, au moins rapidement possible pour en valoir la peine.
Homde
1
@MKO: Pourquoi ne pas l'essayer et voir?
Anon.
1
Oui, vous pouvez le faire rapidement (mais probablement jamais aussi vite qu'un système statique pour les nombres). Mais la partie «ça vaut le coup» est plus délicate. La plupart des gens travaillent avec des données dont la plage s'intègre confortablement dans un intou un double, et si ce n'est pas le cas, ils en sont conscients, donc le dimensionnement dynamique de la valeur est une fonctionnalité pour laquelle ils n'ont pas besoin de payer.
jprete
Comme tous les programmeurs, bien sûr, je rêve de faire un jour mon propre langage;)
Homde
@jprete: je ne suis pas d'accord; la plupart des gens ne sont pas conscients de possibles résultats intermédiaires importants. Un tel langage peut et a été rendu assez rapide pour la plupart des applications.
David Thornley
1

Cela dépend de la langue. Pour les langages de niveau supérieur comme Python, Ruby, Erlang, etc., vous n'avez que le concept de nombres entiers et décimaux.

Cependant, pour une certaine classe de langues, ces types sont très importants. Lorsque vous écrivez du code pour lire et écrire des formats binaires comme PNG, JPeg, etc., vous devez savoir précisément combien d'informations sont lues à la fois. Idem pour l'écriture des noyaux du système d'exploitation et des pilotes de périphérique. Tout le monde ne le fait pas, et dans les langages de niveau supérieur, ils utilisent les bibliothèques C pour faire le gros du travail détaillé.

Dans short, il y a encore une place pour les types plus spécifiques, mais de nombreux problèmes de développement ne nécessitent pas cette précision.

Berin Loritsch
la source
0

J'ai récemment créé un éditeur de logique à relais et un runtime et j'ai décidé d'être très limité avec les types:

  • Booléen
  • Nombre
  • Chaîne
  • DateTime

Je crois que cela l'a rendu plus intuitif pour l'utilisateur. C'est un changement radical par rapport à la plupart des automates programmables qui ont toute la gamme "normale" de types que vous verriez dans une langue comme C.

Scott Whitlock
la source
0

Les langages de programmation ont évolué dans cette direction. Prenez des chaînes par exemple. Dans les anciennes langues, vous devez déclarer la taille de la chaîne, comme PIC X(42)dans COBOL, DIM A$(42)dans certaines versions de BASIC, ou [ VAR] CHAR(42)dans SQL. Dans les langues modernes, vous n'en avez qu'un alloué dynamiquementstring type et vous n'avez pas besoin de penser à la taille.

Les entiers sont cependant différents:

Ce que je veux dire, c'est: avons-nous vraiment besoin de court, int, long, bigint, etc., etc.

Jetez un oeil à Python. Il faisait la distinction entre les entiers de taille machine ( int) et de taille arbitraire ( long). Dans 3.x l'ancien est parti (l'ancien longest le nouveau int) et personne ne le manque.

Mais il existe toujours un type spécialisé pour les séquences d'entiers 8 bits sous la forme de byteset bytearray. Pourquoi ne pas utiliser respectivement un tupleou listdes entiers? Certes, bytesil existe des méthodes de type chaîne supplémentaires qui tuplene le font pas, mais l'efficacité a certainement beaucoup à voir avec cela.

Float, real et double's sont un peu plus délicats car le type dépend de la précision dont vous avez besoin.

Pas vraiment. L'approche «tout est double précision» est très courante.

dan04
la source
1
Peut-être que les types de base devraient déclarer l'intention de base du type, c'est-à-dire int pour les nombres "ordinaires", double pour toutes les "décimales" normales (les entiers ne devraient-ils pas pouvoir avoir des décimales mais pour plus de simplicité?) "Argent" pour travailler avec les montants et les octets pour travailler avec des données binaires. Une contrainte de type déclarée via un attribut pourrait permettre de déclarer la plage autorisée, la précision décimale, la nullité et même les valeurs autorisées. Ce serait cool si vous pouviez créer des types personnalisés et réutilisables de cette façon
Homde
@konrad: À mon humble avis, la raison pour laquelle les entiers "non signés" provoquent de tels maux de tête en C est qu'ils sont parfois utilisés pour représenter des nombres et parfois pour représenter des membres d'un anneau algébrique abstrait enveloppant. Le fait d'avoir des types distincts de "sonnerie" et de "nombre non signé" pourrait garantir que le code comme unum64 += ring32a-ring32bdonnera toujours le comportement correct, que le type entier par défaut soit 16 bits ou 64 [notez que l'utilisation de +=est essentielle; une expression comme unum64a = unum64b + (ring32a-ring32b);devrait être rejetée comme ambiguë.]
supercat
0

Je comprends le raisonnement, les variables / objets sont conservés en mémoire, la mémoire doit être allouée et donc nous devons savoir quelle taille peut avoir une variable. Mais vraiment, un langage de programmation moderne ne devrait-il pas être capable de gérer des "types adaptatifs", c'est-à-dire que si quelque chose n'est alloué que dans la plage courte, il utilise moins d'octets et si quelque chose est soudainement alloué à un très grand nombre, la mémoire est allouée conformément à ce cas particulier.

Float, real et double's sont un peu plus délicats car le type dépend de la précision dont vous avez besoin. Les chaînes devraient cependant être en mesure de prendre upp moins de mémoire dans de nombreux cas (en .Net) où la plupart du temps ascii est utilisé, mais les chaînes buth occupent toujours le double de la mémoire en raison du codage Unicode.

Fortran a eu quelque chose de similaire (je ne sais pas si c'est exactement ce que vous voulez dire, car je vois vraiment deux questions). Par exemple, dans F90 vers le haut, vous n'avez pas besoin de définir explicitement une taille de type , pour ainsi dire. Ce qui est bien, non seulement car il vous donne une place centrale pour définir vos types de données, mais aussi une manière portable de les définir. REAL * 4 n'est pas le même dans toutes les implémentations sur tous les processeurs (et par processeur je veux dire CPU + compilateur), pas par un longhot.

selected_real_kind (p, r) renvoie la valeur de type d'un type de données réel avec une précision décimale supérieure d'au moins p chiffres et une plage d'exposants supérieure d'au moins r.

Vous partez donc, par exemple;

program real_kinds
integer,parameter :: p6 = selected_real_kind(6)
integer,parameter :: p10r100 = selected_real_kind(10,100) !p is precision, r is range
integer,parameter :: r400 = selected_real_kind(r=400)
real(kind=p6) :: x
real(kind=p10r100) :: y
real(kind=r400) :: z

print *, precision(x), range(x)
print *, precision(y), range(y)
print *, precision(z), range(z)
end program real_kinds

(Je pense que c'est un exemple assez explicite).

Je ne sais toujours pas si j'ai bien compris votre question, et c'est ce que vous mentionnez.

Tour
la source