Je voudrais apprendre à créer des graphiques et à effectuer des opérations locales sur eux dans Haskell, mais la question n'est pas spécifique à Haskell, et au lieu de graphiques, nous pouvons envisager des listes doublement liées.
Question: Quelle serait une manière idiomatique ou recommandée d'implémenter une liste doublement liée (ou une autre structure de données doublement liée ou circulaire) et des opérations sur celle-ci dans un langage qui soutient et préconise principalement des structures de données immuables (Haskell, Clojure, etc.) ? En particulier, comment utiliser les mises à jour sur place, qui sont formellement interdites par la langue?
Je peux facilement imaginer que si une opération locale est effectuée sur une liste doublement liée (si un élément est inséré, par exemple), il peut ne pas être nécessaire de copier la liste entière immédiatement en raison de la paresse de la langue. Cependant, étant donné que la liste est doublement liée, si elle est modifiée à un endroit, aucun des anciens nœuds ne peut être utilisé dans la nouvelle version de la liste, et ils devraient être marqués, copiés, récupérés tôt ou tard. . Évidemment, ce sont des opérations redondantes si seule la copie mise à jour de la liste doit être utilisée, mais elles ajouteraient une "surcharge" proportionnelle à la taille de la liste.
Cela signifie-t-il que pour de telles tâches, les données immuables sont tout simplement inappropriées et que les langages déclaratifs fonctionnels sans prise en charge "native" des données mutables ne sont pas aussi bons que les langages impératifs? Ou, y a-t-il une solution de contournement délicate?
PS J'ai trouvé quelques articles et présentations sur ce sujet sur Internet mais j'ai eu du mal à les suivre, alors que je pense que la réponse à cette question ne devrait pas prendre plus d'un paragraphe et peut-être un schéma ... Je veux dire, s'il y a pas de solution "fonctionnelle" à ce problème, la réponse est probablement "utiliser C". S'il y en a un, dans quelle mesure cela peut-il être compliqué?
Questions connexes
"Structures de données en programmation fonctionnelle" . Ma question spécifique sur l'utilisation des mises à jour sur place au lieu d'alternatives inefficaces n'est pas abordée ici.
"Mutation interne de structures de données persistantes" . Là, l'accent semble être mis sur la mise en œuvre de bas niveau dans un langage non spécifié, alors que ma question porte sur le bon choix d'un langage (fonctionnel ou non) et sur les solutions idiomatiques possibles dans les langages fonctionnels.
Citations pertinentes
Les langages de programmation purement fonctionnels permettent à de nombreux algorithmes d'être exprimés de manière très concise, mais il existe quelques algorithmes dans lesquels l'état de mise à jour sur place semble jouer un rôle crucial. Pour ces algorithmes, les langages purement fonctionnels, qui n'ont pas d'état pouvant être mis à jour, semblent intrinsèquement inefficaces ( [Ponder, McGeer et Ng, 1988] ).
- John Launchbury et Simon Peyton Jones, Lazy Functional State Threads (1994), ainsi que John Launchbury et Simon Peyton Jones, State in Haskell (1995). Ces articles présentent le ST
constructeur de type monadique à Haskell.
DiffArray
type. En regardant la source du paquet diffarray , je vois 91 occurrences deunsafePerformIO
. Il semble que la réponse à ma question soit "oui, non, les langages purement fonctionnels avec des données immuables ne sont pas appropriés pour implémenter des algorithmes qui reposent normalement sur des mises à jour sur place".Map
,IntMap
ouHashMap
) en tant que stockage et de faire des nœuds contiennent des ID des noeuds liés. "Tous les problèmes en informatique peuvent être résolus par un autre niveau d'indirection."Réponses:
Il pourrait y avoir d'autres structures de données immuables efficaces qui correspondent à votre tâche particulière, mais ne sont pas aussi générales qu'une liste à double lien (qui est malheureusement sujette à des bogues de modification simultanés en raison de sa mutabilité). Si vous spécifiez votre problème de manière plus précise, une telle structure peut probablement être trouvée.
La réponse générale pour la traversée (relativement) économique de structures immuables est les lentilles. L'idée est que vous pouvez conserver juste assez d'informations pour reconstruire une structure immuable modifiée à partir de ses parties non modifiées et de la pièce actuellement modifiée, et naviguer dessus jusqu'à un nœud voisin.
Une autre structure utile est une fermeture éclair . (La partie amusante est que la signature de type pour une fermeture éclair d'
objectifest un dérivé mathématique de l'école d'une signature de type de la structure.)Voici quelques liens.
Un chapitre sur les fermetures éclair de "Learn you a Haskell"
Introduction aux objectifs en Scala (diapositives)
la source
Haskell n'empêche pas l'utilisation de structures de données mutables. Ils sont fortement découragés et rendus plus difficiles à utiliser car les parties de code qui les utilisent doivent éventuellement renvoyer une action IO (qui doit finalement être liée à l'action IO renvoyée par la fonction principale), mais cela ne le fait pas. rendre impossible l'utilisation de telles structures si vous en avez vraiment besoin.
Je suggérerais d'étudier l'utilisation de la mémoire transactionnelle logicielle comme voie à suivre. En plus de fournir un moyen efficace de mettre en œuvre des structures mutables, il fournit également des garanties très utiles pour la sécurité des threads. Voir la description du module sur https://hackage.haskell.org/package/stm et la présentation du wiki sur https://wiki.haskell.org/Software_transactional_memory .
la source
MVar
,State
,ST
), donc je vais avoir besoin de comprendre leurs différences et utilisations prévues.ST
, OMI, il convient de le mentionner dans la réponse car il permet d'exécuter un calcul avec état, puis de jeter l'état et d'extraire le résultat en tant que valeur pure.ST
avec STM pour avoir à la fois un accès simultané et un état jetable?main
variable. :) (main
n'a même pas de fonction.)