ghci implementation for :sprint 最终使用来自 ghc-prim 的unpackClosure# 来检查闭包。这可以与format of heap objects 的知识相结合,以确定闭包是否已被评估为弱头范式。
有几种方法可以重现 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 和detailed view of the Closure。 ghc-heap-view 依赖于 GHC api。
我们可以在 ghc-heap-view 中将evaluated 写成getBoxedClosureData。
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
我们可以通过选择性地强制评估,在不诉诸任何东西unsafe 的情况下,在函数之外传递信息。下面构建了一个 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