[英]Witnessing how frequently a value has been evaluated in Haskell
使用一点unsafe
,您可以看到在 Haskell 中评估了多少惰性值
import Data.IORef
import System.IO.Unsafe
data Nat = Z | S Nat
deriving (Eq, Show, Read, Ord)
natTester :: IORef Nat -> Nat
natTester ref =
let inf = natTester ref
in unsafePerformIO $ do
modifyIORef ref S
pure $ S inf
newNatTester :: IO (Nat, IORef Nat)
newNatTester = do
ref <- newIORef Z
pure (natTester ref, ref)
howMuchWasEvaled :: (Nat -> b) -> IO Nat
howMuchWasEvaled f = do
(inf, infRef) <- newNatTester
f inf `seq` readIORef infRef
和:
ghci> howMuchWasEvaled $ \x -> x > S (S Z)
S (S (S Z))
表示只计算了infinity:: Nat
的前四个构造函数。
如果x
被使用两次,我们仍然得到所需的总评估:
> howMuchWasEvaled $ \x -> x > Z && x > S (S Z)
S (S (S Z))
这是有道理的——一旦我们对x
进行了一定程度的评估,我们就不必重新开始。 thunk 已经被强迫了。
但是有没有办法检查构造函数被评估了多少次? 即,行为如下的 function magic
:
> magic $ \x -> x > Z
S Z
> magic $ \x -> x > Z && x > Z
S (S Z)
...
我知道这可能涉及编译器标志(可能no-cse
)、内联编译指示、非常不安全的函数等。
编辑:卡尔指出,我可能对我所寻找的限制不够清楚。 要求是不能更改作为参数给出magic
的function (尽管可以假设它的参数是惰性的)。 magic
将成为您可以使用自己的函数调用的库的一部分。
也就是说,特定于 GHC 的黑客和只能不可靠地工作的东西绝对仍然是游戏。
如前所述,这不能在 ghc 中完成。 相同名称的两个用法,例如您的示例中的x
,将始终与 ghc 的 haskell 评估 model 的实现共享。 这是一种保证,它为确保共享发生提供了一个关键的构建块。 至少,要让它做你想做的事情将需要传递多个值,一个用于你想要使用命名值的每个独立位置。
然后,您必须确保在调用端,在传递给 function 之前不会意外共享这些值。 这可以完成,但它可能需要使用-fno-cse
或-fno-full-laziness
等选项,具体取决于您如何实现它以及 ghc 运行的优化级别。
这是对您的起点的一个小修改,至少可以在 ghci 中使用:
{-# OPTIONS_GHC -fno-full-laziness #-}
import Data.IORef
import System.IO.Unsafe
data Nat = Z | S Nat
deriving (Eq, Show, Read, Ord)
natTester :: IORef Nat -> Nat
natTester ref =
let inf = natTester ref
in unsafePerformIO $ do
modifyIORef ref S
pure $ S inf
newNatTester :: IO ((a -> Nat), IORef Nat)
newNatTester = do
ref <- newIORef Z
pure (\x -> x `seq` natTester ref, ref)
howMuchWasEvaled :: ((a -> Nat) -> b) -> IO Nat
howMuchWasEvaled f = do
(infGen, infRef) <- newNatTester
f infGen `seq` readIORef infRef
在 ghci 中使用:
*Main> howMuchWasEvaled $ \gen -> let x = gen 1 ; y = gen 2 in x > Z && y > Z
S (S Z)
*Main> howMuchWasEvaled $ \gen -> let x = gen 1 in x > Z && x > Z
S Z
我用传递一个无穷大发生器代替了将单个无穷大传递给 function。 生成器不关心它用什么参数调用,只要它不是底部值。 ( seq
是为了确保 function 实际使用它的参数,以防止 ghc 可能在 go 参数未使用的情况下进行一些优化。)只要每次都使用不同的值调用它,ghc 将无法把它拿走,因为表达方式不同。 如果与优化一起使用,完全惰性可能会受到 newNatTester 中newNatTester
的浮动natTester ref
的干扰。 为了防止这种情况,我添加了一个编译指示来关闭此模块中的优化。 默认情况下在 ghci 中无关紧要,因为它不使用优化。 这个模块是否被编译可能很重要,所以我把 pragma 扔进去只是为了确定。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.