这是我试图在haskell中转换的代码。但不变性造成了问题。如何在没有可变变量的情况下进行计算。创建[1..20]
列表并在其上进行映射似乎不太节省空间。是否有任何技巧或技巧可以逃避不变性?
int main()
{
int i,n=0;
double sum= 0.0;
for ( i=1; i<20; ++i )
{
n += i;
sum = 1 / (double) n;
}
printf("The sum is: %lf",sum);
return 0;
}
答案 0 :(得分:5)
使用scanl1
生成n
s:
> scanl1 (+) $ [1..19]
[1,3,6,10,15,21,28,36,45,55,66,78,91,105,120,136,153,171,190]
从那里,计算反转是一件简单的事情
> map (1/) . scanl1 (+) $ [1..19]
[1.0,0.3333333333333333,0.16666666666666666, ...]
并总结:
> sum . map (1/) . scanl1 (+) $ [1..19]
1.8999999999999997
虽然 Haskell 代码将值视为不可变的,并且看起来您正在生成一堆中间列表,但编译器足够智能以生成有效的可执行代码。
答案 1 :(得分:4)
您可以使用-ddump-asm
检查GHC生成的代码。
编译此程序:
main = print $ 1.0 / fromIntegral (sum [(1::Int)..12345])
使用:
$ ghc -O2 Main.hs -ddump-asm > asm-output
然后查看asm-output
,搜索12345
,您会看到此循环:
_c47b:
addq %r14,%rsi
incq %r14
_c476:
cmpq $12345,%r14
jne _c47b
这表明实际上并未创建列表[1..12345]
。
<强>更新强>
似乎你打算将三角数的倒数相加, 即1/1 + 1/3 + 1/6 + ......也就是说,你打算写:
sum += 1.0 / (double) n;
这可以在Haskell中表示为:
main = print $ sum $ map (\x -> 1 / (fromIntegral x)) $ scanl (+) 1 [(2::Int)..12345]
检查生成的程序集,我们再次看到没有创建中间列表:
_c4ao:
cvtsi2sdq %rsi,%xmm0
movsd _n4aP(%rip),%xmm2
divsd %xmm0,%xmm2
addsd %xmm2,%xmm1
incq %r14
_c4ad:
addq %r14,%rsi
cmpq $12345,%r14
jne _c4ao
此处%r14
是您的C代码中的计数器i
,%rsi
是变量n
,%xmm1
是累加器sum
。
答案 2 :(得分:3)
要暂时避开不变性,可以使用the ST
monad。
然而,没有必要这样做。在下面的表达式中,由于Stream Fusion:
,编译器将优化所有内容sum (map (\n -> 1 / fromIntegral n) [1..20])