我将尝试解释runST
' 类型的原因:
runST :: (forall s. ST s a) -> a
为什么它不像这个简单的:
alternativeRunST :: ST s a -> a
请注意,这alternativeRunST
对您的程序有效。
alternativeRunST
还允许我们从ST
monad 中泄漏变量:
leakyVar :: STRef s Int
leakyVar = alternativeRunST (newSTRef 0)
evilFunction :: Int -> Int
evilFunction x =
alternativeRunST $ do
val <- readSTRef leakyVar
writeSTRef leakyVar (val+1)
return (val + x)
然后你可以进入 ghci 并执行以下操作:
>>> map evilFunction [7,7,7]
[7,8,9]
evilFunction
不是参照透明的!
顺便说一句,要自己尝试一下,这是运行上述代码所需的“坏 ST”框架:
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
import Control.Monad
import Data.IORef
import System.IO.Unsafe
newtype ST s a = ST { unST :: IO a } deriving Monad
newtype STRef s a = STRef { unSTRef :: IORef a }
alternativeRunST :: ST s a -> a
alternativeRunST = unsafePerformIO . unST
newSTRef :: a -> ST s (STRef s a)
newSTRef = ST . liftM STRef . newIORef
readSTRef :: STRef s a -> ST s a
readSTRef = ST . readIORef . unSTRef
writeSTRef :: STRef s a -> a -> ST s ()
writeSTRef ref = ST . writeIORef (unSTRef ref)
真实runST
不允许我们构造这样的“邪恶”功能。它是如何做到的?这有点棘手,见下文:
试图运行:
>>> runST (newSTRef "Hi")
error:
Couldn't match type `a' with `STRef s [Char]'
...
>>> :t runST
runST :: (forall s. ST s a) -> a
>>> :t newSTRef "Hi"
newSTRef "Hi" :: ST s (STRef s [Char])
newSTRef "Hi"
不适合(forall s. ST s a)
。使用一个更简单的例子也可以看出,GHC 给了我们一个非常好的错误:
dontEvenRunST :: (forall s. ST s a) -> Int
dontEvenRunST = const 0
>>> dontEvenRunST (newSTRef "Hi")
<interactive>:14:1:
Couldn't match type `a0' with `STRef s [Char]'
because type variable `s' would escape its scope
注意我们也可以写
dontEvenRunST :: forall a. (forall s. ST s a) -> Int
它相当于forall a.
像我们之前所做的那样省略了。
注意 的范围a
比的要大s
,但在情况下newSTRef "Hi"
它的值应该取决于s
。类型系统不允许这样做。