Je comprends que la monade ST est quelque chose comme un petit frère d'IO, qui est à son tour la monade d'état avec une magie RealWorld
ajoutée. Je peux imaginer des états et je peux imaginer que RealWorld est en quelque sorte mis dans IO, mais chaque fois que j'écris une signature de type de ST
le s
de la monade ST me confond.
Prenons, par exemple, ST s (STArray s a b)
. Comment fonctionne le s
? Est-il simplement utilisé pour créer une dépendance artificielle des données entre les calculs sans pouvoir être référencé comme des états dans la monade d'état (en raison du forall
)?
Je ne fais que lancer des idées et j'apprécierais vraiment que quelqu'un de mieux informé que moi me l'explique.
s
empêche les objets à l'intérieur de la monade ST
de fuir vers l'extérieur de la monade ST
.
-- This is an error... but let's pretend for a moment...
let a = runST $ newSTRef (15 :: Int)
b = runST $ writeSTRef a 20
c = runST $ readSTRef a
in b `seq` c
D'accord, c'est une erreur de type (ce qui est une bonne chose! Nous ne voulons pas que STRef
fuit en dehors du calcul d'origine!). C'est une erreur de type à cause de l'extra s
. N'oubliez pas que runST
a la signature:
runST :: (forall s . ST s a) -> a
Cela signifie que le s
sur le calcul que vous exécutez ne doit avoir aucune contrainte. Ainsi, lorsque vous essayez d'évaluer a
:
a = runST (newSTRef (15 :: Int) :: forall s. ST s (STRef s Int))
Le résultat aurait le type STRef s Int
, ce qui est faux car le s
s'est "échappé" en dehors du forall
dans runST
. Les variables de type doivent toujours apparaître à l'intérieur d'un forall
, et Haskell autorise les quantificateurs forall
implicites partout. Il n'y a simplement aucune règle qui vous permette de comprendre de manière significative le type de retour de a
.
n autre exemple avec forall
: Pour montrer clairement pourquoi vous ne pouvez pas permettre aux choses d'échapper à un forall
, voici un exemple plus simple:
f :: (forall a. [a] -> b) -> Bool -> b
f g flag =
if flag
then g "abcd"
else g [1,2]
> :t f length
f length :: Bool -> Int
> :t f id
-- error --
Bien sûr f id
est une erreur, car elle renverrait soit une liste de Char
soit une liste de Int
selon que le booléen est vrai ou faux. C'est tout simplement faux, tout comme l'exemple avec ST
.
D'un autre côté, si vous n'aviez pas le paramètre de type s
, alors tout taperait très bien, même si le code est évidemment assez faux.
Comment ST fonctionne réellement: Côté implémentation, la monade ST
est en fait la même que la monade IO
mais avec une interface légèrement différente. Lorsque vous utilisez la monade ST
, vous obtenez en réalité unsafePerformIO
ou l'équivalent, dans les coulisses. La raison pour laquelle vous pouvez le faire en toute sécurité est à cause de la signature de type de toutes les fonctions liées à ST
, en particulier la partie avec forall
.
Le s
est juste un hack qui fait que le système de type vous empêche de faire des choses qui seraient dangereuses. Il ne "fait" rien au moment de l'exécution; cela fait juste que le vérificateur de type rejette les programmes qui font des choses douteuses. (C'est un soi-disant type fantôme, une chose avec n'existe que dans la tête du vérificateur de type, et n'affecte rien au moment de l'exécution.)