web-dev-qa-db-fra.com

Comment fonctionne la monade ST?

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.

71
David

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.

72
Dietrich Epp

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.)

26
MathematicalOrchid