作为使用Haskell数组的简短练习,我想实现一个给出前n个(奇数)素数的函数。以下代码(与GHC 7.10.3编译)在运行时产生循环错误。 “ Haskell的温和介绍”在数组创建中使用了递归调用来计算斐波那契数(https://www.haskell.org/tutorial/arrays.html,13.2,下面的代码供参考),效果很好。我的问题是:
两种递归创建方式之间有何区别?创建数组时通常允许哪些递归调用?
我的代码:
import Data.Array.Unboxed
main = putStrLn $ show $ (primes 500)!500 --arbitrary example
primes :: Int -> UArray Int Int
primes n = a
where
a = array (1,n) $ primelist 1 [3,5..]
primelist i (m:ms) =
if all (not . divides m) [ a!j | j <- [1..(i-1)]]
then (i ,m) : primelist (succ i) ms
else primelist i ms
divides m k = m `mod` k == 0
“ Haskell简介”中的代码:
fibs :: Int -> Array Int Int
fibs n = a where a = array (0,n) ([(0, 1), (1, 1)] ++
[(i, a!(i-2) + a!(i-1)) | i <- [2..n]])
提前感谢您的回答!
答案 0 :(得分:1)
更新:我想我终于了解发生了什么事。 array
在list元素上比较懒惰,但在其脊柱上不必要地 strict !
例如,这会导致<<loop>>
异常
test :: Array Int Int
test = array (1,2) ((1,1) : if test!1 == 1 then [(2,2)] else [(2,100)])
不一样
test :: Array Int Int
test = array (1,2) ((1,1) : [(2, if test!1 == 1 then 2 else 100)])
因此,递归有效,只要它仅影响值。
工作版本:
main :: IO ()
main = do
putStrLn $ show $ (primes 500)!500 --arbitrary example
-- A spine-lazy version of array
-- Assumes the list carries indices lo..hi
arraySpineLazy :: (Int, Int) -> [(Int, a)] -> Array Int a
arraySpineLazy (lo,hi) xs = array (lo,hi) $ go lo xs
where
go i _ | i > hi = []
go i ~((_,e):ys) = (i, e) : go (succ i) ys
primes :: Int -> Array Int Int
primes n = a
where
a :: Array Int Int
a = arraySpineLazy (1,n) $ primelist 1 (2: [3,5..])
primelist :: Int -> [Int] -> [(Int, Int)]
primelist i _ | i > n = []
primelist _ [] = [] -- remove warnings
primelist i (m:ms) =
if all (not . divides m) [ a!j | j <- [1..(i-1)]]
then (i ,m) : primelist (succ i) ms
else primelist i ms
divides m k = m `mod` k == 0
可以说,我们应该改写listArray
的lazier变体,因为我们的array
变体会丢弃该对的第一个组成部分。
这是一个严格性问题:您不能递归地生成 unboxed 数组,只能生成boxed(常规)数组,因为只有boxed的数组才具有惰性语义。
忘记数组,并考虑以下递归对定义
let (x,y) = (0,x)
这递归定义了x=0 ; y=0
。但是,为使递归起作用,必须使该对懒惰。否则,它会生成无限递归,就像以下操作一样:
let p = case p of (x,y) -> (0,x)
以上,p
在评估(,)
对构造函数之前先进行了评估,因此出现了无限循环。相比之下,
let p = (0, case p of (x,y) -> x)
将起作用,因为p
在调用自身之前会产生(,)
。但是请注意,这依赖于构造函数(,)
在返回之前不评估组件-它必须是惰性的,并且必须立即返回,以便稍后进行评估。
操作上,构造了一对在内部有
按照定义,未装箱的数据结构(例如未装箱的数组)避免装箱,因此它们是严格的而不是惰性的,并且它们不能支持相同的递归方法。