web-dev-qa-db-ja.com

STモナドはどのように機能しますか?

STモナドはIOの弟のようなもので、RealWorldマジックが追加された状態モナドであると理解しています。状態を描き、RealWorldがどういうわけかIOに組み込まれていることを描きますが、STの型シグネチャを書き込むたびに、STモナドのsが混乱します。

たとえば、ST s (STArray s a b)を取ります。 sはどのように機能しますか? (forallにより)状態モナドの状態のように参照できずに、計算間の人工的なデータ依存関係を構築するために使用されているだけですか?

私はただアイデアを捨てているだけで、私にそれを説明してくれるより知識のある人に本当に感謝しています。

71
David

sは、STモナド内のオブジェクトが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

さて、これは型エラーです(これは良いことです!STRefが元の計算の外にリークしないようにしてください!)。余分なsが原因で型エラーが発生します。 runSTの署名は次のとおりです。

runST :: (forall s . ST s a) -> a

これは、実行している計算のsに制約を課す必要がないことを意味します。したがって、aを評価しようとすると、次のようになります。

a = runST (newSTRef (15 :: Int) :: forall s. ST s (STRef s Int))

結果の型はSTRef s Intになりますが、sforallrunSTの外で「エスケープ」されているため、これは誤りです。型変数は常にforallの内部に出現する必要があり、Haskellでは暗黙的なforall数量詞をどこでも使用できます。 aの戻り値の型を意味のある形で理解できるルールはありません。

forallを使用した別の例forallをエスケープできない理由を明確に示すために、以下に示します。より簡単な例:

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

もちろんf idはエラーです。これは、ブール値がtrueかfalseかによって、CharのリストまたはIntのリストを返すためです。 STの例のように、それは単に間違っています。

一方、sタイプパラメータがない場合は、コードが明らかにかなり偽物です。

STが実際に機能する方法:実装面では、STモナドは実際にはIOモナドと同じですが、わずかに異なるインターフェース。 STモナドを使用すると、実際にはunsafePerformIOまたは同等のものが背後で取得されます。これを安全に実行できる理由は、すべてのST関連関数の型シグネチャ、特にforallの部分が原因です。

72
Dietrich Epp

sは、型システムに安全ではないことをやめるハックに過ぎません。実行時には何も行いません。型チェッカーに不審なことをするプログラムを拒否させるだけです。 (いわゆるファントムタイプであり、タイプチェッカーの頭にのみ存在するものであり、実行時に何にも影響を与えません。)

26