测试一个值是否被评估为弱头标准形式
在Haskell中,是否有可能测试一个值是否被评估为弱头标准形式? 如果一个函数已经存在,我希望它有一个签名
evaluated :: a -> IO Bool
有一些类似功能的地方。
先前的回答向我介绍了:sprint
ghci命令,它将只打印已经被强制为弱头标准格式的那部分值。 :sprint
可以观察一个值是否被评估过:
> let l = ['a'..]
> :sprint l
l = _
> head l
'a'
> :sprint l
l = 'a' : _
IO
可能会检查否则将被禁止的属性。 例如,可以在IO
比较两个值是否来自同一个声明。 这由System.Mem.StableName
的StableName
提供,用于解决数据StableName
中可观察的共享问题。 相关的StablePtr
没有提供一种机制来检查参考值是否是弱头标准形式。
我不确定有什么预包装的。 但是,可以对其进行编码:
import Data.IORef
import System.IO.Unsafe
track :: a -> IO (a, IO Bool)
track val = do
ref <- newIORef False
return
( unsafePerformIO (writeIORef ref True) `seq` val
, readIORef ref
)
以下是ghci中的一个示例用法:
*NFTrack> (value, isEvaluated) <- track (undefined:undefined)
*NFTrack> isEvaluated
False
*NFTrack> case value of _:_ -> "neat!"
"neat!"
*NFTrack> isEvaluated
True
当然,这将跟踪是否将包装后的write-then-then-return-the-original-thunk评估为WHNF,而不是将传递给track
内容评估为WHNF,因此您需要将其作为接近你最感兴趣的thunk--例如它不能告诉你别人做的thunk在跟踪开始之前是否已经被其他人评价过了。 如果你需要线程安全的话,当然可以考虑使用MVar
而不是IORef
。
:sprint
的ghci实现最终使用ghc-prim的unpackClosure#
来检查闭包。 这可以结合堆对象格式的知识来确定闭包是否已被评估为弱头正常形式。
有几种方法可以重现ghci实现对:sprint
执行的检查。 该GHC API公开getClosureData :: DynFlags -> a -> IO Closure
在RtClosureInspect
。 真空包仅依赖ghc-prim,从RtClosureInspect
再现代码并公开getClosure :: a -> IO Closure
。 例如,如何检查这些Closure
表示中的任何一个,并不明显。 所述GHC堆视图包检查封闭和暴露两者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
我们可以通过函数侧面传达信息,而不必通过有选择地强制评估来避免任何unsafe
的事情。 以下内容构建了一对双向连线,我们可以选择强制连线中的一个或另一个。
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
读取我们的比特流。
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
记录的否定答案:重用sprint
机制似乎不可行,因为它与解释性交互评估紧密相关,与原始运行时结构相对 - 据我所知; 我从来没有看过GHC内部。
我开始在GitHub的GHC源代码中搜索“sprint”,后者证明与“print”命令共享一个实现,但是为一个叫做force
的Bool
标志,并且遵循了定义,直到我到达RtClosureInspect.cvObtainTerm,它似乎是一位专业评估员。
上一篇: Test if a value has been evaluated to weak head normal form
下一篇: What does “⊥” mean in “The Strictness Monad” from P. Wadler's paper?