Quels types de vérifications Raku effectue-t-il au moment de la compilation? Cela peut-il changer à l'avenir?
Actuellement (en août 2020), Rakudo ne vérifie pas les valeurs de retour des fonctions au moment de la compilation; autrement dit, il ne fournit pas de garanties statiques que les fonctions satisfont leurs contraintes de retour. Concrètement, les deux fonctions suivantes se compilent toutes deux en Raku:
sub get-int(--> Int) { 'bug' }
sub get-int($a --> Int} { when $a == 5 { 'Rare bug' }
default { 42 }
}
J'ai deux questions liées:
Existe-t-il un moyen de savoir quelle vérification de type (le cas échéant) a lieu actuellement au moment de la compilation? (Soit via une liste que quelqu'un a écrite, quelque part dans la documentation, ou une place centrale dans la source Rakudo) Ou est-ce plus ad hoc que cela?
Ce manque de temps de compilation est-il une décision de conception intentionnelle? Ou est-ce que l'ajout d'une vérification de type plus statique est quelque chose qui serait bien d'avoir un jour, mais qui n'a tout simplement pas encore été implémenté?
(Je connais la bonne réponse de Johnathan à The performance pennes for types / contraintes in Raku?, Qui stipule que "Raku exige que les contraintes de type écrites dans le programme soient appliquées au plus tard au moment de l' exécution ." Cette réponse décrit différentes manières d'éviter l'exécution -temps des vérifications de type, mais ne décrit pas quelles vérifications de type, le cas échéant, sont effectuées au moment de la compilation (ce qui éviterait certainement les coûts d'exécution!).)
Réponses
Actuellement, très peu de vérification des types est effectuée au moment de la compilation; ce qui se produit principalement comme un effet secondaire de l'optimiseur statique. Les vérifications d'aujourd'hui concernent en grande partie les appels de sous-programmes, où:
- Nous pouvons déterminer l'arité de l'appel et savoir que le nombre d'arguments passés ne correspondra jamais
- Nous avons des arguments littéraux et pouvons voir qu'ils ne correspondraient jamais à la signature
Il s'agit d'un vestige du moment où l'optimiseur statique a effectué plus de travail en ligne. De nos jours, il n'inline que les opérateurs natifs au moment de la compilation et laisse le reste à l'optimiseur dynamique de la VM, qui est beaucoup plus capable d'inliner et peut également se désintégrer (permettant une optimisation spéculative, mais signifiant également que les traces de pile d'origine peuvent être récupérées, alors que le l'optimiseur statique a perdu ces informations).
Faire plus lors de la compilation est considéré comme souhaitable, mais il y a quelques problèmes pratiques à prendre en compte.
- L'introduction de vérifications supplémentaires peut également introduire une rupture de code qui fonctionnait auparavant. Considérez un module avec un chemin de code qui échouerait à une vérification de temps de compilation plus stricte, mais qui est utilisé dans des systèmes qui ne se rencontrent jamais dans ce cas. Si la compilation échouait sur les versions plus récentes du compilateur, il deviendrait alors impossible de déployer ce système après une mise à niveau du compilateur. En général, cela signifie que les contrôles effectués doivent changer lors des changements de version linguistique. (Cela signifie toujours que les gens doivent déclarer la version linguistique sur laquelle ils écrivent lorsqu'ils écrivent du code, attention.)
- Le fait que davantage de vérifications soient effectuées au moment de la compilation "évitera certainement les coûts d'exécution" peut être vrai, mais ce n'est pas anodin à raisonner. Un runtime géré ne peut pas faire confiance aveuglément aux promesses faites dans le bytecode qui lui est donné, car cela pourrait conduire à des violations de la sécurité de la mémoire (qui conduisent à SIGSEGV ou pire). C'est clairement vrai dans un langage comme Raku, où la sémantique de la vérification de type est programmable, mais c'est vrai sur la JVM, le CLR, etc. Les plus gros gains liés aux types dans Raku proviennent de l'utilisation de types natifs, ce qui peut éviter de nombreuses allocations et donc un travail de ramasse-miettes.
- L'implémentation de vérifications supplémentaires augmentera la complexité du compilateur et également le temps nécessaire à la compilation. Le premier est déjà un problème; l'interface du compilateur n'a pas connu de changements architecturaux significatifs depuis environ une décennie. Le travail actuel de RakuAST qui jette les bases des macros implique également une quasi-réécriture de l'interface du compilateur. L'architecture améliorée devrait faciliter l'implémentation de vérifications de type supplémentaires au moment de la compilation, mais on réfléchit également à la façon dont les aspects de la compilation pourraient être parallélisés, ce qui pourrait permettre au compilateur de faire plus sans augmenter le temps de compilation de l'horloge murale.
Une fois la refonte actuelle du frontend du compilateur terminée, d'autres vérifications au moment de la compilation (mais uniquement activées à partir de la prochaine version du langage) semblent très probables - du moins, tant que quelqu'un y travaille.
Cependant, une opportunité encore plus excitante se présente dans ce domaine: comme il y aura une API pour les programmes Raku, et avec des plans se réunissant pour des passes de compilateur personnalisées, il sera également bientôt possible d'implémenter des vérificateurs de type en tant que modules ! Certains d'entre eux peuvent conduire à des vérifications qui en feront de futures versions linguistiques de Raku. D'autres peuvent être assez spécifiques à un domaine et viser à permettre une utilisation plus correcte d'un module donné. D'autres peuvent imposer des rigueurs qui ne sont pas dans l'esprit de la langue de base, mais que certains utilisateurs de langue pourraient souhaiter choisir.