在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
没有提供检查引用值是否为弱头正常形式的机制。
答案 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 Closure
。 vacuum包仅依赖于ghc-prim,从RtClosureInspect
再现代码并公开getClosure :: a -> IO Closure
。如何检查这些Closure
表示中的任何一个,例如,遵循间接性,并不是很明显。 ghc-heap-view包检查闭包并公开getClosureData :: a -> IO Closure
和detailed 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