J'ai une langue dans laquelle les types sont déballés par défaut, avec l'inférence de type basée sur Hindley – Milner. Je voudrais ajouter un polymorphisme de rang supérieur, principalement pour travailler avec des types existentiels.
Je pense que je comprends comment vérifier ces types, mais je ne sais pas quoi faire lors de la compilation. Actuellement, je compile des définitions polymorphes en générant des spécialisations, un peu comme les modèles C ++, afin qu'elles puissent fonctionner avec des valeurs non encadrées. Par exemple, étant donné une définition de f<T>
, si le programme invoque uniquement f<Int32>
et f<Char>
, alors seules ces spécialisations apparaissent dans le programme compilé. (Je suppose que la compilation du programme entier pour l'instant.)
Mais lorsque je passe une fonction polymorphe en argument, je ne vois pas comment générer statiquement la bonne spécialisation, car la fonction pourrait être sélectionnée au moment de l'exécution. Dois-je pas d'autre choix que d'utiliser une représentation encadrée? Ou existe-t-il un moyen de contourner le problème?
Ma première pensée a été de coder en quelque sorte le polymorphisme de rang n en rang 1, mais je ne crois pas que ce soit possible en général parce qu'une formule en logique constructive n'a pas nécessairement une forme normale prénex.
la source
Réponses:
J'y ai réfléchi un peu. Le problème principal est qu'en général, nous ne savons pas quelle est la valeur d'une valeur de type polymorphe. Si vous ne disposez pas de ces informations, vous devez les obtenir d'une manière ou d'une autre. La monomorphisation obtient ces informations pour vous en spécialisant le polymorphisme. La boxe obtient ces informations pour vous en mettant tout dans une représentation de taille connue.
Une troisième alternative est de garder une trace de ces informations dans les types. Fondamentalement, ce que vous pouvez faire est d'introduire un type différent pour chaque taille de données, puis des fonctions polymorphes peuvent être définies sur tous les types d'une taille particulière. Je vais esquisser un tel système ci-dessous.
Ici, l'idée de haut niveau est que le type d'un type vous indique le nombre de mots nécessaires pour disposer un objet en mémoire. Pour une taille donnée, il est facile d'être polymorphe sur tous les types de cette taille particulière. Étant donné que chaque type, même polymorphe, a toujours une taille connue, la compilation n'est pas plus difficile que pour C.
Les références sont intéressantes - les pointeurs sont toujours un mot, mais ils peuvent pointer vers des valeurs de n'importe quelle taille. Cela permet aux programmeurs d' implémenter le polymorphisme à des objets arbitraires par boxe, mais ne les oblige pas à le faire. Enfin, une fois que les tailles explicites sont en jeu, il est souvent utile d'introduire un type de remplissage, qui utilise de l'espace mais ne fait rien. (Donc, si vous voulez prendre l'union disjointe d'un int et d'une paire d'entiers, vous devrez ajouter un remplissage au premier int, afin que la disposition de l'objet soit uniforme.)
Les types récursifs ont la règle de formation standard, mais notez que les occurrences récursives doivent avoir la même taille, ce qui signifie que vous devez généralement les coller dans un pointeur pour que le tri fonctionne. Par exemple, le type de données de liste pourrait être représenté comme
Donc, cela pointe vers une valeur de liste vide, ou une paire d'int et un pointeur vers une autre liste liée.
La vérification de type pour des systèmes comme celui-ci n'est pas non plus très difficile; l'algorithme de mon article ICFP avec Joshua Dunfield, Typechecking bidirectionnel complet et facile pour le polymorphisme de rang supérieur s'applique à ce cas avec presque aucun changement.
la source
*
vs#
), mais je n'avais pas envisagé de le faire de cette façon. Il semble raisonnable de restreindre les quantificateurs de rang supérieur à des types de taille connue, et je pense que cela me permettrait également de générer des spécialisations par taille statiquement, sans avoir besoin de connaître le type réel. Maintenant, il est temps de relire ce document. :)Cela semble être plus proche d'un problème de compilation que d'un problème "d'informatique théorique", il vaut donc mieux demander ailleurs.
Dans le cas général, en effet, je pense qu'il n'y a pas d'autre solution que d'utiliser une représentation encadrée. Mais je m'attends également à ce que, dans la pratique, il existe de nombreuses alternatives différentes, selon les spécificités de votre situation.
Par exemple, la représentation de bas niveau des arguments non encadrés peut généralement être classée en très peu d'alternatives, par exemple entier ou similaire, virgule flottante ou pointeur. Donc, pour une fonction
f<T>
, vous n'avez peut-être vraiment besoin que de générer 3 implémentations différentes non encadrées et vous pouvez représenter celle polymorphe comme un tuple de ces 3 fonctions, donc l'instanciation de T en Int32 ne fait que sélectionner le premier élément du tuple, ...la source