web-dev-qa-db-fra.com

Programmation fonctionnelle: des idées appropriées sur la concurrence et l'état?

Les promoteurs PF ont affirmé que la concurrence est facile, car leur paradigme évite l'état mutable. Je ne comprends pas.

Imaginez que nous créons un crawl de donjon multijoueur (A Roguelike) en utilisant FP Où mettons l'accent sur les fonctions pures et les structures de données immuables. Nous générons un donjon composé de chambres, de corridors, de héros, de monstres et de butins. Notre monde est effectivement un graphique d'objet des structures et de leurs relations. Comme les choses changent notre représentation du monde est modifiée pour refléter ces changements. Notre héros tue un rat, ramasse un court-époque, etc.

Pour moi, le monde (la réalité actuelle) porte cette idée d'état et je manque comment FP surmonte ceci. Comme notre héros prend des mesures, des fonctions modifient l'état du monde. Il semble être Chaque décision (AI ou Human) doit être basée sur l'état du monde tel qu'il est dans le présent. Où permettrions-nous de la concurrence? Nous ne pouvons pas avoir de processus multiples ampommenant simultanément l'état du monde de peur d'une base de processus Résultats sur certains Etat expiré. Il me semble que tout contrôle devrait se produire dans une seule boucle de contrôle afin que nous traitons toujours l'état actuel représenté par notre graphique d'objet actuel du monde.

Il existe clairement des situations parfaitement adaptées à la concurrence (c'est-à-dire lors du traitement des tâches isolées dont les États sont indépendants les uns des autres).

Je ne vois pas comment la concurrence est utile dans mon exemple et que cela peut être le problème. Je peux me méfier la réclamation en quelque sorte.

Quelqu'un peut-il mieux représenter cette affirmation?

21
Mario T. Lanza

Écouter quelques riches discussions de Hickey Celui-ci en particulier - a soulagé ma confusion. Il a indiqué qu'il est correct que les processus concurrents peuvent ne pas avoir l'état le plus courant. Je devais entendre ça. Ce que j'avais du mal à digérer, c'était que les programmes seraient bien acceptables de fonder des décisions sur les instantanés du monde qui ont été remplacées depuis des plus récents. Je me suis interdit de vous demander à quel point FP a-t-il eu la question de la baisse des décisions sur l'ancien état.

Dans une demande bancaire, nous ne voudrions jamais baser une décision sur un instantané d'État qui a depuis été remplacée par un nouveau (un retrait survenu).

Cette concurrence est facile, car le FP paradigme évite l'état mutable est une affirmation technique qui ne tent pas de dire quoi que ce soit sur les mérites logiques de la base de décisions sur l'état potentiellement ancien. FP Modèle toujours en fin de compte Changement d'état. Il n'y a pas de contourner cela.

4
Mario T. Lanza

Je vais essayer d'indiquer la réponse. Ceci est non une réponse, seulement une illustration d'introduction. @ JK's Réponse pointe sur la vraie chose, les fermetures à glissière.

Imaginez que vous avez une structure d'arbre immuable. Vous voulez modifier un nœud en insérant un enfant. En conséquence, vous obtenez un tout nouvel arbre.

Mais la plupart du nouvel arbre est exactement le même que le vieil arbre. ne implémentation intelligente réutiliserait la plupart des fragments d'arbres, des pointeurs de routage autour du nœud modifié:

From Wikipedia

Le livre de Okasaki est plein d'exemples comme celui-ci.

Donc, je suppose que vous pourriez raisonnablement altérer de petites parties de votre monde de jeu chaque mouvement (ramasser une pièce de monnaie) et ne changera que de petites parties de votre structure de données mondiale (la cellule où la pièce a été ramassée). Les parties qui n'appartiennent qu'aux états passés seront recueillies à temps.

Cela prend probablement une certaine considération dans la conception de la structure mondiale de jeu de données de manière appropriée. Malheureusement, je ne suis pas un expert en ces questions. Certainement, cela doit être autre chose qu'une matrice NXM utilise comme une structure de données mutable. Il devrait probablement être constitué de pièces plus petites (couloirs? Cellules individuelles?) Cela pointe les uns des autres, comme le font les nœuds d'arbre.

15
9000

Réponse de 9 0 est la moitié de la réponse, les structures de données persistantes vous permettent de réutiliser des pièces inchangées.

Vous pensez peut-être déjà "hé et si je veux changer la racine de l'arbre?" Comme il se tient avec l'exemple étant donné que signifie maintenant changer tous les nœuds. C'est là que - zippers Venez à la rescousse. Ils permettent d'être modifiés dans O (1) et la mise au point peut être déplacée n'importe où dans la structure.

L'autre point avec des fermetures à glissière est que A Zipper existe pour à peu près tout type de données que vous souhaitez

9
jk.

Les programmes de style fonctionnel créent de nombreuses opportunités comme celle-ci à utiliser la concurrence. Chaque fois que vous transformez ou filtrez ou regroupez une collection, et tout est pur ou immuable, il est possible que l'opération soit accumulée par une concurrence.

Par exemple, supposons que vous effectuez des décisions d'AI indépendamment les unes des autres et dans aucun ordre particulier. Ils ne sont pas à tour de rôle, ils prennent tous une décision simultanément, puis le monde avance. Le code pourrait ressembler à ceci:

func MakeMonsterDecision curWorldState monster =
    ...
    ...
    return monsterDecision

func NextWorldState curWorldState =
    ...
    let monsterMakeDecisionForCurrentState = MakeMonsterDecision curWorldState
    let monsterDecisions = List.map monsterMakeDecisionForCurrentState activeMonsters
    ...
    return newWorldState

Vous avez une fonction pour calculer ce que fera un monstre aura donné un État mondial et l'appliquera à chaque monstre dans le cadre de l'informatique de l'État suivant. Il s'agit d'une chose naturelle à faire dans une langue fonctionnelle et le compilateur est libre d'effectuer les "appliquer à chaque monstre" en parallèle.

Dans une langue impérative, vous seriez plus susceptible de parcourir chaque monstre, en appliquant leurs effets au monde. Il est tout simplement plus facile de le faire de cette façon, car vous ne voulez pas faire face au clonage ou à un aliasing compliqué. Le compilateur ne peut pas Effectuer les calculs de monstres en parallèle dans ce cas, car les décisions de monstres précoces ont une incidence sur les décisions de monstres plus tard.

4
Craig Gidney

Les promoteurs PF ont affirmé que la concurrence est facile, car leur paradigme évite l'état mutable. Je ne comprends pas.

Je voulais lancer dans la question de cette question générale comme une personne qui est un néophyte fonctionnel mais qui fait mes yeux dans les effets secondaires au fil des ans et que je voudrais les atténuer, pour toutes sortes de raisons, y compris plus facilement (ou plus précisément ". moins d'erreur ") concurrence. Quand je suis coupé sur mes pairs fonctionnels et ce qu'ils font, l'herbe semble un peu plus écologique et sent plus agréable, du moins à cet égard.

algorithmes série

Cela dit, à propos de votre exemple spécifique, si votre problème est en série de nature et B ne peut pas être exécuté tant que cela ne sera pas terminé, conceptuellement, vous ne pouvez pas exécuter A et B en parallèle, quoi que ce soit. Vous devez trouver un moyen de casser la dépendance de la commande comme dans votre réponse basée sur la création de mouvements parallèles à l'aide d'un ancien état de jeu ou d'utiliser une structure de données qui permet à des éléments de celui-ci d'être modifié de manière indépendante pour éliminer la dépendance des commandes, comme proposé dans les autres réponses. , ou quelque chose de ce genre. Mais il y a définitivement une part de problèmes de concept conceptuels comme celui-ci où vous ne pouvez pas nécessairement tout simplement multipliant si facilement, car les choses sont immuables. Certaines choses vont simplement être en série de la nature jusqu'à ce que vous trouviez un moyen intelligent de casser la dépendance de la commande, si cela est même possible.

simultanéité plus facile

Cela dit, il existe de nombreux cas où nous ne parlons pas de paralléliser des programmes qui impliquent des effets secondaires dans des endroits susceptibles d'améliorer considérablement les performances simplement en raison de la possibilité de ne pas être en sécurité. L'un des cas où éliminant l'état mutable (ou plus spécifiquement d'effets secondaires externes) aide beaucoup comme je le vois, c'est qu'il tourne "peut ou non être thread-coffre-fort" dans "définitivement thread-coffre-fort" .

Pour faire cette déclaration un peu plus concrète, considérez que je vous donne une tâche pour mettre en œuvre une fonction de tri en C qui accepte un comparateur et utilise cela pour trier une gamme d'éléments. Il est censé être assez généralisé, mais je vais vous donner une hypothèse facile qu'elle sera utilisée contre les intrants d'une telle échelle (des millions d'éléments ou plus) qu'il sera sans doute bénéfique pour toujours utiliser une mise en œuvre multithreadée. Pouvez-vous multiplier votre fonction de tri?

Le problème est que vous ne pouvez pas ne pas être parce que les comparateurs de votre fonction de tri appelle peuvent provoquer des effets secondaires à moins que vous sachiez comment ils sont mis en œuvre (ou à tout le moins documenté). Pour tous les cas possibles qui est un peu impossible sans dégénéraliser la fonction. Un comparateur pourrait faire quelque chose de dégoûtant, comme modifier une variable globale à l'intérieur d'une manière non atomique. 99,9999% des comparateurs peuvent ne pas le faire, mais nous ne pouvons toujours pas multiplier cette fonction généralisée simplement en raison de la 0,00001% des cas susceptibles de causer des effets secondaires. En conséquence, vous devrez peut-être proposer une fonction de tri unique et filetée et de passer la responsabilité envers les programmeurs l'utiliser pour décider lequel à utiliser en fonction de la sécurité du fil. Et les gens pourraient toujours utiliser la version à une seule fois fileté et manquer des opportunités de multithread, car ils pourraient également être incertains si le comparateur est le fil-coffre-fort ou s'il restera toujours comme tel à l'avenir.

Il y a beaucoup de cerveaux pouvant être impliqués dans la simple rationalisation de la sécurité du fil des choses sans jeter des serrures partout où nous pouvions nous éloigner si nous n'avions que des garanties difficiles que les fonctions ne proviendront pas d'effets secondaires pour le moment et l'avenir. Et il y a la peur: la peur pratique, parce que quiconque a dû déboguer une condition de race que quelques fois de trop souvent serait probablement hésitant à tout ce qu'ils ne peuvent pas être à 110%, c'est le fil-sûr et restera en tant que tel. Même pour le plus paranoïaque (dont je suis probablement au moins limite), la fonction pure fournit ce sentiment de soulagement et de confiance que nous pouvons l'appeler en toute sécurité en parallèle.

Et c'est l'un des principaux cas où je le considère comme si bénéfique si vous pouvez obtenir une garantie difficile que ces fonctions soient en sécurité que vous obtenez avec des langues fonctionnelles pures. L'autre est que les langues fonctionnelles favorisent souvent la création de fonctions exemptes d'effets secondaires. Par exemple, ils peuvent vous fournir des structures de données persistantes dans lesquelles il est raisonnablement assez efficace d'entrer une structure de données massive, puis de générer une nouvelle une nouvelle avec une petite partie de celle-ci modifiée de l'original sans toucher l'original. Ceux qui travaillent sans ces structures de données peuvent vouloir les modifier directement et perdre une certaine sécurité du thread en cours de route.

Effets secondaires

Cela dit, je suis en désaccord avec une partie avec tout le respect due à mes amis fonctionnels (qui je pense sont super cool):

[...] parce que leur paradigme évite l'état mutable.

Ce n'est pas nécessairement immuabilité qui fait aussi pratique concurrency que je le vois. Il est des fonctions qui puissent causer des effets secondaires. Si une entrée de fonction d'un array trier, copies, puis mute la copie pour trier son contenu et fournit la copie, il est toujours aussi thread-safe comme un travail avec un certain type de tableau immuable, même si vous passez la même entrée réseau à partir de plusieurs threads elle. Donc, je pense qu'il ya encore une place pour les types mutables dans la création d'un code très concurrency convivial, pour ainsi dire, mais il y a beaucoup d'avantages supplémentaires à des types immuables, y compris les structures de données persistantes dont je me sers pas tant pour leurs propriétés immuables mais éliminer les frais d'avoir à tout profond de copie afin de créer des fonctions libres d'effets secondaires.

Et il y a souvent les frais généraux pour faire des fonctions sans effets secondaires sous forme de brassage et la copie des données supplémentaires, peut-être un niveau supplémentaire d'indirection, et peut-être un peu GC sur les parties d'une structure de données persistantes, mais je regarde un de mes amis qui a une machine 32-core et je pense l'échange est probablement la peine si nous pouvons plus faire plus de choses avec confiance en parallèle.

0
user204677