STRef和幻影类型



STRef s a中的s是否用具体类型实例化?可以很容易地想象一些代码,其中STRefaInt的上下文中使用。但类型推理似乎并没有给s一个具体的类型。

想象一下像MyList<S, A>这样的伪Java。即使S从未出现在MyList的实现中,实例化像MyList<S, Integer>这样的具体类型(其中不使用具体类型来代替S)也是没有意义的。那么STRef s a是如何工作的呢?

tl;dr-在实践中,它似乎总是在最后初始化为RealWorld

来源指出,s可以在stToIO的调用中实例化为RealWorld,但在其他方面是不实例化的:

--s参数是

--未实例化的类型变量(在"runST"调用内部),或

--"真实世界"(调用"Control.Monad.ST.stToIO"内部).

然而,从ST的实际代码来看,runST似乎使用了一个特定的值realWorld#:

newtype ST s a = ST (STRep s a)
type STRep s a = State# s -> (# State# s, a #)
runST :: (forall s. ST s a) -> a
runST st = runSTRep (case st of { ST st_rep -> st_rep })
runSTRep :: (forall s. STRep s a) -> a
runSTRep st_rep = case st_rep realWorld# of
                        (# _, r #) -> r

realWorld#被定义为GHC源代码中的魔术原语:

realWorldName     = mkWiredInIdName gHC_PRIM  (fsLit "realWorld#")
                                    realWorldPrimIdKey realWorldPrimId
realWorldPrimId :: Id   -- :: State# RealWorld
realWorldPrimId = pcMiscPrelId realWorldName realWorldStatePrimTy
                     (noCafIdInfo `setUnfoldingInfo` evaldUnfolding
                                  `setOneShotInfo` stateHackOneShot)

您也可以在ghci:中确认这一点

Prelude> :set -XMagicHash
Prelude> :m +GHC.Prim
Prelude GHC.Prim> :t realWorld#
realWorld# :: State# RealWorld

根据您的问题,我不知道您是否理解为什么会出现幻影s类型。即使你没有明确要求,让我详细说明一下。

幻影类型的作用

幻影类型的主要用途是约束引用(也称为指针)保持在ST monad的"内部"。粗略地说,当runST返回时,动态分配的数据必须结束其生命周期。

为了了解问题,让我们假设runST的类型是

runST :: ST s a -> a

然后,考虑一下:

data Dummy
let var :: STRef Dummy Int
    var    = runST (newSTRef 0)
    change :: () -> ()
    change = runST (modifySTRef var succ)
    access :: () -> Int
    result :: (Int, ())
    result = (access() , change())
 in result

(上面我添加了一些无用的()参数,使其类似于命令式代码)

现在,上面代码的结果应该是什么?根据评估顺序,它可以是(0,())(1,())。在纯粹的哈斯克尔世界里,这是一个很大的禁忌。

这里的问题是var是从其runST"逃逸"的引用。当你逃离ST monad时,你不再被迫使用monad运算符>>=(或者等效地,do表示法)来顺序化副作用的顺序。如果引用仍然存在,那么我们仍然可以在应该没有副作用的情况下产生副作用。

为了避免此问题,我们将runST限制为在ST s a上工作,其中a不依赖于s。为什么这样?由于newSTRef返回一个STRef s a,即对a的引用标记有幻影类型s,因此返回类型取决于s,并且不能从ST monad到runST中提取。

从技术上讲,这种限制是通过使用秩2类型来实现的:

runST :: (forall s. ST s a) -> a

这里的"forall"用于实现限制。类型是这样说的:选择您想要的任何a,然后为我想要的任意s提供类型为ST s a的值,然后我将返回一个a。请注意,s是由runST选择的,而不是由调用者选择的,所以它绝对可以是任何东西。因此,只有当action :: forall s. ST s a(其中s不受约束,并且a不涉及s)时,类型系统才会接受应用程序runST action(回想一下,在runST选择s之前,调用者必须选择a)。

实现独立性约束确实是一个有点棘手的技巧,但它确实有效。

关于实际问题

将此与您的实际问题联系起来:在runST的实现中,s将被选择为任何具体类型。注意,即使s被简单地选择为runST内的Int,也不会有太大的关系,因为类型系统已经将a约束为独立于s,因此是无引用的。正如@Ganesh所指出的,RealWorld是GHC使用的类型。

您还提到了Java。人们可以尝试在Java中玩类似的把戏,如下所示:(警告,下面是过于简化的代码)

interface ST<S,A> { A call(); }
interface STAction<A> { <S> ST<S,A> call(S dummy); }
...
<A> A runST(STAction<A> action} {
    RealWorld dummy = new RealWorld();
    return action.call(dummy).call();
}

上述STAction中的参数A不能依赖于S

最新更新