ghci实现的:sprint
最终使用了来自ghc-prim的
unpackClosure#
来检查一个闭包。这可以与对堆对象格式的
了解相结合,以确定一个闭包是否已经被完全评估到弱头归一范式。
有几种方法可以重现ghci实现:sprint
所做的检查。 GHC api在RtClosureInspect
中公开了getClosureData :: DynFlags -> a -> IO Closure
。仅依赖于ghc-prim的vacuum软件包复制了RtClosureInspect
中的代码,并公开了getClosure :: a -> IO Closure
。不明显如何检查这些Closure
表示的任何一个,例如跟随间接引用。 ghc-heap-view软件包检查闭包并公开了getClosureData :: a -> IO Closure
和详细查看Closure
。ghc-heap-view依赖于GHC api。
我们可以使用ghc-heap-view中的getBoxedClosureData
编写evaluated
。
import GHC.HeapView
evaluated :: a -> IO Bool
evaluated = go . asBox
where
go box = do
c <- getBoxedClosureData box
case c of
ThunkClosure {} -> return False
SelectorClosure {} -> return False
APClosure {} -> return False
APStackClosure {} -> return False
IndClosure {indirectee = b'} -> go b'
BlackholeClosure {indirectee = b'} -> go b'
_ -> return True
在黑洞闭包正在被评估时,处理黑洞闭包的方法可能是不正确的。选择器闭包的处理可能不正确。假设AP闭包不处于弱头正常形式可能是不正确的。所有其他闭包都处于WHNF中的假设几乎肯定是不正确的。
示例
我们的示例将需要两个并发线程,在一个线程中观察到另一个线程正在评估表达式。
import Data.Char
import Control.Concurrent
我们可以通过选择性地强制求值,而无需使用任何不安全的方法来在函数外侧传递信息。以下代码构建了一个thunk对的流,在其中我们可以选择强制求值其中一个或另一个thunk。
mkBitStream :: Integer -> [(Integer, Integer)]
mkBitStream a = (a+2, a+3) : mkBitStream (a+1)
zero
强制执行第一个,one
强制执行第二个。
zero :: [(x, y)] -> [(x, y)]
zero ((x, _):t) = x `seq` t
one :: [(x, y)] -> [(x, y)]
one ((_, y):t) = y `seq` t
copy
是一个有害的身份函数,它会检查数据并强制修改流中的位。
copy :: (a -> Bool) -> [(x, y)] -> [a] -> [a]
copy f bs [] = []
copy f bs (x:xs) = let bs' = if f x then one bs else zero bs
in bs' `seq` (x:copy f bs' xs)
readBs
函数通过检查一对thunk是否已经被 evaluated
来读取我们的位流。
readBs :: [(x, y)] -> IO ()
readBs bs@((f, t):bs') = do
f' <- evaluated f
if f'
then putStrLn "0" >> readBs bs'
else do
t' <- evaluated t
if t'
then putStrLn "1" >> readBs bs'
else readBs bs
在打印时强制使用copy
会导致打印有关读取字符串的信息的副作用。
main = do
let bs = mkBitStream 0
forkIO (readBs bs)
text <- getLine
putStrLn (copy isAlpha bs text)
getLine
如果我们运行程序并提供输入
abc123
,我们将观察到检查每个字符
isAlpha
的副作用。
abc123
abc123
1
1
1
0
0
0