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
, IORef
et 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?
MVar
devrait être préféréSTRef
?STRef
garantit 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?Ok, je vais commencer par
IORef
.IORef
fournit 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 dansIO
. 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" ).MVar
s sont fondamentalement la même chose qu'un IORef, à l'exception de deux différences très importantes.MVar
est 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 anMVar
est une boîte qui peut être pleine ou vide. Donc, là où an aIORef Int
toujours unInt
(ou est en bas), anMVar Int
peut avoir unInt
ou il peut être vide. Si un thread essaie de lire une valeur à partir d'un videMVar
, il bloquera jusqu'à ce que leMVar
soit rempli (par un autre thread). Fondamentalement, unMVar a
équivaut à unIORef (Maybe a)
avec une sémantique supplémentaire qui est utile pour la concurrence.State
est 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 pasIO
, uneState
monade 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.ST
est quelque chose de légèrement différent. La structure de données principaleST
est leSTRef
, qui est comme unIORef
mais avec une monade différente. LaST
monade 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 celaST
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 leST
calcul s'exécutait dans un thread séparé avec un stockage local du thread.la source
D'autres ont fait l'essentiel, mais pour répondre à la question directe:
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 évaluezprintIt
, vous souhaitez rechercher le nom de x dans l'environnement initial dans lequel aprintIt
été défini, mais vous souhaitez rechercher la valeur à laquelle ce nom est lié dans l'environnement dans lequelprintIt
est appelé (aprèssetIt
peut 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.
la source