测试值是否已评估为弱头正常形式

Cir*_*dec 17 haskell lazy-evaluation thunk weak-head-normal-form

在Haskell中,是否可以测试一个值是否已被评估为弱头正常形式?如果一个函数已经存在,我希望它有一个像这样的签名

evaluated :: a -> IO Bool
Run Code Online (Sandbox Code Playgroud)

有一些类似功能的地方.

一个以前的答案给我介绍:sprintghci的命令,该命令将打印已经被迫弱头部正常形态的值只有部分.:sprint可以观察是否已评估某个值:

> let l = ['a'..]
> :sprint l
l = _
> head l
'a'
> :sprint l
l = 'a' : _
Run Code Online (Sandbox Code Playgroud)

有可能IO检查本来是禁止的属性.例如,可以比较IO以查看两个值是否来自同一声明.这是由StableNames in提供System.Mem.StableName并用于解决数据统一中的可观察共享问题.相关内容StablePtr未提供检查引用值是否为弱头正常形式的机制.

Dan*_*ner 12

我不确定是否有预先包装好的东西.但是,可以编写代码:

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
        )
Run Code Online (Sandbox Code Playgroud)

这是ghci中的一个示例用法:

*NFTrack> (value, isEvaluated) <- track (undefined:undefined)
*NFTrack> isEvaluated
False
*NFTrack> case value of _:_ -> "neat!"
"neat!"
*NFTrack> isEvaluated
True
Run Code Online (Sandbox Code Playgroud)

当然,这将跟踪包装的 write-then-then-return-the-original-value thunk是否被评估为WHNF,而不是传递给track它的东西是否被评估为WHNF,所以你想要把它作为接近尽可能地对你感兴趣的thunk - 例如,在跟踪开始之前,它无法告诉你其他人制作的thunk是否已被其他人评估过.当然MVar,IORef如果您需要线程安全,请考虑使用而不是.

  • AFAICS,这里的'False`意味着该值不在whnf中,而'True`意味着它在whnf _或_它正在被评估(并且还没有产生whnf).也许使用``val`seq` unsafePerformIO(writeIORef ref True)`seq` val``可以导致相反的保证(`True`保证whnf,'False`非-whnf /正在进行评估).在三态状态下,人们可以更准确地表达这一点:未评估/正在进行中/未来. (4认同)
  • 我相信`unsinPerformIO`(以及石棉内衣)周围通常需要`NOINLINE`来防止它被优化掉. (2认同)

Cir*_*dec 8

为ghci的实施:sprint最终使用unpackClosure#从GHC-拘谨检查封闭.这可以与堆对象格式的知识结合起来,以确定是否已经将闭包一直评估为弱头正常形式.

有几种方法可以重现ghci实现完成的检查:sprint.GHC api暴露getClosureData :: DynFlags -> a -> IO ClosureRtClosureInspect.的真空包装,其仅取决于GHC-整洁,从再现代码RtClosureInspect并暴露getClosure :: a -> IO Closure.如何检查这些Closure表示中的任何一个并不是很明显,例如,遵循间接.所述GHC堆视图包检查封闭和暴露两者getClosureData :: a -> IO Closure和一个的详细视图Closure.ghc-heap-view取决于GHC api.

我们可以写evaluated在以下方面getBoxedClosureData从GHC堆视图.

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
Run Code Online (Sandbox Code Playgroud)

在评估黑洞时,这种黑洞封闭处理可能是不正确的.选择器闭包的处理可能不正确.AP闭合不是弱头正常形式的假设可能是不正确的.所有其他闭包都在WHNF中的假设几乎肯定是不正确的.

我们的示例将需要两个并发线程在一个线程中观察另一个线程正在评估表达式.

import Data.Char
import Control.Concurrent
Run Code Online (Sandbox Code Playgroud)

我们可以unsafe通过选择性地强制评估而无需借助任何东西来从功能中传递信息.下面构建了一对thunk的流,我们可以选择强制其中一个或另一个.

mkBitStream :: Integer -> [(Integer, Integer)]
mkBitStream a = (a+2, a+3) : mkBitStream (a+1)
Run Code Online (Sandbox Code Playgroud)

zero迫使第一个并one强制第二个.

zero :: [(x, y)] -> [(x, y)]
zero ((x, _):t) = x `seq` t

one :: [(x, y)] -> [(x, y)]
one ((_, y):t) = y `seq` t
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

readBs通过检查一对中的每个thunks是否已经读取我们的比特流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
Run Code Online (Sandbox Code Playgroud)

copy在打印时强制它具有打印关于读取字符串观察到的信息的副作用.

main = do
    let bs = mkBitStream 0
    forkIO (readBs bs)
    text <- getLine
    putStrLn (copy isAlpha bs text)
    getLine
Run Code Online (Sandbox Code Playgroud)

如果我们运行程序并提供输入,abc123我们会观察对应于检查每个字符的副作用isAlpha

abc123
abc123
1
1
1
0
0
0
Run Code Online (Sandbox Code Playgroud)


Kev*_*eid 7

对于记录来说是一个否定的答案:重用机制似乎不可行sprint,因为它与解释的交互式评估紧密相关,而不是原始的运行时结构 - 据我所知; 我以前从未看过GHC内部.

我开始在GitHub上的GHC源代码中搜索"sprint" ,结果是与"print"命令共享一个实现但是对于一个Bool名为的标志force,并且遵循定义直到我得到RtClosureInspect.cvObtainTerm,这似乎是一个专门的评估.