测试一个值是否被评估为弱头标准形式

在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.StableNameStableName提供,用于解决数据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 ClosureRtClosureInspect 。 真空包仅依赖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”命令共享一个实现,但是为一个叫做forceBool标志,并且遵循了定义,直到我到达RtClosureInspect.cvObtainTerm,它似乎是一位专业评估员。

链接地址: http://www.djcxy.com/p/7445.html

上一篇: Test if a value has been evaluated to weak head normal form

下一篇: What does “⊥” mean in “The Strictness Monad” from P. Wadler's paper?