どのような条件でエラーが発生したかを確認するために、コードを単純化することにしました。ST s (STArray s x y)
次のような単純なネストされた「s」から始めます。
{-# LANGUAGE RankNTypes #-}
import Control.Monad.ST
import Control.Applicative
data Foo s = Foo { foo::Bool }
newFoo :: ST s (Foo s)
newFoo = return $ Foo False
状態コードをテストするために、次の変換を実行します。
changeFoo :: (forall s. ST s (Foo s)) -> ST s (Foo s)
changeFoo sf = do
f <- sf
let f' = Foo (not $ foo f)
return f'
状態を維持したまま状態から値を抽出したいので、次のステップは Bool 値を抽出することです。
splitChangeFoo :: (forall s. ST s (Foo s)) -> ST s (Bool,(Foo s))
splitChangeFoo sf = do
f <- changeFoo sf
let b = foo f
return (b,f)
その Bool を抽出するには、 を使用する必要がありますrunST
。私の理解では、これにより追加の状態が作成されます。これはforall s.
、戻り値の型にa を指定することで指定します。
extractFoo :: (forall s. ST s (Bool,(Foo s))) -> (forall s. (Bool,ST s ((Foo s))))
extractFoo sbf = (runST $ fst <$> sbf,snd <$> sbf)
上記の例はファイナルなしでコンパイルされますforall
が、デバッグしようとしている状況ではこれは不可能です。とにかく、この場合、どちらの方法でもコンパイルされます。
上記のコードを使用して、状態の複数の使用を一緒に連鎖させることができます。
testFoo :: (Bool, ST s (Foo s))
testFoo = (b && b',sf')
where
(b,sf) = extractFoo $ splitChangeFoo newFoo
(b',sf') = extractFoo $ splitChangeFoo sf
今、私は IO をミックスに投入しようとしているので、適用可能な fmap を利用しています<$>
。これはコンパイルされません: (NB. または を使用した場合と同じfmap
問題)>>= return
<$>
testBar :: IO (Bool, ST s (Foo s))
testBar = (\(b,sf) -> extractFoo $ splitChangeFoo sf) <$> testBar'
where
testBar' :: IO (Bool, ST s (Foo s))
testBar' = return $ extractFoo $ splitChangeFoo newFoo
次のエラーが発生します。
Couldn't match type `s0' with `s2'
because type variable `s2' would escape its scope
This (rigid, skolem) type variable is bound by
a type expected by the context: ST s2 (Foo s2)
The following variables have types that mention s0
sf :: ST s0 (Foo s0) (bound at src\Tests.hs:132:16)
Expected type: ST s2 (Foo s2)
Actual type: ST s0 (Foo s0)
In the first argument of `splitChangeFoo', namely `sf'
In the second argument of `($)', namely `splitChangeFoo sf'
In the expression: extractFoo $ splitChangeFoo sf
ST関数の構成に関するこのSOの質問から、STのすべての可能な用途がコンパイラによって説明されているわけではないことを認識しています。この仮定をテストするために、上記の関数を変更して IO を使用せず、戻り値をラムダに渡すだけにしました。
testFooBar :: (Bool, ST s (Foo s))
testFooBar = (\(b,sf) -> extractFoo $ splitChangeFoo sf) testFooBar'
where
testFooBar' :: (Bool, ST s (Foo s))
testFooBar' = extractFoo $ splitChangeFoo newFoo
予想通り、これも同じエラー メッセージでコンパイルされません。
これには課題があります。深くネストされた一連の ST 操作と対話する必要がある IO の妥当な量があります。1 回の繰り返しでは問題なく動作しますが、戻り値をさらに利用することはできません。