The function runST :: (forall s . ST sa) -> a is where magic happens. The right question to ask is what it takes to perform a calculation with type forall s . ST sa forall s . ST sa .
Reading it as English, this is a calculation that is valid for all variants of s , a phantom variable that indicates the specific "stream" of the ST calculation.
When you use newSTRef :: a -> ST s (STRef sa) , you create a STRef that shares its stream variable with the ST stream that generates it. This means that the type s for the value of v fixed as identical to the type s for the larger thread. This fixation means that operations with v no longer valid for all the "variants" of s . Thus, runST reject operations with v .
Conversely, a calculation like newSTRef True >>= \v -> readSTRef v makes sense in any ST stream. All computation is of a type that is valid for all ST threads s . This means that it can be started using runST .
Typically, runST should wrap the creation of all ST threads. This means that all variants of s inside the runST argument runST arbitrary. If something from the context surrounding runST interacts with the runST argument, then this will most likely correct some of these s options to suit the surrounding context and, therefore, will no longer be freely applied to all s options.
J. abrahamson
source share