我决定简化我的代码,看看是什么条件产生了错误。我从一个简单的嵌套“s”开始,ST s (STArray s x y)
类似于这样:
{-# 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)
上面的示例确实在没有 final 的情况下编译,forall
但是在我尝试调试的情况下这是不可能的。无论如何,在这种情况下,它可以编译任何一种方式。
我可以使用上面的代码将状态的多种用途链接在一起:
testFoo :: (Bool, ST s (Foo s))
testFoo = (b && b',sf')
where
(b,sf) = extractFoo $ splitChangeFoo newFoo
(b',sf') = extractFoo $ splitChangeFoo sf
现在我尝试将 IO 加入其中,因此我使用了应用程序 fmap <$>
。这不编译:(注意,如果我使用fmap
or>>= 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 并简单地将返回值传递给 lambda:
testFooBar :: (Bool, ST s (Foo s))
testFooBar = (\(b,sf) -> extractFoo $ splitChangeFoo sf) testFooBar'
where
testFooBar' :: (Bool, ST s (Foo s))
testFooBar' = extractFoo $ splitChangeFoo newFoo
可以预见的是,这也不会与相同的错误消息一起编译。
这提出了一个挑战。我有合理数量的 IO 需要与一组深度嵌套的 ST 操作进行交互。它对于单次迭代非常有效,但是我无法进一步使用返回值。