En quoi les langages impératifs sont-ils plus différents les uns des autres que les langages fonctionnels?


17

Je lis The Implementation of Functional Programming Language de Simon Peyton Jones et il y a une déclaration qui m'a un peu surpris (à la page 39):

Dans une bien plus grande mesure que pour les langages impératifs, les langages fonctionnels sont en grande partie des variations syntaxiques les uns des autres, avec relativement peu de différences sémantiques.

Maintenant, cela a été écrit en 1987 et mes réflexions à ce sujet pourraient être influencées par des langages de programmation plus modernes qui n'étaient pas là ou populaires à l'époque. Cependant, je trouve cela un peu difficile à croire. Par exemple, je pense que le langage de programmation Miranda décrit (un prédécesseur précoce de Haskell) a une sémantique beaucoup plus différente par rapport à un langage strict comme ML que de dire que C doit Pascal ou peut-être même C doit parler petit C ++ fournit une certaine validation de son point :-).

Mais là encore, je fonde cela sur ma compréhension intuitive. Simon Peyton Jones a-t-il largement raison de dire cela, ou s'agit-il d'un point controversé?

Réponses:


19

Simon a fondamentalement raison, d'un point de vue extensionnel. Nous savons assez bien ce qu'est la sémantique des langages fonctionnels modernes, et ce sont vraiment des variations relativement petites les unes des autres - elles représentent chacune des traductions légèrement différentes dans un métalangage monadique. Même un langage comme Scheme (un langage impératif d'ordre supérieur typé dynamiquement avec un contrôle de première classe) a une sémantique qui est assez proche de ML et de Haskell.

V

Mais pour arriver à une catégorie adaptée à l'interprétation des langages fonctionnels typés modernes, les choses deviennent assez effrayantes. Fondamentalement, vous finissez par construire une catégorie enrichie en ultramétriques de relations d'équivalence partielle sur ce domaine. (Par exemple, voir Birkedal, Stovring et Thamsborg "Realizability Semantics of Parametric Polymorphism, General References, and Recursive Types".) Les personnes qui préfèrent la sémantique opérationnelle connaissent ce genre de choses comme des relations logiques indexées par étapes. (Par exemple, voir «L'indépendance de la représentation dépendante de l'État» d'Ahmed, Dreyer et Rossberg.) Quoi qu'il en soit, les techniques utilisées sont relativement nouvelles.

a -> buneTbunebT(UNE)unea

En ce qui concerne la théorie équationnelle, puisque ces langues peuvent toutes deux être décrites par des traductions en sous-ensembles légèrement différents de la même langue, il est tout à fait juste de les appeler des variations syntaxiques les unes des autres.

La différence de sensation entre ML et Haskell provient en fait des propriétés intensionnelles des deux langages - c'est-à-dire du temps d'exécution et de la consommation de mémoire. ML a un modèle de performance de composition (c'est-à-dire que le coût temps / espace d'un programme peut être calculé à partir des coûts temps / espace de ses sous-termes), comme le ferait un véritable langage d'appel par nom. Haskell réel est implémenté avec appel par besoin, une sorte de mémorisation, et par conséquent ses performances ne sont pas de composition - le temps qu'une expression liée à une variable prend pour évaluer dépend de si elle a été utilisée auparavant ou non. Cela n'est pas modélisé dans la sémantique à laquelle j'ai fait allusion ci-dessus.

Si vous voulez prendre les propriétés intensionnelles plus au sérieux, alors ML et Haskell commencent à montrer des différences plus sérieuses. Il est probablement encore possible de leur concevoir un métalangage commun, mais l'interprétation des types différera de manière beaucoup plus systématique, liée à l'idée théoriquement de la preuve de la focalisation . Un bon endroit pour en savoir plus est la thèse de doctorat de Noam Zeilberger.


10

Mon sentiment est que SPJ faisait référence à des langages purement fonctionnels - c'est-à-dire des langages qui sont référentiellement transparents. Cela inclut, par exemple, Haskell, Miranda, Clean, mais pas ML. Une fois que vous avez un langage purement fonctionnel, en général, vous pouvez lui donner une sémantique de dénotation assez propre et bien définie. Cette sémantique ressemblera en général à celle du calcul lambda, avec quelques ajustements ici et là. Généralement, vous aurez un système de type qui se résume à quelque chose qui ressemble à une variante du système F - peut-être plus puissant à certains égards, plus restreint à d'autres. C'est pourquoi l'extraction de code vers / compilation vers Haskell, O'Caml, etc. est relativement simple à partir d'assistants de preuve sophistiqués de type dépendant comme Agda.

Dans ce cadre, il y a beaucoup de place pour jouer. Certes, il existe encore une différence entre un langage non strict et un langage strict. Cependant, en l'absence d'effets secondaires, la seule différence est qu'un langage non strict contient plus d'expressions qui ne désignent pas le bas - dans la mesure où les deux stratégies d'évaluation ne donnent pas toutes les deux le bas, conviennent-elles.

La déclaration de Simon s'inscrit également dans un contexte historique très important. Au moment de la naissance de Haskell (1987), il y avait une panoplie de langages fonctionnels non stricts - non seulement Miranda, mais Lazy ML, Orwell, Clean et bien d'autres. Hormis certaines variations syntaxiques, elles étaient toutes à peu près le même langage. C'est précisément ce qui a motivé la formation du comité Haskell. Pour plus d'informations à ce sujet, voir «Une histoire de Haskell: être paresseux avec la classe»: http://research.microsoft.com/en-us/um/people/simonpj/papers/history-of-haskell/ .


5

Je pense que SPJ a raison de dire cela pour la sémantique de base.

Bien qu'il existe de nombreuses subtilités avancées, comme le fait de passer par défaut à une évaluation stricte ou paresseuse, comme vous le mentionnez, les détails des systèmes de types ou la façon dont les unités de code plus grandes sont organisées (modules, structures), le modèle mental d'un programme est très similaire entre les langages fonctionnels.

Choisissez une fonction particulière spécifiée et écrivez-la dans toutes les langues que vous comparez, et vous constaterez probablement que la structure et la sémantique des différentes implémentations seront très similaires pour toutes, y compris le niveau d'abstraction, les structures de données choisies, elles '' Tous assumeront la collecte des ordures, etc.

Au contraire, disons qu'une implémentation C vs une implémentation Smalltalk de la même fonction aura probablement une structure différente (fonctions et structures de données de bas niveau vs objets), se concentrera sur différents niveaux de détails (par exemple, la gestion manuelle de la mémoire vs le garbage collection ) et opèrent à différents niveaux d'abstraction.

La vision du monde de l'espace de conception de langage fonctionnel est simplement plus spécifique et cohérente que celle de l'espace de "programmation impérative" qui regroupe l'assemblage, le C, le Smalltalk, le Forth et des dizaines d'autres langages radicalement différents dans une seule catégorie fourre-tout.


4

Je pense que la citation de Simon PJ est en fait une remarque désinvolte.

La similitude entre les langues est déterminée par le consensus qui a été généré dans la communauté des chercheurs et des concepteurs de langues. Il ne fait aucun doute qu'il existe un degré de consensus plus élevé dans la communauté de programmation fonctionnelle que dans la communauté de programmation impérative. Mais il est également vrai que les langages de programmation fonctionnels sont principalement conçus par des chercheurs plutôt que par des praticiens. Il est donc naturel qu'un tel consensus se dégage.

Presque tous les langages fonctionnels utilisent une gestion de la mémoire collectée par les ordures et des structures de données récursives (originaires de Lisp), la plupart utilisent des types de données "algébriques" et des correspondances de modèles (originaires de Hope), beaucoup utilisent des fonctions d'ordre supérieur et des fonctions polymorphes ( originaire de ML). Au-delà, le consensus disparaît. Ils diffèrent dans les systèmes de modules utilisés, la façon dont les opérations de changement d'état et les autres effets de calcul doivent être traités, l'ordre d'évaluation (appel par nom vs appel par valeur), etc.

Les langages de programmation impératifs utilisent généralement des structures de contrôle imbriquées (générées par Algol 60) et des systèmes de types (générés par Algol 60, mais consolidés par Algol 68). Ils ont généralement une syntaxe de surface encombrante (remontant à nouveau à Algol 60), font des tentatives timides pour gérer des fonctions d'ordre supérieur et des types polymorphes, et diffèrent dans leur prise en charge de la structure des blocs et des systèmes de modules. Il y a probablement plus d'uniformité dans l'ordre d'évaluation car, après les années 60, l'appel par nom a pratiquement disparu des langues impératives.

Donc, il n'est pas clair pour moi que la différence entre les deux classes de langues dans leur uniformité est si grande.

Il serait vraiment intéressant d'apporter les notations plus propres et uniformes de la programmation fonctionnelle dans les langages de programmation impératifs. Je vois que Scala a fait un début dans cette direction. Il reste à voir si la tendance se poursuivra.


Je me demande en vain pourquoi vous avez utilisé les citations effrayantes dans les types de données "algébriques"?
Steven Shaw
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.