Quelqu'un sait-il quel est le pire ralentissement asymptotique possible qui peut se produire lors de la programmation purement fonctionnelle et non impérative (c.-à-d. Autoriser les effets secondaires)?
Clarification à partir du commentaire d'itowlson : y a-t-il un problème pour lequel le meilleur algorithme non destructif connu est asymptotiquement pire que le meilleur algorithme destructif connu, et si oui de combien?
Réponses:
Selon Pippenger [1996] , lorsque l'on compare un système Lisp qui est purement fonctionnel (et a une sémantique d'évaluation stricte, pas paresseux) à un système qui peut muter des données, un algorithme écrit pour le Lisp impur qui s'exécute en O ( n ) peut être traduit à un algorithme en Lisp pur qui s'exécute en temps O ( n log n ) (basé sur les travaux de Ben-Amram et Galil [1992] sur la simulation de la mémoire à accès aléatoire en utilisant uniquement des pointeurs). Pippenger établit également qu'il existe des algorithmes pour lesquels c'est le mieux que vous puissiez faire; il y a des problèmes qui sont O ( n ) dans le système impur qui sont Ω ( n log n ) dans le système pur.
Il y a quelques mises en garde à faire sur ce document. Le plus important est qu'il ne traite pas des langages fonctionnels paresseux, tels que Haskell. Bird, Jones et De Moor [1997] démontrent que le problème construit par Pippenger peut être résolu dans un langage fonctionnel paresseux en temps O ( n ), mais ils n'établissent pas (et pour autant que je sache, personne ne l'a) si ou pas un langage fonctionnel paresseux ne peut résoudre tous les problèmes dans le même temps d'exécution asymptotique qu'un langage avec mutation.
Le problème construit par Pippenger nécessite que Ω ( n log n ) soit spécifiquement construit pour atteindre ce résultat, et n'est pas nécessairement représentatif des problèmes pratiques du monde réel. Il y a quelques restrictions sur le problème qui sont un peu inattendues, mais nécessaires pour que la preuve fonctionne; en particulier, le problème nécessite que les résultats soient calculés en ligne, sans pouvoir accéder aux futures entrées, et que l'entrée soit constituée d'une séquence d'atomes provenant d'un ensemble illimité d'atomes possibles, plutôt que d'un ensemble de taille fixe. Et l'article établit uniquement des résultats (borne inférieure) pour un algorithme impur de temps de parcours linéaire; pour les problèmes qui nécessitent un temps d'exécution plus long, il est possible que le O supplémentaire (log n), le facteur observé dans le problème linéaire peut être «absorbé» au cours d'opérations supplémentaires nécessaires pour les algorithmes avec des temps d'exécution plus longs. Ces clarifications et questions ouvertes sont explorées brièvement par Ben-Amram [1996] .
En pratique, de nombreux algorithmes peuvent être implémentés dans un langage fonctionnel pur avec la même efficacité que dans un langage avec des structures de données mutables. Pour une bonne référence sur les techniques à utiliser pour implémenter efficacement des structures de données purement fonctionnelles, voir "Purely Functional Data Structures" de Chris Okasaki [Okasaki 1998] (qui est une version étendue de sa thèse [Okasaki 1996] ).
Quiconque a besoin d'implémenter des algorithmes sur des structures de données purement fonctionnelles doit lire Okasaki. Vous pouvez toujours obtenir au pire un ralentissement O (log n ) par opération en simulant la mémoire mutable avec un arbre binaire équilibré, mais dans de nombreux cas, vous pouvez faire beaucoup mieux que cela, et Okasaki décrit de nombreuses techniques utiles, des techniques amorties aux techniques réelles. ceux qui effectuent le travail amorti progressivement. Les structures de données purement fonctionnelles peuvent être un peu difficiles à travailler et à analyser, mais elles offrent de nombreux avantages, tels que la transparence référentielle, qui sont utiles dans l'optimisation du compilateur, dans l'informatique parallèle et distribuée et dans la mise en œuvre de fonctionnalités telles que la gestion des versions, l'annulation et la restauration.
Notez également que tout cela ne traite que des temps de fonctionnement asymptotiques. De nombreuses techniques pour implémenter des structures de données purement fonctionnelles vous donnent un certain ralentissement constant des facteurs, en raison de la tenue de livres supplémentaire nécessaire pour qu'elles fonctionnent et des détails d'implémentation de la langue en question. Les avantages des structures de données purement fonctionnelles peuvent l'emporter sur ces ralentissements constants des facteurs, vous devrez donc généralement faire des compromis en fonction du problème en question.
Références
la source
Il existe en effet plusieurs algorithmes et structures de données pour lesquels aucune solution purement fonctionnelle asymptotiquement efficace (celle implémentable en calcul lambda pur) n'est connue, même avec paresse.
Cependant, nous supposons que dans les langues "impératives", l'accès à la mémoire est O (1) alors qu'en théorie cela ne peut pas être aussi asymptotique (c'est-à-dire pour des tailles de problème illimitées) et l'accès à la mémoire dans un énorme ensemble de données est toujours O (log n) , qui peut être émulé dans un langage fonctionnel.
En outre, nous devons nous rappeler qu'en réalité, tous les langages fonctionnels modernes fournissent des données mutables, et Haskell les fournit même sans sacrifier la pureté (la monade ST).
la source
Cet article prétend que les implémentations purement fonctionnelles connues de l'algorithme de recherche d'union ont toutes une complexité asymptotique pire que celle qu'elles publient, qui a une interface purement fonctionnelle mais utilise des données mutables en interne.
Le fait que d'autres réponses prétendent qu'il ne peut jamais y avoir de différence et que, par exemple, le seul "inconvénient" du code purement fonctionnel est qu'il peut être parallélisé vous donne une idée de la connaissance / objectivité de la communauté de programmation fonctionnelle sur ces questions. .
ÉDITER:
Les commentaires ci-dessous soulignent qu'une discussion biaisée des avantages et des inconvénients de la programmation fonctionnelle pure peut ne pas provenir de la «communauté de programmation fonctionnelle». Bon point. Peut-être que les avocats que je vois sont, pour citer un commentaire, «analphabètes».
Par exemple, je pense que ce billet de blog est écrit par quelqu'un qui pourrait être considéré comme représentatif de la communauté de programmation fonctionnelle, et puisqu'il s'agit d'une liste de «points pour une évaluation paresseuse», ce serait un bon endroit pour mentionner tout inconvénient une programmation paresseuse et purement fonctionnelle pourrait avoir. Un bon endroit aurait été à la place du licenciement suivant (techniquement vrai, mais biaisé au point de ne pas être drôle):
la source
Avec une limite supérieure fixe sur l'utilisation de la mémoire, il ne devrait pas y avoir de différence.
Esquisse de preuve: étant donné une limite supérieure fixe sur l'utilisation de la mémoire, on devrait être capable d'écrire une machine virtuelle qui exécute un jeu d'instructions impératif avec la même complexité asymptotique que si vous exécutiez réellement sur cette machine. En effet, vous pouvez gérer la mémoire mutable en tant que structure de données persistante, donnant à O (log (n)) la lecture et l'écriture, mais avec une limite supérieure fixe sur l'utilisation de la mémoire, vous pouvez avoir une quantité fixe de mémoire, ce qui provoque leur décroissance en O (1). Ainsi, l'implémentation fonctionnelle peut être la version impérative exécutée dans l'implémentation fonctionnelle de la machine virtuelle, et elles doivent donc toutes deux avoir la même complexité asymptotique.
la source
Je suggère de lire sur les performances de Haskell , puis de jeter un coup d'œil aux performances des jeux de référence pour les langages fonctionnels par rapport aux langages procéduraux / OO.
la source