Je travaille sur Ecrivez-vous un schéma en 48 heures (je suis à environ 85 heures) et je suis arrivé à la partie sur Ajout de variables et d'affectations . Il y a un grand saut conceptuel dans ce chapitre, et je souhaite qu'il 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…
Je me suis perdu avec un certain nombre de classes différentes qui semblent servir le même but: State
, ST
, IORef
et MVar
. Les trois premiers sont mentionnés dans le texte, tandis que le dernier semble être la réponse privilégiée à de nombreuses questions StackOverflow sur les trois premiers. 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 agrégé pour nous. Cela nous permet de traiter les variables mutables comme nous le ferions dans tout 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 monographie IO ).
Tout cela rend la ligne type ENV = IORef [(String, IORef LispVal)]
déroutante - pourquoi la seconde IORef
? Que se passera-t-il si j'écris à la place type ENV = State [(String, LispVal)]
?
The State Monad: un modèle d'état mutable
La monade d'état est un environnement purement fonctionnel pour les programmes avec état, avec une API simple:
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 d'état mutable dans son implémentation. Au lieu de cela, le programme est paramétré par la valeur d'é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 STRefs
La monade ST est le cousin restreint de la monade IO.
Il permet arbitraire état mutable, 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 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 qui sont 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:
Considérez-le comme le frère le moins dangereux de la IO monade. Ou IO, où vous ne pouvez lire et écrire qu'en mémoire.
IORef: STRefs dans IO
Ce sont des STRefs (voir ci-dessus) dans la monade IO. Ils n'ont pas les mêmes garanties de sécurité que les STRefs concernant la localité.
MVars: IORefs avec verrous
Comme STRefs ou IORefs, mais avec un verrou attaché, pour un accès sûr simultané à partir de plusieurs threads. Les IORefs et les STRefs ne sont sûrs que dans un paramètre multithread lors de l'utilisation de atomicModifyIORef
(une opération atomique de comparaison et d'échange). Les MVars sont un mécanisme plus général pour partager en toute sécurité un état mutable.
En général, à Haskell, utilisez des MVars ou des TVars (cellules mutables basées sur STM), sur STRef ou IORef.
Ok, je vais commencer par IORef
. IORef
fournit une valeur qui est modifiable dans la IO monade. 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 fait 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 y obtenir et définir les données, mais cela nécessite en passant par IO. La raison IO est nécessaire du tout est parce que Haskell est pur ; le compilateur a besoin d'un moyen de savoir à quelles données la référence pointe à un moment donné (lire "Vous auriez pu inventer des monades" de sigfpe blogpost).
MVar
s sont fondamentalement la même chose qu'un IORef, à l'exception de deux différences très importantes. MVar
est une primitive de concurrence, elle est donc conçue pour l'accès à partir de plusieurs threads. La deuxième différence est qu'un MVar
est une boîte qui peut être pleine ou vide. Ainsi, lorsqu'un IORef Int
A toujours un Int
(ou est en bas), un MVar Int
Peut avoir un Int
ou il peut être vide. Si un thread essaie de lire une valeur à partir d'un MVar
vide, il se bloquera jusqu'à ce que le MVar
soit rempli (par un autre thread). Fondamentalement, un MVar a
Équivaut à une IORef (Maybe a)
avec une sémantique supplémentaire utile pour la concurrence.
State
est une monade qui fournit un état mutable, pas nécessairement avec IO. En fait, il est particulièrement utile pour les calculs purs. Si vous avez un algorithme qui utilise l'état mais pas IO
, une monade State
est souvent une solution élégante.
Il existe également une version transformateur monade de State, StateT
. Ceci est fréquemment utilisé pour contenir des données de configuration de programme ou des types d'état "game-world-state" dans les applications.
ST
est quelque chose de légèrement différent. La structure de données principale dans ST
est le STRef
, qui est comme un IORef
mais avec une monade différente. La monade ST
utilise la ruse du système de type (les "threads d'état" mentionnés par les docs) pour garantir 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 les contours et les pointeurs. Cela signifie que ST
peut 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 calcul ST
s'exécute dans un thread séparé avec un stockage local de thread.
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? Que se passera-t-il si je fais à la placetype ENV = State [(String, LispVal)]
?
LISP est un langage fonctionnel à état mutable et à portée lexicale. Imaginez que vous avez fermé une variable mutable. Maintenant, vous avez une référence à cette variable qui traîne dans une autre fonction - disons (dans le 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 printIt
a été défini, mais vous souhaitez rechercher le value auquel ce nom est lié dans l'environnement dans lequel printIt
est appelé (après que setIt
peut avoir été appelé un certain nombre de fois).
Il existe des moyens en plus des deux IORef pour le faire, mais vous avez certainement besoin de plus que le dernier type que vous avez proposé, ce qui ne vous permet pas de modifier les valeurs auxquelles les noms sont liés de manière à portée lexicale. Google le "problème des funargs" pour beaucoup de préhistoire intéressante.