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

时间:2015-02-24 02:51:30

标签: haskell lazy-evaluation thunk weak-head-normal-form

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

这样的签名
evaluated :: a -> IO Bool

有一些类似功能的地方。

previous answer向我介绍了:sprint ghci命令,该命令将仅打印已被强制为弱头正常形式的值的一部分。 :sprint可以观察是否已评估某个值:

> let l = ['a'..]
> :sprint l
l = _
> head l
'a'
> :sprint l
l = 'a' : _

IO中可以检查否则会被禁止的属性。例如,可以在IO中进行比较,看看两个值是否来自同一个声明。这由System.Mem.StableName中的StableName提供,用于解决data-reify中的可观察共享问题。相关的StablePtr没有提供检查引用值是否为弱头正常形式的机制。

4 个答案:

答案 0 :(得分: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
        )

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

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

当然,这将跟踪包裹写入并然后返回原始值thunk是否被评估为WHNF,而不是是否传递给track被评估为WHNF,因此您希望尽可能接近您感兴趣的thunk - 例如在跟踪开始之前,它无法告诉您其他人制作的thunk是否已被其他人评估过。当然,如果您需要线程安全,请考虑使用MVar而不是IORef

答案 1 :(得分:8)

ghci implementation for :sprint最终使用ghc-prim中的unpackClosure#来检查闭包。这可以与format of heap objects的知识相结合,以确定是否已经对弱头正常形式进行了评估。

有几种方法可以重现:sprint的ghci实现所完成的检查。 GHC api在RtClosureInspect中公开getClosureData :: DynFlags -> a -> IO Closurevacuum包仅依赖于ghc-prim,从RtClosureInspect再现代码并公开getClosure :: a -> IO Closure。如何检查这些Closure表示中的任何一个,例如,遵循间接性,并不是很明显。 ghc-heap-view包检查闭包并公开getClosureData :: a -> IO Closuredetailed view of the 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来从函数中横向传递信息。下面构建了一对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通过检查对中的每个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

打印时强制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

答案 2 :(得分:7)

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

我开始在the GHC source on GitHub中搜索“sprint”,结果是与“print”命令共享一个实现,但是对于一个名为Bool的{​​{1}}标志,并遵循定义直到我到RtClosureInspect.cvObtainTerm,这似乎是一个专门的评估者。

答案 3 :(得分:1)

最近有一个提案,也许它已在某个地方实施https://mail.haskell.org/pipermail/libraries/2015-February/024917.html