Différence entre State, ST, IORef et MVar

91

Je travaille sur Écrivez-vous un schéma en 48 heures (je suis jusqu'à environ 85 heures) et je suis arrivé à la partie sur l' ajout de variables et d'affectations . Il y a un grand saut conceptuel dans ce chapitre, et j'aurais aimé que cela ait été fait en deux étapes avec une bonne refactorisation entre les deux plutôt que de sauter directement à la solution finale. En tous cas…

J'ai été perdu avec un certain nombre de différentes classes qui semblent servir le même but: State, ST, IORefet MVar. Les trois premiers sont mentionnés dans le texte, tandis que le dernier semble être la réponse préférée à de nombreuses questions StackOverflow sur les trois premières. Ils semblent tous porter un état entre des invocations consécutives.

Quels sont chacun de ces éléments et en quoi diffèrent-ils les uns des autres?


En particulier, ces phrases n'ont pas de sens:

Au lieu de cela, nous utilisons une fonctionnalité appelée threads d'état , permettant à Haskell de gérer l'état d'agrégation pour nous. Cela nous permet de traiter les variables mutables comme nous le ferions dans n'importe quel autre langage de programmation, en utilisant des fonctions pour obtenir ou définir des variables.

et

Le module IORef vous permet d'utiliser des variables avec état dans la monade IO .

Tout cela rend la ligne type ENV = IORef [(String, IORef LispVal)]confuse - pourquoi la seconde IORef? Qu'est-ce qui va casser si j'écris à la type ENV = State [(String, LispVal)]place?

John F. Miller
la source

Réponses:

119

La Monade d'État: un modèle d'état mutable

La monade d'état est un environnement purement fonctionnel pour les programmes avec état, avec une API simple:

  • avoir
  • mettre

Documentation dans le package mtl .

La monade d'état est couramment utilisée lorsque vous avez besoin d'un état dans un seul thread de contrôle. Il n'utilise pas réellement l'état mutable dans son implémentation. Au lieu de cela, le programme est paramétré par la valeur de l'état (c'est-à-dire que l'état est un paramètre supplémentaire à tous les calculs). L'état ne semble être muté que dans un seul thread (et ne peut pas être partagé entre les threads).

La monade ST et les STRef

La monade ST est la cousine restreinte de la monade IO.

Il permet un état mutable arbitraire , implémenté en tant que mémoire mutable réelle sur la machine. L'API est sécurisée dans les programmes sans effets secondaires, car le paramètre de type de rang 2 empêche les valeurs qui dépendent de l'état mutable d'échapper à la portée locale.

Il permet ainsi une mutabilité contrôlée dans des programmes par ailleurs purs.

Couramment utilisé pour les tableaux mutables et autres structures de données mutées, puis gelées. Il est également très efficace, car l'état mutable est "accéléré par le matériel".

API principale:

  • Control.Monad.ST
  • runST - lance un nouveau calcul d'effet mémoire.
  • Et STRefs : pointeurs vers des cellules mutables (locales).
  • Les tableaux basés sur ST (tels que les vecteurs) sont également courants.

Considérez-le comme le frère le moins dangereux de la monade IO. Ou IO, où vous ne pouvez lire et écrire qu'en mémoire.

IORef: STRefs dans IO

Ce sont des STRef (voir ci-dessus) dans la monade IO. Ils n'ont pas les mêmes garanties de sécurité que les STRef sur la localité.

MVars: IORefs avec serrures

Comme STRefs ou IORefs, mais avec un verrou attaché, pour un accès simultané sécurisé à partir de plusieurs threads. Les IORefs et STRefs ne sont sûrs que dans un paramètre multi-thread lors de l'utilisation atomicModifyIORef(opération atomique de comparaison et d'échange). Les MVars sont un mécanisme plus général pour partager en toute sécurité l'état mutable.

Généralement, dans Haskell, utilisez des MVars ou des TVars (cellules mutables basées sur STM), sur STRef ou IORef.

Don Stewart
la source
3
Que signifie le M dans MVars et T dans TVars? Je devine "Mutable", "Transactionnel". Intéressant de voir comment ST signifie State Thread.
CMCDragonkai
10
Pourquoi dites-vous que cela MVardevrait être préféré STRef? STRefgarantit qu'un seul thread peut le muter (et qu'aucun autre type d'E / S ne peut se produire) - c'est sûrement mieux si je n'ai pas besoin d'un accès simultané à l'état mutable?
Benjamin Hodgson
@CMCDragonkai J'ai toujours supposé que le M signifie mutex, mais je ne le trouve documenté nulle part.
Andrew Thaddeus Martin
37

Ok, je vais commencer par IORef. IOReffournit une valeur modifiable dans la monade IO. C'est juste une référence à certaines données, et comme toute référence, il existe des fonctions qui vous permettent de modifier les données auxquelles elles font référence. Dans Haskell, toutes ces fonctions fonctionnent dans IO. Vous pouvez le considérer comme une base de données, un fichier ou un autre magasin de données externe - vous pouvez obtenir et définir les données qu'il contient, mais cela nécessite de passer par IO. La raison pour laquelle IO est absolument nécessaire est que Haskell est pur ; le compilateur a besoin d'un moyen de savoir sur quelles données la référence pointe à un moment donné (lisez l' article de blog de sigfpe "Vous auriez pu inventer des monades" ).

MVars sont fondamentalement la même chose qu'un IORef, à l'exception de deux différences très importantes. MVarest une primitive d'accès concurrentiel, elle est donc conçue pour l'accès à partir de plusieurs threads. La deuxième différence est que an MVarest une boîte qui peut être pleine ou vide. Donc, là où an a IORef Inttoujours un Int(ou est en bas), an MVar Intpeut avoir un Intou il peut être vide. Si un thread essaie de lire une valeur à partir d'un vide MVar, il bloquera jusqu'à ce que le MVarsoit rempli (par un autre thread). Fondamentalement, un MVar aéquivaut à un IORef (Maybe a)avec une sémantique supplémentaire qui est utile pour la concurrence.

Stateest une monade qui fournit un état mutable, pas nécessairement avec IO. En fait, c'est particulièrement utile pour les calculs purs. Si vous avez un algorithme qui utilise l'état mais pas IO, une Statemonade est souvent une solution élégante.

Il existe également une version du transformateur de monade d'Etat, StateT. Ceci est fréquemment utilisé pour contenir des données de configuration de programme, ou des types d'état "état du monde du jeu" dans les applications.

STest quelque chose de légèrement différent. La structure de données principale STest leSTRef , qui est comme un IORefmais avec une monade différente. La STmonade utilise la ruse du système de type (les "threads d'état" mentionnés dans la documentation) pour s'assurer que les données mutables ne peuvent pas échapper à la monade; c'est-à-dire que lorsque vous exécutez un calcul ST, vous obtenez un résultat pur. La raison pour laquelle ST est intéressant est qu'il s'agit d'une monade primitive comme IO, permettant aux calculs d'effectuer des manipulations de bas niveau sur des bytearrays et des pointeurs. Cela signifie que cela STpeut fournir une interface pure tout en utilisant des opérations de bas niveau sur des données mutables, ce qui signifie que c'est très rapide. Du point de vue du programme, c'est comme si le STcalcul s'exécutait dans un thread séparé avec un stockage local du thread.

John L
la source
17

D'autres ont fait l'essentiel, mais pour répondre à la question directe:

Tout cela rend le type de ligne ENV = IORef [(String, IORef LispVal)] déroutant. Pourquoi le deuxième IORef? Qu'est-ce qui va casser si je fais à la type ENV = State [(String, LispVal)]place?

Lisp est un langage fonctionnel avec un état mutable et une portée lexicale. Imaginez que vous avez fermé une variable mutable. Vous avez maintenant une référence à cette variable dans une autre fonction - disons (dans un pseudocode de style haskell) (printIt, setIt) = let x = 5 in (\ () -> print x, \y -> set x y). Vous avez maintenant deux fonctions: l'une imprime x et l'autre définit sa valeur. Lorsque vous évaluez printIt, vous souhaitez rechercher le nom de x dans l'environnement initial dans lequel a printItété défini, mais vous souhaitez rechercher la valeur à laquelle ce nom est lié dans l'environnement dans lequel printItest appelé (après setItpeut avoir été appelé un nombre de fois quelconque ).

Il existe des moyens en plus des deux IORef de le faire, mais vous avez certainement besoin de plus que le dernier type que vous avez proposé, qui ne vous permet pas de modifier les valeurs auxquelles les noms sont liés de manière lexicale. Cherchez sur Google le "problème funargs" pour une préhistoire intéressante.

sclv
la source