Haskell函数定义和缓存数组
我有一个关于在Haskell中使用数组实现缓存(记忆化)的问题。以下模式有效:Haskell函数定义和缓存数组,haskell,memoization,Haskell,Memoization,我有一个关于在Haskell中使用数组实现缓存(记忆化)的问题。以下模式有效: f = (fA !) where fA = listArray... 但事实并非如此(程序的速度表明每次调用或其他操作都会重新创建阵列): 在where子句之外(在“全局范围”中)定义fA也适用于这两种模式 我希望有人能给我提供一个技术上的解释,说明上述两种模式之间的区别 请注意,我使用的是最新的GHC,我不确定这是编译器的特性还是语言本身的一部分 编辑:!用于阵列访问,所以fA!5表示C++语法中的FA(5)
f = (fA !)
where fA = listArray...
但事实并非如此(程序的速度表明每次调用或其他操作都会重新创建阵列):
在where子句之外(在“全局范围”中)定义fA也适用于这两种模式
我希望有人能给我提供一个技术上的解释,说明上述两种模式之间的区别
请注意,我使用的是最新的GHC,我不确定这是编译器的特性还是语言本身的一部分
编辑:!用于阵列访问,所以fA!5表示C++语法中的FA(5)。我对Haskell的理解是,(fA!)n将与(fA!n)相同……而且对我来说,写“fn=fA!n”(不带括号)更为传统。无论如何,无论我如何插入括号,我都会得到相同的行为。找到发生了什么的最好方法是告诉编译器使用
-v4
输出其中间表示形式。输出量很大,读起来有点困难,但应该允许您确切地了解生成代码中的差异,以及编译器是如何到达的
您可能会注意到,在第一个示例中,fA
正在被移出函数(到“全局范围”)。在第二个示例中,它可能不是(这意味着每次调用都会重新创建它)
它没有被移出函数的一个可能原因是编译器认为它取决于n
的值。在您的工作示例中,n
对于fA
来说是不可依赖的
但是,我认为编译器在第二个示例中避免将fA
移到外部的原因是,它试图避免空间泄漏。考虑一下,如果<代码> FA<代码>,而不是数组,则是一个无限列表(在上面使用了<代码>!> /代码>运算符)。想象一下,您曾经用一个大数字(例如F1000
)来调用它,后来只用小数字来调用它(F2
,F3
,F12
)。先前调用中的10000个元素仍在内存中,浪费了空间。因此,为了避免这种情况,每次调用函数时,编译器都会再次创建fA
避免空间泄漏可能不会发生在第一个示例中,因为在这种情况下,f
实际上只调用一次,返回一个闭包(我们现在处于纯函数和命令世界的前沿,所以事情变得更微妙)。这个闭包替换了原来的函数,它将永远不会被调用,因此fA
只被调用一次(因此优化器可以自由地将它移到函数之外)。在第二个示例中,f
不会被闭包替换(因为它的值取决于参数),因此会再次被调用
如果您想进一步了解这一点(这将有助于阅读-v4
输出),您可以看看这篇论文()
至于你的最后一个问题,我认为这是编译器的特点(但我可能错了)。但是,如果所有编译器都做相同的事情,即使它不是语言的一部分,我也不会感到惊讶。Haskell标准没有规定行为上的差异。它所要说的是,函数是相同的(在相同的输入下会产生相同的输出) 然而,在这种情况下,有一种简单的方法可以预测大多数编译器所遵循的时间和内存性能。我再次强调,这不是必要的,只是大多数编译器都这样做 首先,将两个示例改写为纯lambda表达式,展开部分:
f = let fA = listArray ... in \n -> fA ! n
f' = \n -> let fA = listArray ... in fA ! n
编译器使用let绑定来表示共享。保证是在给定的环境中(一组局部变量,lambda body,类似的东西),不带参数的let绑定的右侧最多只计算一次。前者的fA环境是整个程序,因为它不在任何lambda下,但后者的环境较小,因为它在lambda下
这意味着,在后一种情况下,可以针对每个不同的n对fA进行一次评估,而在前一种情况下,这是禁止的
即使使用多参数函数,我们也可以看到这种模式的效果:
g x y = (a ! y) where a = [ x ^ y' | y' <- [0..] ]
g' x = (\y -> a ! y) where a = [ x ^ y' | y' <- [0..] ]
我们可能会多次计算2^100,但是:
let k = g' 2 in k 100 + k 100
我们只计算一次
如果您正在使用Memorization,我推荐data memo Combinators on Hackage,这是一个不同形状的备忘录表库,因此您不必自己滚动。很酷,感谢您的回答,这对我很有帮助,我一定会查看data memo Combinators on Hackage。我来自C++背景,一直在努力理解Haskell在给定程序中会做什么(主要是在复杂性方面),而教程似乎没有涉及到这一点。这里发布了一个类似的问题:-虽然说得更清楚一些,并且有一些很好的回答。
let k = g 2 in k 100 + k 100
let k = g' 2 in k 100 + k 100