40 votes

Comment les développeurs Haskell expérimentés abordent-ils la paresse au moment de la *conception* ?

Je suis un programmeur Haskell intermédiaire avec des tonnes d'expérience dans les langages FP et non FP stricts. La plupart de mon code Haskell analyse des ensembles de données modérément grands (10^6..10^9 choses), donc la paresse est toujours présente. J'ai une assez bonne compréhension des thunks, du WHNF, du pattern matching et du partage, et j'ai été capable de réparer des fuites avec les bang patterns et les seq, mais cette approche profil-and-pray est sordide et fausse.

Je veux savoir comment les programmeurs Haskell expérimentés abordent la paresse à temps de conception . Il ne s'agit pas de questions faciles comme Data.ByteString.Lazy ou foldl' ; je veux plutôt savoir comment vous pensez à la machinerie paresseuse de bas niveau qui cause des problèmes de mémoire d'exécution et un débogage délicat.

Comment pensez-vous aux thunks, à la correspondance des modèles et au partage pendant la conception ?

Quels modèles de conception et idiomes utilisez-vous pour éviter les fuites ?

Comment avez-vous appris ces modèles et expressions idiomatiques, et avez-vous de bonnes références ?

Comment éviter l'optimisation prématurée des non-problèmes sans fuite ?

(Modifié 2014-05-15 pour la budgétisation du temps) :

Prévoyez-vous un temps de projet important pour la recherche et la résolution des problèmes de mémoire ?

Ou bien, vos compétences en matière de conception vous permettent-elles de contourner les problèmes de mémoire et d'obtenir la consommation de mémoire prévue très tôt dans le cycle de développement ?

45voto

luqui Points 26009

Je pense que la plupart des problèmes de "fuites de rigueur" sont dus au fait que les gens n'ont pas un bon modèle conceptuel. Les Haskellers sans un bon modèle conceptuel ont tendance à avoir et à propager la superstition que plus strict est mieux. Cette intuition provient peut-être des résultats obtenus en jouant avec de petits exemples et des boucles serrées. Mais elle est incorrecte. Il est tout aussi important d'être paresseux aux bons moments que d'être strict aux bons moments.

Il existe deux camps de types de données, généralement appelés "données" et "codonnées". Il est essentiel de respecter les schémas de chacun d'eux.

  • Les opérations qui produisent des "données" (Int, ByteString, ...) doivent être forcées près de l'endroit où elles se produisent. Si j'ajoute un nombre à un accumulateur, je fais attention à ce qu'il soit forcé avant d'en ajouter un autre. Une bonne compréhension de la paresse est très importante ici, en particulier sa nature conditionnelle (c'est-à-dire que les propositions de rigueur ne prennent pas la forme " X est évalué" mais plutôt " quand Y est évalué, il en est de même pour X ").
  • Les opérations qui produisent et consommer "codata" (listes la plupart du temps, arbres, la plupart des autres types récursifs) doivent le faire de manière incrémentielle. Habituellement, la transformation codata -> codata doit produire de l'information pour chaque bit d'information qu'elle consomme (modulo le saut de type filter ). Un autre élément important pour codata est que vous l'utilisez de manière linéaire chaque fois que possible -- c'est-à-dire que vous utilisez la queue d'une liste exactement une fois ; vous utilisez chaque branche d'un arbre exactement une fois. Cela garantit que le GC peut collecter les morceaux au fur et à mesure qu'ils sont consommés.

Il faut faire preuve d'une attention particulière lorsque des données codées contiennent des données. Par exemple. iterate (+1) 0 !! 1000 finira par produire un thunk de taille 1000 avant de l'évaluer. Vous devez à nouveau penser à la rigueur conditionnelle -- la façon d'éviter ce cas est de s'assurer que quand un contre de la liste est consommé, l'ajout de son élément se produit. iterate viole ceci, donc nous avons besoin d'une meilleure version.

iterate' :: (a -> a) -> a -> [a]
iterate' f x = x : (x `seq` iterate' f (f x))

Lorsque vous commencez à composer des choses, il devient bien sûr plus difficile de dire quand les mauvais cas se produisent. En général, il est difficile de créer des structures de données / fonctions efficaces qui fonctionnent aussi bien sur les données que sur les codonnées, et il est important de garder à l'esprit lequel est lequel (même dans un cadre polymorphe où ce n'est pas garanti, vous devriez avoir une idée en tête et essayer de la respecter).

Le partage est délicat, et je pense que je l'aborde surtout au cas par cas. Parce que c'est délicat, j'essaie de le garder localisé, en choisissant de ne pas exposer de grandes structures de données aux utilisateurs de modules en général. Cela peut généralement être fait en exposant des combinateurs pour les modules générant la chose en question, puis la produire et la consommer d'un seul coup (la transformation de la codensité sur les monades en est un exemple).

Mon objectif de conception est de faire en sorte que chaque fonction respecte les modèles de données/codonnées de mes types. J'y parviens généralement (même si cela demande parfois une réflexion approfondie - c'est devenu naturel avec les années), et j'ai rarement des problèmes de fuite lorsque j'y parviens. Mais je ne prétends pas que c'est facile -- cela demande de l'expérience avec les bibliothèques et les modèles canoniques du langage. Ces décisions ne sont pas prises isolément, et tout doit être parfait en même temps pour que cela fonctionne bien. Un instrument mal accordé peut ruiner l'ensemble du concert (c'est pourquoi l'"optimisation par perturbation aléatoire" ne fonctionne presque jamais pour ce type de problèmes).

Apfelmus's Invariants spatiaux L'article est utile pour développer davantage votre intuition de l'espace et des objets. Voir également le commentaire d'Edward Kmett ci-dessous.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X