繁体   English   中英

见证在 Haskell 中评估值的频率

[英]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 )、内联编译指示、非常不安全的函数等。

编辑:卡尔指出,我可能对我所寻找的限制不够清楚。 要求是不能更改作为参数给出magicfunction (尽管可以假设它的参数是惰性的)。 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM