I decided to simplify my code to see what conditions produced the error. I start with a simple nested "s" similar to ST s (STArray s x y)
like so:
{-# LANGUAGE RankNTypes #-}
import Control.Monad.ST
import Control.Applicative
data Foo s = Foo { foo::Bool }
newFoo :: ST s (Foo s)
newFoo = return $ Foo False
To test the state code, I run the following transformation:
changeFoo :: (forall s. ST s (Foo s)) -> ST s (Foo s)
changeFoo sf = do
f <- sf
let f' = Foo (not $ foo f)
return f'
I would like to extract a value from the state while keeping the state, so the next step is to extract the Bool value:
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)
In order to extract that Bool I must use runST
. My understanding is that this will create an additional state, which I specify by providing a forall s.
in the return type:
extractFoo :: (forall s. ST s (Bool,(Foo s))) -> (forall s. (Bool,ST s ((Foo s))))
extractFoo sbf = (runST $ fst <$> sbf,snd <$> sbf)
The example above does compile without the final forall
however in the circumstance I am trying to debug this is not possible. Regardless, in this case it compiles either way.
I am able to use the above code to chain multiple uses of the state together:
testFoo :: (Bool, ST s (Foo s))
testFoo = (b && b',sf')
where
(b,sf) = extractFoo $ splitChangeFoo newFoo
(b',sf') = extractFoo $ splitChangeFoo sf
Now I try to throw IO into the mix and so I make use of the applicative fmap <$>
. This does not compile: (NB. same problem if I use fmap
or >>= return
rather than <$>
)
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
With the following error:
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
I'm aware from this SO question about ST function composition that not all possible uses of ST are accounted for by the compiler. To test this assumption I modified the above function to not use IO and simply pass the return value into a 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
Predictably this also does not compile with an identical error message.
This presents a challenge. I have a reasonable amount of IO that needs to interact with a set of deeply nested ST operations. It works perfectly fine for a single iteration, however I am not able to make further use of the return value.