所以我的问题的简短版本是,一般来说,我们应该如何在 Haskell 中编码循环?Haskell 中没有尾部优化保证,爆炸模式甚至不是标准的一部分(对吗?),折叠/展开范式不能保证在所有情况下都有效。这就是一个很好的例子,只有 bang-patterns 对我来说是让它在恒定空间中运行的技巧(甚至没有使用help $!
...尽管测试是在使用 ghc-6.8.2 的 Ideone.com 上完成的)。
它基本上是关于一个嵌套循环,在列表范式中可以表示为
prod (sum,concat) . unzip $
[ (c, [r | t]) | k<-[0..kmax], j<-[0..jmax], let (c,r,t)=...]
prod (f,g) x = (f.fst $ x, g.snd $ x)
或者在伪代码中:
let list_store = [] in
for k from 0 to kmax
for j from 0 to jmax
if test(k,j)
list_store += [entry(k,j)]
count += local_count(k,j)
result = (count, list_store)
直到我添加了爆炸模式,我得到了内存爆裂甚至堆栈溢出。但是刘海图案不是标准的一部分,对吧?所以问题是,如何在标准 Haskell 中编写上述代码以在恒定空间中运行?
这是测试代码。计算是假的,但问题是一样的。编辑: -formulatedfoldr
代码是:
testR m n = foldr f (0,[])
[ (c, [(i,j) | (i+j) == d ])
| i<- [0..m], j<-[0..n],
let c = if (rem j 3) == 0 then 2 else 1 ]
where d = m + n - 3
f (!c1, []) (!c, h) = (c1+c,h)
f (!c1, (x:_)) (!c, h) = (c1+c,x:h)
尝试运行 print $ testR 1000 1000
会产生堆栈溢出。更改为foldl
仅在使用 bang-patterns 时才会成功f
,但它会以相反的顺序构建列表。我想以正确的顺序懒惰地构建它。fold
对于惯用的解决方案,可以使用任何类型的, 来完成吗?
编辑:总结一下我从@ehird 得到的答案:使用爆炸模式没有什么好害怕的。虽然不在标准 Haskell 本身中,但它很容易在其中编码为f ... c ... = case (seq c False) of {True -> undefined; _ -> ...}
. 教训是,只有模式匹配会强制一个值,并且seq
不会自己强制任何东西,而是安排何时 seq x y
强制 - 通过模式匹配 -x
也会被强制,y
并将成为答案。与我从在线报告中所理解的相反,它$!
本身不会强制执行任何操作,尽管它被称为“严格的应用程序操作员”。
@stephentetley 的观点 -严格性对于控制空间行为非常重要。因此,可以在 Haskell 中对循环进行编码,并在需要时正确使用带有 bang 模式的严格性注释,以编写任何需要的特殊折叠(即结构消耗)函数——就像我最终做的那样- 并依靠 GHC 来优化代码。
非常感谢大家的帮助。