Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/haskell/8.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Haskell 如何避免重新计算具有相同参数的纯函数?_Haskell_Sum_Complexity Theory_Probability_Memoization - Fatal编程技术网

Haskell 如何避免重新计算具有相同参数的纯函数?

Haskell 如何避免重新计算具有相同参数的纯函数?,haskell,sum,complexity-theory,probability,memoization,Haskell,Sum,Complexity Theory,Probability,Memoization,我有以下函数将计数列表转换为离散概率密度函数: freq2prob l = [ (curr / (sum l))) | curr <- l ] freq2prob l=[(curr/(sum l))| curr很简单: freq2prob l = [ curr / s | let s = sum l, curr <- l ] 第二个,显然是同一个代码 freq2prob l = let s = sum l in [ curr / s | curr <- l ] = le

我有以下函数将计数列表转换为离散概率密度函数:

freq2prob l = [ (curr / (sum l))) | curr <- l ]
freq2prob l=[(curr/(sum l))| curr很简单:

freq2prob l = [ curr / s | let s = sum l, curr <- l ] 
第二个,显然是同一个代码

freq2prob l = let s = sum l in [ curr / s | curr <- l ]
 = let s = sum l in
   do
     curr <- l
     return (curr / s)
 = let s=sum l in
   l >>= (\curr -> [curr / s])
freq2prob l=让s=以[curr/s | curr=(\curr->[curr/s]表示的和l)

我们可以使用let语句或where子句:

freq2prob l = let s = sum l in 
              [ curr / s | curr <- l ]
除法函数
(/sum l)
中的
sum l
将只计算一次

这是因为在计算
map f xs
时,编译器不会犯一个基本的错误,即创建要单独计算的函数
f
的多个副本;这是一个会被每次需要时所指向的thunk

作为一个简单而直接的测试,我们可以调查ghci中的粗略计时统计数据,以确定重复使用相同的函数或每次使用稍微不同的函数是否明显更快。首先,我将检查总和的结果是否通常缓存在ghci中:

ghci> sum [2..10000000]
50000004999999
(8.31 secs, 1533723640 bytes)
ghci> sum [2..10000000]
50000004999999
(8.58 secs, 1816661888 bytes)
所以你可以看到它没有被缓存,而且在这些原始数据中有一些变化。 现在,让我们每次乘以同样复杂的东西:

ghci> map (* sum [2..10000000]) [1..10]
[50000004999999,100000009999998,150000014999997,200000019999996,250000024999995,300000029999994,350000034999993,400000039999992,450000044999991,500000049999990]
(8.30 secs, 1534499200 bytes)
因此(包括一点变化,使用
map
将十个数字乘以
sum[2..10000000]
所用的时间几乎与乘以一个数字所用的时间完全相同。将十对数字相乘几乎不需要任何时间。因此ghci(一个解释器,甚至不是一个优化编译器)没有引入同一计算的多个副本

这并不是因为ghci很聪明,而是因为懒惰求值,这是纯函数式编程的一个很好的特性,它所做的工作永远不会超过所需。在大多数编程语言中,如果不将结果保存在变量中,而是将长时间的计算到处传递,则很难进行优化

现在,让我们将其与每次进行稍微不同的计算进行比较,在计算过程中,我们加起来的数字稍微少一些

ghci> map (\x -> sum [x..10000000]) [1..10]
[50000005000000,50000004999999,50000004999997,50000004999994,50000004999990,50000004999985,50000004999979,50000004999972,50000004999964,50000004999955]
(77.98 secs, 16796207024 bytes)

好的,这花费了我们预期的大约十倍的时间,因为现在我们每次都要求它做一件不同的事情。我可以为您验证,每个数字都暂停了,而当我们没有改变计算数字的代价时,它只计算了一次,暂停时间在第一个数字之前,其余的都很快出现。

很简单:
freq2prob l=[curr/s | let s=sum l,curr谢谢!但不清楚为什么
let s=sum l
在列表理解范围之外。在每次迭代中,
curr
值都会被替换。分配给
s
的位置会让人认为它也属于每个迭代。它在“迭代规范”之前,所以它只做了一次。你甚至可以像往常一样写
[1 | 1很好的解释。你能澄清一下为什么
中的
sum l
(/sum l)
保证被缓存?@WillNess,因为它的惰性计算非常出色。解释器不会引入同一个常量表达式的多个副本
sum l
。我对答案进行了更全面的解释。解释器不会引入同一个常量表达式的多个副本
sum l
”,而是函数(比如
(\x->x/sum l)
)()。是的,谢谢。@WillNess是的。
x/sum l
不是一个常量表达式,这就是为什么它不起作用的原因。我认为常量应用形式正是我们要寻找的标准。
ghci> sum [2..10000000]
50000004999999
(8.31 secs, 1533723640 bytes)
ghci> sum [2..10000000]
50000004999999
(8.58 secs, 1816661888 bytes)
ghci> map (* sum [2..10000000]) [1..10]
[50000004999999,100000009999998,150000014999997,200000019999996,250000024999995,300000029999994,350000034999993,400000039999992,450000044999991,500000049999990]
(8.30 secs, 1534499200 bytes)
ghci> map (\x -> sum [x..10000000]) [1..10]
[50000005000000,50000004999999,50000004999997,50000004999994,50000004999990,50000004999985,50000004999979,50000004999972,50000004999964,50000004999955]
(77.98 secs, 16796207024 bytes)