我正在尝试使用Debug.Trace.trace来计算一个函数被评估的次数,并看到一些令人惊讶的结果。
ghci> import Debug.Trace
ghci> let x = (trace " Eval'd!" (* 2)) 3 in (x, x)
( Eval'd!
6, Eval'd!
6)
ghci> let x = (trace " Eval'd!" (* 2)) 3 in x `seq` (x, x)
Eval'd!
( Eval'd!
6, Eval'd!
6)
ghci> let x = (trace " Eval'd!" (* 2)) (3 :: Int) in (x, x)
( Eval'd!
6,6)
我假设Eval'd
每次评估(* 2)
函数时都会打印一次x
。这是正确的假设吗?
其次,为什么这个功能不止一次打印过?我怀疑Num
属于某种未指定类型的(x, x) :: Num a => (a, a)
类型类与它有关,因为第三种情况有效,但我想不出解释。
(x, x) :: (Int, Int)
保证元组的两个元素具有与x
相同的值,那么为什么eval (x, x)
两次?
更新:
实际上我假设Num a => (a, a)
的类型是(x, x) :: (Num t, Num t1) => (t, t1)
。但它显然是t ~ t1
。
为什么GHC没有意识到ggsave
在这里?我怀疑这与我的问题的答案有关。
答案 0 :(得分:3)
他们不保证是同一类型:
Prelude Debug.Trace> :t let x = (trace " Eval'd!" (* 2)) 3 in (x, x)
let x = (trace " Eval'd!" (* 2)) 3 in (x, x)
:: (Num t, Num t1) => (t, t1)
另外,如果你把它放在一个文件中,它只会被评估一次,即使从GHCi调用也是如此。 (这是因为在文件中的声明但不是GHCi中,默认情况下dreaded monomorphism restriction处于启用状态):
import Debug.Trace
main = print $ let x = (trace " Eval'd!" (* 2)) 3 in (x, x)
此外,
let x = (trace " Eval'd!" 6) in (x,x)
行为大致相同,所以它实际上都存在于类型(类)歧义中。
它不共享x
的所有用途的原因是因为在GHC核心级x
,未经优化,实际上是函数采用Num
类型类字典参数。要分享它,它必须做足够的分析才能看出类型是相同的。
它没有意识到的原因基本上是GHCi用于快速代码实验的转变,而不是用于创建好的代码,所以它几乎没有在所有进行优化,所以它几乎是纯粹的运气是否检测到这些事情。
(GHCi字节码设计中还有一个outstanding hole,这意味着你无法为它启用优化级别,即使你想要它。基本上它不支持“unboxed”元组“,GHC优化使用了很多。”