【问题标题】:Test if a value has been evaluated to weak head normal form测试一个值是否已被评估为弱头范式
【发布时间】:2015-04-25 13:54:26
【问题描述】:

在 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 中的StableNames 提供的,用于解决data-reify 中的可观察共享问题。相关的StablePtr 没有提供检查引用值是否为弱头正常形式的机制。

【问题讨论】:

  • 有趣的是,这已经是 Google 中“haskell check if whnf”的第二个结果了,至少对我来说:)。
  • 在一个充满希望的注释中,GHC Commentary 建议每个堆对象信息表都包含闭包类型,它看起来应该给你你想要的那种东西寻找。不太乐观的是,我感觉 GHC 的拆箱改造会让整个想法变得很滑。
  • @dfeuer 谢谢。 GHC 评论对写approximate answer 很有帮助。

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


【解决方案1】:

我不确定是否为此预先打包了任何东西。但是,可以对其进行编码:

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

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

【讨论】:

  • 我相信NOINLINE 周围通常需要unsafePerformIO(以及石棉内衣),以防止它被优化掉。
  • AFAICS,这里 False 表示该值不在 whnf 中,而 True 表示它在 whnf 中,它正在被评估(并且还没有产生whnf)。也许使用val `seq` unsafePerformIO (writeIORef ref True) `seq` val 会导致相反的保证(True 保证 whnf,False 非 whnf/评估正在进行中)。使用三态状态可以更准确地表示这一点:未评估/进行中/whnf。
【解决方案2】:

ghci implementation for :sprint 最终使用来自 ghc-prim 的unpackClosure# 来检查闭包。这可以与format of heap objects 的知识相结合,以确定闭包是否已被评估为弱头范式。

有几种方法可以重现 ghci 实现对 :sprint 所做的检查。 GHC api 在RtClosureInspect 中公开getClosureData :: DynFlags -&gt; a -&gt; IO Closure。仅依赖 ghc-prim 的 vacuum 包从 RtClosureInspect 复制代码并暴露 getClosure :: a -&gt; IO Closure。例如,如何检查这些Closure 表示中的任何一个以遵循间接指令并不是很明显。 ghc-heap-view 包检查闭包并公开getClosureData :: a -&gt; IO Closuredetailed 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

【讨论】:

  • 我认为您可以通过添加至少最相关的闭包类型的简短描述来改进此答案。
【解决方案3】:

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

我首先在the GHC source on GitHub 中搜索“sprint”,结果发现它与“print”命令共享一个实现,但使用名为forceBool 标志,并遵循定义直到找到@987654322 @ 似乎是一个专门的评估者。

【讨论】:

【解决方案4】:

最近有一个提案,可能已经在某个地方实现了https://mail.haskell.org/pipermail/libraries/2015-February/024917.html

【讨论】:

    猜你喜欢
    • 2015-09-03
    • 1970-01-01
    • 1970-01-01
    • 2011-10-15
    • 2017-03-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-20
    相关资源
    最近更新 更多