Haskell数组创建中允许哪些递归调用?

时间:2018-09-29 08:59:11

标签: haskell recursion

作为使用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]])

提前感谢您的回答!

1 个答案:

答案 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在调用自身之前会产生(,)。但是请注意,这依赖于构造函数(,)在返回之前不评估组件-它必须是惰性的,并且必须立即返回,以便稍后进行评估。

操作上,构造了一对在内部有 thunks 的对子:两个指向代码的指针,它们将在以后评估结果。因此,该对实际上不是一对整数,而是一对间接整数。这被称为“装箱”,即使它花费很少的计算成本,也需要达到懒惰状态。

按照定义,未装箱的数据结构(例如未装箱的数组)避免装箱,因此它们是严格的而不是惰性的,并且它们不能支持相同的递归方法。