Je suis certes biaisé en tant qu'appliquant de tels concepts en C ++ par le langage et sa nature, ainsi que mon domaine, et même la façon dont nous utilisons le langage. Mais étant donné ces choses, je pense que les conceptions immuables sont l'aspect le moins intéressant lorsqu'il s'agit de récolter une grande partie des avantages associés à la programmation fonctionnelle, comme la sécurité des threads, la facilité de raisonnement sur le système, la recherche de plus de réutilisation pour les fonctions (et la conclusion que nous pouvons les combiner dans n'importe quel ordre sans mauvaises surprises), etc.
Prenez cet exemple C ++ simpliste (certes pas optimisé pour la simplicité pour éviter de m'embarrasser devant des experts en traitement d'image):
// Inputs an image and outputs a new one with the specified size.
Image resized_image(const Image& src, int new_w, int new_h)
{
Image dst(new_w, new_h);
for (int y=0; y < new_h; ++y)
{
for (int x=0; x < new_w; ++x)
dst[y][x] = src.sample(x / (float)new_w, y / (float)new_h);
}
return dst;
}
Bien que la mise en œuvre de cette fonction mute l'état local (et temporaire) sous la forme de deux variables de compteur et d'une image locale temporaire à produire, elle n'a pas d'effets secondaires externes. Il entre une image et en sort une nouvelle. Nous pouvons le multithread au contenu de nos cœurs. Il est facile de raisonner, de tester en profondeur. C'est exceptionnellement sûr car si quelque chose se déclenche, la nouvelle image est automatiquement supprimée et nous n'avons pas à nous soucier de faire reculer les effets secondaires externes (il n'y a pas d'images externes modifiées en dehors de la portée de la fonction, pour ainsi dire).
Je vois peu à gagner, et potentiellement beaucoup à perdre, en rendant Image
immuable dans le contexte ci-dessus, en C ++, sauf pour rendre potentiellement la fonction ci-dessus plus difficile à mettre en œuvre, et peut-être un peu moins efficace.
Pureté
Les fonctions pures (sans effets secondaires externes ) sont donc très intéressantes pour moi, et je souligne l'importance de les privilégier souvent aux membres de l'équipe même en C ++. Mais les conceptions immuables, appliquées dans un contexte et des nuances généralement absents, ne sont pas aussi intéressantes pour moi car, étant donné la nature impérative de la langue, il est souvent utile et pratique de pouvoir muter certains objets temporaires locaux dans le processus de manière efficace (à la fois pour développeur et matériel) implémentant une fonction pure.
Copie bon marché de structures lourdes
La deuxième propriété la plus utile que je trouve est la possibilité de copier à moindre coût les structures de données vraiment lourdes lorsque le coût de le faire, comme cela serait souvent encouru pour rendre les fonctions pures compte tenu de leur nature stricte d'entrée / sortie, ne serait pas trivial. Ce ne seraient pas de petites structures pouvant tenir sur la pile. Ce seraient de grandes structures lourdes, comme l'ensemble Scene
pour un jeu vidéo.
Dans ce cas, la surcharge de copie pourrait empêcher des opportunités de parallélisme efficace, car il pourrait être difficile de paralléliser la physique et de rendre efficacement sans se verrouiller et se goulotter mutuellement si la physique mute la scène que le moteur de rendu tente simultanément de dessiner, tout en ayant une physique profonde copier la scène de jeu entière juste pour sortir une image avec la physique appliquée pourrait être également inefficace. Cependant, si le système physique était `` pur '' en ce sens qu'il ne faisait qu'entrer une scène et en produire une nouvelle avec la physique appliquée, et qu'une telle pureté ne se faisait pas au détriment des frais généraux de copie astronomique, il pouvait fonctionner en toute sécurité parallèlement à la rendu sans que l'un n'attende l'autre.
Ainsi, la possibilité de copier à moindre coût les données vraiment lourdes de l'état de votre application et de produire de nouvelles versions modifiées avec un coût de traitement et d'utilisation de la mémoire minimal peut vraiment ouvrir de nouvelles portes pour la pureté et le parallélisme efficace, et là je trouve beaucoup de leçons à apprendre de la façon dont les structures de données persistantes sont mises en œuvre. Mais tout ce que nous créons en utilisant de telles leçons ne doit pas être entièrement persistant ou offrir des interfaces immuables (il peut utiliser la copie sur écriture, par exemple, ou un "constructeur / transitoire"), pour atteindre cette capacité à être très bon marché copier et modifier uniquement des sections de la copie sans doubler l'utilisation de la mémoire et l'accès à la mémoire dans notre quête de parallélisme et de pureté dans nos fonctions / systèmes / pipeline.
Immutabilité
Enfin, il y a l'immuabilité que je considère comme la moins intéressante de ces trois, mais elle peut imposer, avec une poigne de fer, lorsque certains modèles d'objet ne sont pas destinés à être utilisés comme temporaires locaux pour une fonction pure, et au lieu de cela dans un contexte plus large, un précieux sorte de "pureté au niveau de l'objet", comme dans toutes les méthodes ne provoquent plus d'effets secondaires externes (ne mutent plus les variables membres en dehors de la portée locale immédiate de la méthode).
Et bien que je le considère comme le moins intéressant de ces trois dans des langages comme C ++, il peut certainement simplifier les tests et la sécurité des threads et le raisonnement des objets non triviaux. Cela peut être une charge de travailler avec la garantie qu'un objet ne peut recevoir aucune combinaison d'états unique en dehors de son constructeur, par exemple, et que nous pouvons le faire circuler librement, même par référence / pointeur sans s'appuyer sur la constance et la lecture. seuls les itérateurs et les poignées et autres, tout en garantissant (enfin, au moins autant que possible dans la langue) que son contenu d'origine ne sera pas modifié.
Mais je trouve cela la propriété la moins intéressante parce que la plupart des objets que je vois aussi bénéfiques que d'être utilisés temporairement, sous une forme mutable, pour implémenter une fonction pure (ou même un concept plus large, comme un "système pur" qui pourrait être un objet ou une série de fonctionne avec l'effet ultime de simplement entrer quelque chose et de sortir quelque chose de nouveau sans toucher à rien d'autre), et je pense que l'immuabilité poussée aux extrémités dans un langage largement impératif est un objectif plutôt contre-productif. Je l'appliquerais avec parcimonie pour les parties de la base de code où cela aide vraiment le plus.
Finalement:
[...] il semblerait que les structures de données persistantes ne soient pas en elles-mêmes suffisantes pour gérer des scénarios où un thread apporte une modification visible aux autres threads. Pour cela, il semble que nous devons utiliser des dispositifs tels que des atomes, des références, de la mémoire transactionnelle logicielle, ou même des verrous classiques et des mécanismes de synchronisation.
Naturellement, si votre conception nécessite que les modifications (au sens de la conception de l'utilisateur) soient visibles simultanément par plusieurs threads au fur et à mesure qu'elles se produisent, nous revenons à la synchronisation ou au moins à la planche à dessin pour trouver des moyens sophistiqués de gérer cela ( J'ai vu des exemples très élaborés utilisés par des experts traitant de ce genre de problèmes en programmation fonctionnelle).
Mais j'ai trouvé qu'une fois que vous obtenez ce type de copie et la capacité de produire des versions partiellement modifiées de structures lourdes à bon marché, comme vous le feriez avec des structures de données persistantes à titre d'exemple, cela ouvre souvent beaucoup de portes et d'opportunités que vous pourriez n'avais pas pensé auparavant à paralléliser du code qui peut s'exécuter complètement indépendamment les uns des autres dans une sorte stricte d'E / S de pipeline parallèle. Même si certaines parties de l'algorithme doivent être de nature sérielle, vous pouvez reporter ce traitement à un seul thread mais constater que s'appuyer sur ces concepts a ouvert des portes pour paralléliser facilement et sans souci 90% du travail lourd, par exemple