STモナドはIOの弟のようなもので、RealWorld
マジックが追加された状態モナドであると理解しています。状態を描き、RealWorldがどういうわけかIOに組み込まれていることを描きますが、ST
の型シグネチャを書き込むたびに、STモナドのs
が混乱します。
たとえば、ST s (STArray s a b)
を取ります。 s
はどのように機能しますか? (forall
により)状態モナドの状態のように参照できずに、計算間の人工的なデータ依存関係を構築するために使用されているだけですか?
私はただアイデアを捨てているだけで、私にそれを説明してくれるより知識のある人に本当に感謝しています。
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
になりますが、s
がforall
のrunST
の外で「エスケープ」されているため、これは誤りです。型変数は常に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
の部分が原因です。
s
は、型システムに安全ではないことをやめるハックに過ぎません。実行時には何も行いません。型チェッカーに不審なことをするプログラムを拒否させるだけです。 (いわゆるファントムタイプであり、タイプチェッカーの頭にのみ存在するものであり、実行時に何にも影響を与えません。)