Answers:
GHC不会记住功能。
但是,它确实会在每次输入其周围的lambda表达式时最多计算一次代码中的任何给定表达式,或者如果在顶级时则最多计算一次。当像示例中那样使用语法糖时,确定lambda表达式的位置可能会有些棘手,因此让我们将其转换为等效的已删除语法:
m1' = (!!) (filter odd [1..]) -- NB: See below!
m2' = \n -> (!!) (filter odd [1..]) n
(注意:Haskell 98报告实际上描述了一个左操作符部分(a %)
,等同于\b -> (%) a b
,但是GHC将其删除(%) a
。这在技术上是不同的,因为它们可以被区分seq
。我想我可能已经为此提交了GHC Trac票证。)
鉴于此,您可以看到in m1'
中的表达式filter odd [1..]
不包含在任何lambda-expression中,因此,该表达式每次程序运行仅计算一次,而in中m2'
,filter odd [1..]
则将在每次输入lambda-expression时计算一次,即,在的每次通话中m2'
。这就解释了您所看到的时间上的差异。
实际上,具有某些优化选项的某些版本的GHC将共享比上述说明所指示的更多的值。在某些情况下,这可能会带来问题。例如考虑功能
f = \x -> let y = [1..30000000] in foldl' (+) 0 (y ++ [x])
GHC可能会注意到y
不依赖x
该函数并将其重写为
f = let y = [1..30000000] in \x -> foldl' (+) 0 (y ++ [x])
在这种情况下,新版本的效率要低得多,因为它必须从存储的内存中读取大约1 GB y
,而原始版本将在恒定的空间中运行并适合处理器的缓存。实际上,根据GHC 6.12.1,在不进行优化的情况下f
编译该函数的速度几乎是使用编译时的两倍。-O2
f
:main = interact $ unlines . (show . map f . read) . lines
; 有无编译-O2
; 然后echo 1 | ./main
。如果编写类似的测试main = print (f 5)
,则y
可以在使用时对其进行垃圾回收,并且两者之间没有区别f
。
map (show . f . read)
当然应该是。现在,我已经下载了GHC 6.12.3,看到的结果与GHC 6.12.1中的结果相同。是的,您对GHC 的原始m1
和m2
版本是正确的:在启用了优化的情况下执行这种提升的GHC版本将转换m2
为m1
。
m1仅计算一次,因为它是一个常数适用形式,而m2不是CAF,因此每次评估都要计算一次。
请参阅CAF上的GHC Wiki:http://www.haskell.org/haskellwiki/Constant_applicative_form
[1 ..]
是在程序执行期间仅计算一次,还是针对该功能的每个应用程序计算一次,但是与CAF相关吗?
m1
是CAF,因此第二个适用,并且filter odd [1..]
(不只是[1..]
!)仅计算一次。GHC还可能注意到m2
指向filter odd [1..]
,并放置一个与相同的thunk的链接m1
,但这不是一个好主意:在某些情况下,它可能导致大量内存泄漏。
[1..]
和filter odd [1..]
。对于其余的,我仍然不服气。如果我没记错的话,那么CAF仅在我们要争辩说编译器可以用全局thunk 替换filter odd [1..]
in 时才有意义m2
(该thunk 可能与in中使用的thunk相同m1
)。但在提问者的情况,编译器并没有这样做“优化”,而我不能看到其相关的问题。
m1
,它也。
两种形式之间有一个关键的区别:单态性限制适用于m1而不适用于m2,因为m2明确给出了参数。因此,m2的类型是常规的,而m1的类型是特定的。为其分配的类型为:
m1 :: Int -> Integer
m2 :: (Integral a) => Int -> a
大多数Haskell编译器和解释器(我实际上都知道所有这些)并不记忆多态结构,因此,每次调用m2时都会重新创建m2的内部列表,而不会调用m1的内部列表。
我不确定,因为我本人对Haskell还是陌生的,但似乎是因为第二个函数已参数化,而第一个没有参数化。函数的性质是,其结果取决于输入值,而在功能范例中,它仅取决于输入。明显的含义是,无参数的函数总是反复地返回相同的值,无论如何。
显然,GHC编译器中有一个优化机制,可以利用这一事实在整个程序运行时仅计算一次该函数的值。可以肯定的是,它确实很懒惰,但是仍然可以做到。当我编写以下函数时,我自己注意到了它:
primes = filter isPrime [2..]
where isPrime n = null [factor | factor <- [2..n-1], factor `divides` n]
where f `divides` n = (n `mod` f) == 0
然后进行测试,我进入GHCI并写道:primes !! 1000
。花费了几秒钟,但最终我得到了答案:7927
。然后我打电话primes !! 1001
给我,并立即得到答案。类似地,在瞬间我得到了的结果take 1000 primes
,因为Haskell必须计算整个千元素列表才能返回前1001个元素。
因此,如果您可以编写不带任何参数的函数,则可能需要它。;)
seq
m1 10000000)。但是,如果未指定优化标志,则有所不同。顺便提一下,“ f”的两个变体的最大驻留时间均为5356字节,而与优化无关(使用-O2时总分配较少)。