Je veux comprendre à un bas niveau ce qui se passerait si la structure des données n'était pas persistante?
Regardons un générateur de nombres pseudo-aléatoires avec un immense espace d'état (comme " Twister Mersenne " avec un état de 2450 octets) comme structure de données. Nous ne voulons pas vraiment utiliser un nombre aléatoire plus d'une fois, donc il semble y avoir peu de raisons de l'implémenter comme une structure de données persistante immuable. Maintenant, demandons-nous ce qui pourrait mal se passer dans le code suivant:
mt_gen = CreateMersenneTwisterPRNGen(seed)
integral = MonteCarloIntegral_Bulk(mt_gen) + MonteCarloIntegral_Boundary(mt_gen)
La plupart des langages de programmation ne spécifient pas l'ordre dans lequel MonteCarloIntegral_Bulk
et MonteCarloIntegral_Boundary
seront évalués. Si les deux prennent une référence à un mt_gen mutable comme argument, le résultat de ce calcul peut dépendre de la plateforme. Pire encore, il peut y avoir des plates-formes où le résultat n'est pas du tout reproductible entre différentes exécutions.
On peut concevoir une structure de données mutable efficace pour mt_gen de telle sorte que tout entrelacement de l'exécution de MonteCarloIntegral_Bulk
et MonteCarloIntegral_Boundary
donnera un résultat "correct", mais un entrelacement différent conduira généralement à un résultat "correct" différent. Cette non-reproductibilité rend la fonction correspondante "impure" et entraîne également d'autres problèmes.
La non-reproductibilité peut être évitée en appliquant un ordre d'exécution séquentiel fixe. Mais dans ce cas, le code pourrait être organisé de telle sorte qu'une seule référence à mt_gen soit disponible à un moment donné. Dans un langage de programmation fonctionnel typé, des types d'unicité pourraient être utilisés pour imposer cette contrainte, permettant ainsi des mises à jour mutables sûres également dans le contexte de langages de programmation fonctionnels purs. Tout cela peut sembler agréable et dandy, mais au moins en théorie, les simulations de Monte-Carlo sont parallèlement embarrassantes, et notre «solution» vient de détruire cette propriété. Ce n'est pas seulement un problème théorique, mais un problème pratique très réel. Cependant, nous devons modifier (la fonctionnalité offerte par) notre générateur de nombres pseudo-aléatoires et la séquence de nombres aléatoires qu'il produit, et aucun langage de programmation ne peut le faire automatiquement pour nous. (Bien sûr, nous pouvons utiliser une autre bibliothèque de numéros pseudo-aléatoires qui offre déjà les fonctionnalités requises.)
À un faible niveau, les structures de données mutables conduisent facilement à la non-reproductibilité (et donc à l'impureté), si l'ordre d'exécution n'est pas séquentiel et fixe. Une stratégie impérative typique pour traiter ces problèmes consiste à avoir des phases séquentielles avec un ordre d'exécution fixe, au cours desquelles les structures de données mutables sont modifiées, et des phases parallèles avec un ordre d'exécution arbitraire, au cours desquelles toutes les structures de données mutables partagées restent constantes.
Andrej Bauer a soulevé la question de l'aliasing pour les structures de données mutables. Chose intéressante, différents langages impératifs comme Fortran et C ont des hypothèses différentes sur l'aliasing autorisé des arguments de fonction, et la plupart des programmeurs ne savent pas du tout que leur langage a un modèle d'aliasing.
L'immuabilité et la sémantique des valeurs peuvent être légèrement surévaluées. Ce qui est plus important, c'est que le système de type et le cadre logique (comme le modèle de machine abstraite, le modèle d'alias, le modèle de concurrence ou le modèle de gestion de la mémoire) de votre langage de programmation offrent un support suffisant pour travailler "en toute sécurité" avec des données "efficaces" structures. L'introduction de "déplacer la sémantique" dans C ++ 11 pourrait ressembler à un énorme pas en arrière en termes de pureté et de "sécurité" d'un point de vue théorique, mais en pratique, c'est le contraire. Le système de types et le cadre logique du langage ont été étendus pour éliminer d'énormes parties du danger associé à la nouvelle sémantique. (Et même s'il reste des bords rugueux, cela ne signifie pas que cela ne pourrait pas être amélioré par un "meilleur"
Uday Reddy a soulevé la question du fait que les mathématiques n'ont jamais fonctionné avec des objets de données mutables et que les systèmes de types pour les programmes fonctionnels sont bien développés pour les objets de données immuables. Cela m'a rappelé l'explication de Jean-Yves Girard selon laquelle les mathématiques ne sont pas utilisées pour travailler avec des objets changeants, lorsqu'il essaie de motiver la logique linéaire.
On pourrait se demander comment étendre le système de types et le cadre logique des langages de programmation fonctionnels pour permettre de travailler "en toute sécurité" avec des structures de données mutantes "efficaces" non persistantes. Un problème ici pourrait être que la logique classique et les algèbres booléennes pourraient ne pas être le meilleur cadre logique pour travailler avec des structures de données mutables. Peut-être que la logique linéaire et les monoïdes commutatifs pourraient être mieux adaptés à cette tâche? Peut-être devrais-je lire ce que Philip Wadler a à dire sur la logique linéaire comme système de type pour les langages de programmation fonctionnels? Mais même si la logique linéaire ne devrait pas être en mesure de résoudre ce problème, cela ne signifie pas que le système de type et le cadre logique d'un langage de programmation fonctionnel ne pourraient pas être étendus pour permettre "sûr" et "efficace"